|
| 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). |
0 commit comments