Skip to content
This repository was archived by the owner on Jan 21, 2020. It is now read-only.

Commit 0755e99

Browse files
author
David Chung
authored
Vars Metadata Plugin (#716)
Signed-off-by: David Chung <[email protected]>
1 parent 4c54e32 commit 0755e99

File tree

24 files changed

+748
-98
lines changed

24 files changed

+748
-98
lines changed

cmd/infrakit/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import (
6767
_ "github.com/docker/infrakit/pkg/run/v0/time"
6868
_ "github.com/docker/infrakit/pkg/run/v0/vagrant"
6969
_ "github.com/docker/infrakit/pkg/run/v0/vanilla"
70+
_ "github.com/docker/infrakit/pkg/run/v0/vars"
7071
_ "github.com/docker/infrakit/pkg/run/v0/vsphere"
7172
)
7273

cmd/infrakit/manager/manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ func Command(plugins func() discovery.Plugins) *cobra.Command {
325325
return err
326326
}
327327

328-
types.Sort(all)
328+
types.SortPaths(all)
329329
for _, p := range all {
330330
fmt.Println(p.String())
331331
}

docs/plugin/metadata/vars/README.md

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
Vars Plugin
2+
===========
3+
4+
Design
5+
======
6+
7+
The `vars` plugin implements the [`metadata.Updatable`](../pkg/spi/metadata) SPI. It supports
8+
9+
+ Reading of metadata values
10+
+ Updating of metadata values
11+
12+
The base Metadata SPI looks like this:
13+
14+
```
15+
// Plugin is the interface for metadata-related operations.
16+
type Plugin interface {
17+
18+
// List returns a list of *child nodes* given a path, which is specified as a slice
19+
List(path types.Path) (child []string, err error)
20+
21+
// Get retrieves the value at path given.
22+
Get(path types.Path) (value *types.Any, err error)
23+
}
24+
```
25+
26+
The `Updatable` SPI adds two methods for changes and commit:
27+
28+
```
29+
// Updatable is the interface for updating metadata
30+
type Updatable interface {
31+
32+
// Plugin - embeds a readonly plugin interface
33+
Plugin
34+
35+
// Changes sends a batch of changes and gets in return a proposed view of configuration and a cas hash.
36+
Changes(changes []Change) (original, proposed *types.Any, cas string, err error)
37+
38+
// Commit asks the plugin to commit the proposed view with the cas. The cas is used for
39+
// optimistic concurrency control.
40+
Commit(proposed *types.Any, cas string) error
41+
}
42+
```
43+
44+
where `Change` captures atom of change:
45+
46+
```
47+
// Change is an update to the metadata / config
48+
type Change struct {
49+
Path types.Path
50+
Value *types.Any
51+
}
52+
```
53+
54+
Updating metadata entries involve creating a set of Changes and then sending it to the plugin to get
55+
a proposal, a hash, and a view of the current dataset in its entirety. This is followed by a commit
56+
which takes the returned proposal view and the hash. If the data has been updated before the commit
57+
is issued, this commit will fail because the hash value will be different now than the one returned
58+
via the `Changes` call. This is how the plugin handles optimistic concurrency.
59+
60+
The plugin takes a template URL as a way to initialize itself with some data. This is useful for cases
61+
where there's already a set of parameters in an JSON that's in version control, and you can use that
62+
as an initial value. The URL is set as an [`Option` attribute](./pkg/run/v0/vars/vars.go) so it can
63+
be specified in the `plugins.json` passed to `infrakit plugin start`. You can also use the environment
64+
variable `INFRAKIT_VARS_TEMPLATE` as a way to set it.
65+
66+
Corresponding to the SPI methods, there are new commands / verbs as `metadata`:
67+
68+
+ `ls` lists the metadata paths
69+
+ `cat` reads metadata values
70+
+ `change` updates metadata values, provided the plugin implements the updatable SPI (not readonly)
71+
72+
You can try it out using the `vars` kind:
73+
74+
```shell
75+
INFRAKIT_VARS_TEMPLATE=file://$(pwd)/docs/plugin/metadata/vars/example.json infrakit plugin start vars
76+
```
77+
78+
We start up the plugin using `example.json` here as initial values:
79+
```
80+
{{ var `cluster/user/name` `user` }}
81+
{{ var `zones/east/cidr` `10.20.100.100/24` }}
82+
{{ var `zones/west/cidr` `10.20.100.200/24` }}
83+
84+
{
85+
"cluster" : {
86+
"size" : 5,
87+
"name" : "test"
88+
},
89+
"shell" : {{ env `SHELL` }}
90+
}
91+
```
92+
93+
Note that this file is itself a template. You can 'export' parameter values via the `var` function, as
94+
well as, creating the actual JSON object in this document.
95+
96+
Now in another terminal session, you should see `vars` show up as a subcommand in `infrakit`
97+
98+
```shell
99+
$ infrakit -h
100+
101+
102+
infrakit command line interface
103+
104+
Usage:
105+
infrakit [command]
106+
107+
Available Commands:
108+
manager Access the manager
109+
playbook Manage playbooks
110+
plugin Manage plugins
111+
remote Manage remotes
112+
template Render an infrakit template at given url. If url is '-', read from stdin
113+
up Up everything
114+
util Utilities
115+
vars Access object vars which implements Updatable/0.1.0
116+
version Print build version information
117+
x Experimental features
118+
```
119+
120+
Getting help:
121+
122+
```shell
123+
$ infrakit vars metadata -h
124+
125+
126+
Access metadata of vars
127+
128+
Usage:
129+
infrakit vars metadata [command]
130+
131+
Available Commands:
132+
cat Get metadata entry by path
133+
change Update metadata where args are key=value pairs and keys are within namespace of the plugin.
134+
ls List metadata
135+
```
136+
137+
### Listing, Reading
138+
139+
Listing metadata values:
140+
141+
```shell
142+
$ infrakit vars metadata ls -al
143+
total 6:
144+
cluster/name
145+
cluster/size
146+
cluster/user/name
147+
shell
148+
zones/east/cidr
149+
zones/west/cidr
150+
```
151+
152+
or
153+
154+
```shell
155+
$ infrakit vars metadata ls -al zones
156+
total 2:
157+
east/cidr
158+
west/cidr
159+
```
160+
161+
Reading a value using `cat`:
162+
163+
```shell
164+
$ infrakit vars metadata cat zones/east/cidr
165+
10.20.100.100/24
166+
```
167+
168+
Complex values:
169+
170+
```shell
171+
$ infrakit vars metadata cat zones
172+
{"east":{"cidr":"10.20.100.100/24"},"west":{"cidr":"10.20.100.200/24"}}
173+
```
174+
175+
You can also use the `metadata` template function and evaluate an inline template:
176+
177+
```shell
178+
$ infrakit template 'str://The CIDR is {{ metadata `vars/zones/east/cidr`}}!'
179+
The CIDR is 10.20.100.100/24!
180+
```
181+
182+
Formatting it as YAML, if the value at a given path is actually a struct/object:
183+
184+
```shell
185+
$ infrakit template 'str://{{ metadata `vars/zones` | yamlEncode }}'
186+
east:
187+
cidr: 10.20.100.100/24
188+
west:
189+
cidr: 10.20.100.200/24
190+
```
191+
192+
### Updating
193+
194+
In the `infrakit` CLI, the `Changes` + `Commit` steps have been combined into a single `change`
195+
verb with `-c` for commit.
196+
197+
The `change` verb is followed by a list of `name=value` pairs which are committed togeter as one
198+
unit. The `change` verb either prints the proposal or prints the proposal and commits if `-c` is set.
199+
200+
No changes means getting a dump of the entire plugin's metadata as a document:
201+
202+
```shell
203+
$ infrakit vars metadata change
204+
Proposing 0 changes, hash=f34a016c93733536ebd5de6e3e7aa87c
205+
{
206+
"cluster": {
207+
"name": "test",
208+
"size": 5,
209+
"user": {
210+
"name": "user"
211+
}
212+
},
213+
"shell": "/bin/bash",
214+
"zones": {
215+
"east": {
216+
"cidr": "10.20.100.100/24"
217+
},
218+
"west": {
219+
"cidr": "10.20.100.200/24"
220+
}
221+
}
222+
}
223+
```
224+
225+
Updating multiple values:
226+
227+
```shell
228+
$ infrakit vars metadata change cluster/name=hello shell=/bin/zsh zones/east/cidr=10.20.100/16
229+
Proposing 3 changes, hash=0d2e7576bafc24c7f07839f77fad6952
230+
{
231+
"cluster": {
232+
"name": "thestllo",
233+
"size": 5,
234+
"user": {
235+
"name": "user"
236+
}
237+
},
238+
"shell": "/bin/bazsh",
239+
"zones": {
240+
"east": {
241+
"cidr": "10.20.100.100/2416"
242+
},
243+
"west": {
244+
"cidr": "10.20.100.200/24"
245+
}
246+
}
247+
}
248+
```
249+
250+
Not shown above, your terminal show show color differences of the change. Using the `-c` option will
251+
commit the change (which has the hash `0d2e7576bafc24c7f07839f77fad6952`):
252+
253+
```shell
254+
$ infrakit vars metadata change cluster/name=hello shell=/bin/zsh zones/east/cidr=10.20.100/16 -c
255+
Committing 3 changes, hash=0d2e7576bafc24c7f07839f77fad6952
256+
{
257+
"cluster": {
258+
"name": "thestllo",
259+
"size": 5,
260+
"user": {
261+
"name": "user"
262+
}
263+
},
264+
"shell": "/bin/bazsh",
265+
"zones": {
266+
"east": {
267+
"cidr": "10.20.100.100/2416"
268+
},
269+
"west": {
270+
"cidr": "10.20.100.200/24"
271+
}
272+
}
273+
}
274+
```
275+
276+
Verify:
277+
278+
```shell
279+
$ infrakit vars metadata cat cluster/name
280+
hello
281+
$ infrakit vars metadata cat zones/east/cidr
282+
10.20.100/16
283+
```
284+
285+
You can also add new values / structs:
286+
287+
```shell
288+
$ infrakit vars metadata change this/is/a/new/struct='{ "message":"i am here"}' -c
289+
Committing 1 changes, hash=1c5bb84ad728337950127a3a4710509d
290+
{
291+
"cluster": {
292+
"name": "hello",
293+
"size": 5,
294+
"user": {
295+
"name": "user"
296+
}
297+
},
298+
"shell": "/bin/zsh",
299+
"this": {
300+
"is": {
301+
"a": {
302+
"new": {
303+
"struct": {
304+
"message": "i am here"
305+
}
306+
}
307+
}
308+
}
309+
},
310+
"zones": {
311+
"east": {
312+
"cidr": "10.20.100/16"
313+
},
314+
"west": {
315+
"cidr": "10.20.100.200/24"
316+
}
317+
}
318+
}
319+
```
320+
321+
Verify:
322+
323+
```shell
324+
$ infrakit vars metadata cat this/is/a/new/struct/message
325+
i am here
326+
```
327+
328+
## TODO - Durability of Changes
329+
330+
The metadata / updatable plugin is one of the key patterns provided by Infrakit. The base implementation
331+
of this plugin does not store state, like all of the plugins (e.g. Instance and Group controllers).
332+
This means all the `change` you apply will be gone when the plugin process exits.
333+
While this is useful for the case of storing some kind of secrets (which is prompted from user and then
334+
set in memory), there are many cases where we want to persist the user's changes.
335+
336+
In keeping with the design philosophy of layering and composition, the `vars` plugin which does not store state,
337+
relies on something else that will help with persistence. The `manager` is the layer which provides that, because
338+
the `manager` already provides leadership detection and persistence for a number of other controllers such as
339+
Ingress and Group by implementing an interceptor for the `Group` and `Controller` interfaces.
340+
341+
In a future PR we will make the `manager` implement the same Updatable interface, which will also persist the entire
342+
struct into a backend of your choosing (e.g. 'swarm', 'etcd', or 'file'). This allows the simple layering of
343+
plugins to give desired effects: some vars are durable (via wrapper/ proxy by manager) while some are in memory only
344+
(maybe a key/ secret that is one-time and shall have no trace like a key to generate other persistable keys).
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{{ var `cluster/user/name` `user` }}
2+
{{ var `zones/east/cidr` `10.20.100.100/24` }}
3+
{{ var `zones/west/cidr` `10.20.100.200/24` }}
4+
5+
{
6+
"cluster" : {
7+
"size" : 5,
8+
"name" : "test"
9+
},
10+
"shell" : {{ env `SHELL` }}
11+
}

examples/event/time/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (t *timer) getEndpoint() interface{} {
3333
func (t *timer) init() *timer {
3434
t.topics = map[string]interface{}{}
3535

36-
for _, topic := range types.PathFromStrings(
36+
for _, topic := range types.PathsFromStrings(
3737
"msec/100",
3838
"msec/500",
3939
"sec/1",

0 commit comments

Comments
 (0)