Skip to content

Commit 5f2585f

Browse files
committed
feat(deisctl): add trireme support for config
1 parent bd3a694 commit 5f2585f

3 files changed

Lines changed: 277 additions & 9 deletions

File tree

trireme/main.go

Lines changed: 140 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,33 @@ import (
55
"os"
66

77
"github.com/codegangsta/cli"
8+
"github.com/deis/deis/trireme/storage"
89
)
910

11+
var parts = map[string]string{
12+
// In alpha order, please.
13+
"builder": "the builder",
14+
"controller": "the controller",
15+
"database": "the database",
16+
"router": "the router mesh",
17+
"store": "the store and all of its components",
18+
// FIXME: Do the rest.
19+
}
20+
21+
var config storage.Storer
22+
1023
func main() {
24+
25+
// This is temporary: We need a place to store configuration data in
26+
// lieu of etcd. For now, we'll store it locally in a configuration
27+
// file.
28+
var err error
29+
config, err = storage.New(defaultConfigFile())
30+
if err != nil {
31+
fmt.Printf("Failed to load or create file %s", defaultConfigFile())
32+
os.Exit(321)
33+
}
34+
1135
app := cli.NewApp()
1236
app.Name = "trireme"
1337
app.Usage = "A control tool for Deis"
@@ -27,19 +51,126 @@ func commands() []cli.Command {
2751
*/
2852
Subcommands: installCommands(),
2953
},
54+
{
55+
Name: "config",
56+
Usage: "Get and set configuration values",
57+
Subcommands: configCommands(),
58+
},
3059
}
3160
}
3261

33-
func installCommands() []cli.Command {
34-
parts := map[string]string{
35-
// In alpha order, please.
36-
"builder": "the builder",
37-
"controller": "the controller",
38-
"database": "the database",
39-
"router": "the router mesh",
40-
"store": "the store and all of its components",
41-
// FIXME: Do the rest.
62+
func defaultConfigFile() string {
63+
return os.ExpandEnv("${HOME}/.trireme")
64+
}
65+
66+
func configCommands() []cli.Command {
67+
cmds := make([]cli.Command, 0, len(parts)+3)
68+
69+
// Why deprecate `deisctl config <target> set ...`? Three reasons:
70+
// 1. The predominant form of multi-commands is <CMD> <VERB> <DO>..., not
71+
// <CMD> <NOUN> <DO> <VERB>...
72+
// 2. The common unix paradigm for subcommands is to move all variables to
73+
// the arguments portion: <CMD> <SUBCMD> <ARG1>..., not <CMD> <SUBCMD> <ARG1> <SUBCMD> <ARG2>...
74+
// 3. The logic is simply cleaner when arguments are grouped together.
75+
cmds = append(cmds,
76+
cli.Command{
77+
Name: "get",
78+
Usage: "Get an existing parameter from an existing component",
79+
Action: func(c *cli.Context) {
80+
a := c.Args()
81+
if len(a) < 2 {
82+
fmt.Println("Usage: deisctl config get <TARGET> <KEY>")
83+
os.Exit(1)
84+
}
85+
ns := a[0]
86+
key := a[1]
87+
val, err := config.Get(ns, key)
88+
if err != nil {
89+
fmt.Printf("Failed to get %s:%s: '%s'\n", ns, key, err)
90+
os.Exit(2)
91+
}
92+
fmt.Println(val)
93+
},
94+
},
95+
cli.Command{
96+
Name: "set",
97+
Usage: "Set an existing parameter for an existing component",
98+
Action: func(c *cli.Context) {
99+
a := c.Args()
100+
if len(a) < 3 {
101+
fmt.Println("Usage: deisctl config get <TARGET> <KEY> <VALUE>")
102+
os.Exit(1)
103+
}
104+
ns := a[0]
105+
key := a[1]
106+
val := a[2]
107+
if err := config.Set(ns, key, val); err != nil {
108+
fmt.Printf("Failed to set %s:%s=%s: '%s'\n", ns, key, val, err)
109+
os.Exit(3)
110+
}
111+
fmt.Println(val)
112+
},
113+
},
114+
cli.Command{
115+
Name: "rm",
116+
Usage: "Remove an existing parameter from an existing component",
117+
Action: func(c *cli.Context) {
118+
a := c.Args()
119+
if len(a) < 2 {
120+
fmt.Println("Usage: deisctl config rm <TARGET> <KEY>")
121+
os.Exit(1)
122+
}
123+
ns := a[0]
124+
key := a[1]
125+
if err := config.Remove(ns, key); err != nil {
126+
fmt.Printf("Failed to remove %s:%s: '%s'\n", ns, key, err)
127+
os.Exit(4)
128+
}
129+
},
130+
},
131+
)
132+
133+
for n, _ := range parts {
134+
cmds = append(cmds, cli.Command{
135+
Name: n,
136+
Usage: fmt.Sprintf("Deprecated. Use `deisctl config set|get|rm %s`", n),
137+
Action: func(c *cli.Context) {
138+
a := c.Args()
139+
if len(a) < 2 {
140+
fmt.Println("Usage: deisctl config get|set|rm <TARGET> <KEY> [<VALUE>]")
141+
os.Exit(1)
142+
}
143+
key := a[1]
144+
switch a[0] {
145+
case "get":
146+
val, err := config.Get(n, key)
147+
if err != nil {
148+
fmt.Println(err)
149+
os.Exit(2)
150+
}
151+
fmt.Println(val)
152+
case "set":
153+
if err := config.Set(n, key, a[2]); err != nil {
154+
fmt.Println("Usage: deisctl config get|set|rm <TARGET> <KEY> [<VALUE>]")
155+
os.Exit(3)
156+
}
157+
fmt.Println(a[2])
158+
case "rm":
159+
if err := config.Remove(n, key); err != nil {
160+
fmt.Println("Usage: deisctl config get|set|rm <TARGET> <KEY> [<VALUE>]")
161+
os.Exit(4)
162+
}
163+
default:
164+
fmt.Println("Usage: deisctl config get|set|rm <TARGET> <KEY> [<VALUE>]")
165+
os.Exit(1)
166+
}
167+
},
168+
})
42169
}
170+
return cmds
171+
}
172+
173+
func installCommands() []cli.Command {
43174

44175
// This basically ensures that append() will not have to reallocate.
45176
cmds := make([]cli.Command, 0, len(parts)+1)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package storage
2+
3+
import (
4+
"os"
5+
"testing"
6+
)
7+
8+
var filename = "./testdata/storage.json"
9+
10+
func TestJSONStorage(t *testing.T) {
11+
12+
// Interface Canary
13+
var _ Storer = &JSONStorage{}
14+
15+
js, err := New(filename)
16+
if err != nil {
17+
t.Fatal(err)
18+
}
19+
20+
if _, err := js.Get("testdata", "test1"); err == nil {
21+
t.Errorf("Test data appears to be tainted by existing tests.")
22+
}
23+
24+
if err := js.Set("testdata", "test1", "C0FF33"); err != nil {
25+
t.Errorf("Failed to set data: %s", err)
26+
}
27+
28+
if val, err := js.Get("testdata", "test1"); err != nil {
29+
t.Errorf("Failed to get data: %s", err)
30+
} else if val != "C0FF33" {
31+
t.Errorf("Expected 'C0FF33', got '%s'", val)
32+
}
33+
34+
if err := js.Remove("testdata", "test1"); err != nil {
35+
t.Errorf("Failed to remove data: %s", err)
36+
}
37+
38+
if _, err := js.Get("testdata", "test1"); err == nil {
39+
t.Errorf("Test data was not actually deleted.")
40+
}
41+
42+
if _, err := New(filename); err != nil {
43+
t.Errorf("Failed to re-open the new testing file.", err)
44+
}
45+
46+
os.Remove(filename)
47+
}

trireme/storage/storage.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/* Package storage defines a backend for storing and retrieving configuration data.
2+
3+
*/
4+
package storage
5+
6+
import (
7+
"encoding/json"
8+
"errors"
9+
"io/ioutil"
10+
"os"
11+
)
12+
13+
var KeyNotFound = errors.New("Key not found")
14+
15+
type Storer interface {
16+
Get(ns, key string) (string, error)
17+
Set(ns, key, value string) error
18+
Remove(ns, key string) error
19+
}
20+
21+
type storage map[string]map[string]string
22+
23+
type JSONStorage struct {
24+
loc string
25+
s storage
26+
}
27+
28+
func New(loc string) (*JSONStorage, error) {
29+
var s storage
30+
31+
if _, err := os.Stat(loc); err != nil {
32+
if os.IsNotExist(err) {
33+
s := map[string]map[string]string{}
34+
return &JSONStorage{
35+
s: s,
36+
loc: loc,
37+
}, nil
38+
} else {
39+
return nil, err
40+
}
41+
} else {
42+
data, err := ioutil.ReadFile(loc)
43+
if err != nil {
44+
return nil, err
45+
}
46+
if err := json.Unmarshal(data, &s); err != nil {
47+
return nil, err
48+
}
49+
}
50+
51+
return &JSONStorage{s: s, loc: loc}, nil
52+
}
53+
54+
func (j *JSONStorage) Set(ns, key, value string) error {
55+
if _, ok := j.s[ns]; !ok {
56+
j.s[ns] = map[string]string{key: value}
57+
return j.Save()
58+
}
59+
j.s[ns][key] = value
60+
return j.Save()
61+
}
62+
63+
func (j *JSONStorage) Remove(ns, key string) error {
64+
if inner, ok := j.s[ns]; ok {
65+
if _, ok := j.s[ns][key]; ok {
66+
delete(inner, key)
67+
if len(j.s[ns]) == 0 {
68+
delete(j.s, ns)
69+
}
70+
return j.Save()
71+
}
72+
}
73+
return KeyNotFound
74+
}
75+
func (j *JSONStorage) Get(ns, key string) (string, error) {
76+
if _, ok := j.s[ns]; ok {
77+
if val, ok := j.s[ns][key]; ok {
78+
return val, nil
79+
}
80+
}
81+
return "", KeyNotFound
82+
}
83+
84+
func (j *JSONStorage) Save() error {
85+
data, err := json.Marshal(j.s)
86+
if err != nil {
87+
return err
88+
}
89+
return ioutil.WriteFile(j.loc, data, 0770)
90+
}

0 commit comments

Comments
 (0)