1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
|
@title = "Configuration Files"
@summary = "Understanding and editing the configuration files."
Files
-------------------------------------------
Here are a list of some of the common files that make up a provider. Except for `Leapfile` and `provider.json`, the files are optional. Unless otherwise specified, all file names are relative to the 'provider directory' root (where the Leapfile is).
<table class="table table-striped">
<tr>
<td><code>Leapfile</code></td>
<td>If present, this file tells <code>leap</code> that the directory is a provider directory. This file is usually empty, but can contain global options.</td>
</tr>
<tr>
<td><code>~/.leaprc</code></td>
<td>Evaluated the same as Leapfile, but not committed to source control.</td>
</tr>
<tr>
<td><code>provider.json</code></td>
<td>Global options related to this provider. See [[provider-configuration]].</td>
</tr>
<tr>
<td><code>provider.ENVIRONMENT.json</code></td>
<td>Global options for the provider that are applied to only a single environment.</td>
</tr>
<tr>
<td><code>nodes/NAME.json</code></td>
<td>The configuration file for node called NAME.</td>
</tr>
<tr>
<td><code>common.json</code></td>
<td>All nodes inherit from this file. In other words, any options that appear in <code>common.json</code> will be added as default values to each node configuration, value that can be locally overridden.</td>
</tr>
<tr>
<td><code>services/SERVICE.json</code></td>
<td>The properties in this configuration file are applied to any node that includes SERVICE in its <code>services</code> property.</td>
</tr>
<tr>
<td><code>services/SERVICE.ENVIRONMENT.json</code></td>
<td>The properties in this configuration file are applied to any node that includes SERVICE in its services and has environment equal to ENVIRONMENT.</td>
</tr>
<tr>
<td><code>tags/TAG.json</code></td>
<td>The properties in this configuration file are applied to any node that has includes TAG in its <code>tags</code> property.</td>
</tr>
<tr>
<td><code>tags/TAG.ENVIRONMENT.json</code></td>
<td>The properties in this configuration file are applied to any node that has includes TAG in its <code>tags</code> property and has <code>environment</code> property equal to ENVIRONMENT.</td>
</tr>
<tr>
<td><code>secrets.json </code></td>
<td>An automatically generated file that contains any randomly generated strings needed in order to deploy. These strings are often secret and should be protected, although any need for a random string or number that is remembered will produce another entry in this file. This file is automatically generated and refreshed each time you run <code>leap compile</code> or <code>leap deploy</code>. If an entry is no longer needed, it will get removed. If you want to change a secret, you can remove this file and have it regenerated, or remove the particular line item and just those items will be created anew.</td>
</tr>
<tr>
<td><code>facts.json</code></td>
<td>If some of your servers are running on AWS or OpenStack, you will need to discover certain properties about how networking is configured on these machines in order for a full deploy to work. In these cases, make sure to run <code>leap facts update</code> to periodically regenerate the facts.json file.</td>
</tr>
<tr>
<td><code>files/*</code></td>
<td>Various static files used by the platform (e.g. keys, certificates, webapp customization, etc). In general, only generated files and files used to customize the provider (such as images) live in the <code>files</code> directory.</td>
</tr>
<tr>
<td><code>users/USER/</code></td>
<td>A directory that stores the public keys of the sysadmin with name USER. This person will have root access to all the servers.</td>
</tr>
</table>
Leapfile
-------------------------------------------
A `Leapfile` defines options for the `leap` command and lives at the root of your provider directory. `Leapfile` is evaluated as ruby, so you can include whatever weird logic you want in this file. In particular, there are several variables you can set that modify the behavior of leap. For example:
@platform_directory_path = '../leap_platform'
@log = '/var/log/leap.log'
Additionally, you can create a `~/.leaprc` file that is loaded after `Leapfile` and is evaluated the same way.
Platform options:
* `@platform_directory_path` (required). This must be set to the path where `leap_platform` lives. The path may be relative.
Vagrant options:
* `@vagrant_provider`. Changes the default vagrant provider ("virtualbox"). For example, `@vagrant_provider = "libvirt"`.
* `@vagrant_network`. Allows you to override the default network used for local nodes. It should include a netmask like `@vagrant_network = '10.0.0.0/24'`.
* `@custom_vagrant_vm_line`. Insert arbitrary text into the auto-generated Vagrantfile. For example, `@custom_vagrant_vm_line = "config.vm.boot_mode = :gui"`.
* `@vagrant_basebox` allows specifying a different basebox as the default one. For example, `@vagrant_basebox = "LEAP/jessie"`.
Logging options:
* `@log`. If set, all command invocation and results are logged to the specified file. This is the same as the switch `--log FILE`, except that the command line switch will override the value in the Leapfile.
JSON format
-------------------------------------------
All configuration files, other than `Leapfile`, are in the JSON format. For example:
{
"key1": "value1",
"key2": "value2"
}
Keys should match `/[a-z0-9_]/` and must be in double quotes.
Unlike traditional JSON, comments are allowed. If the first non-whitespace characters are `//` then the line is treated as a comment.
// this is a comment
{
// this is a comment
"key": "value" // this is an error
}
Options in the configuration files might be nested hashes, arrays, numbers, strings, or boolean. Numbers and boolean values should **not** be quoted. For example:
{
"openvpn": {
"ip_address": "1.1.1.1",
"protocols": ["tcp", "udp"],
"ports": [80, 53],
"options": {
"public_ip": false,
"adblock": true
}
}
}
If the value string is prefixed with an '=' character, the result is evaluated as ruby. For example:
{
"domain": {
"public": "domain.org"
}
"api_domain": "= 'api.' + domain.public"
}
In this case, the property "api_domain" will be set to "api.domain.org". So long as you do not create unresolvable circular dependencies, you can reference other properties in evaluated ruby that are themselves evaluated ruby.
See "Macros" below for information on the special macros available to the evaluated ruby.
TIP: In rare cases, you might want to force the evaluation of a value to happen in a later pass after most of the other properties have been evaluated. To do this, prefix the value string with "=>" instead of "=".
Node inheritance
----------------------------------------
Every node inherits from common.json and also any of the services or tags attached to the node. Additionally, the `leap_platform` contains a directory `provider_base` that defines the default values for tags, services and common.json.
Suppose you have a node configuration for `bitmask/nodes/willamette.json` like so:
{
"services": "webapp",
"tags": ["production", "northwest-us"],
"ip_address": "1.1.1.1"
}
This node will have hostname "willamette" and it will inherit from the following files (in this order):
1. common.json
- load defaults: `provider_base/common.json`
- load provider: `bitmask/common.json`
2. service "webapp"
- load defaults: `provider_base/services/webapp.json`
- load provider: `bitmask/services/webapp.json`
3. tag "production"
- load defaults: `provider_base/tags/production.json`
- load provider: `bitmask/tags/production.json`
4. tag "northwest-us"
- load: `bitmask/tags/northwest-us.json`
5. finally, load node "willamette"
- load: `bitmask/nodes/willamette.json`
The `provider_base` directory is under the `leap_platform` specified in the file `Leapfile`.
To see all the variables a node has inherited, you could run `leap inspect willamette`.
### Inheritance rules
Suppose you have a node configuration `mynode.json`:
{
"tags": "production",
"simple_value": 100,
"replaced_array": ["dolphin", "kangaroo"],
"+add_array": ["red", "black"],
"-subtract_array": ["bitter"],
"converted_to_array": "not_array_element",
"!override": ["insist on this value"],
"hash": {
"key1": 1,
"key2": 2
}
}
And a file `tags/production.json`:
{
"simple_value": 99999,
"replaced_array": ["zebra"],
"add_array": ["green],
"subtract_array": ["bitter", "sweet", "salty"],
"converted_to_array": ["array_element"],
"override": "this value will be overridden",
"hash": {
"key1": "one"
}
}
In this scenario, `mynode.json` will inherit from `production.json`. The output of this inheritance will be:
{
"tags": "production",
"simple_value": 100,
"replaced_array": ["dolphin", "kangaroo"],
"add_array": ["red", "black", "green"],
"subtract_array": ["sweet", "salty"],
"converted_to_array": ["not_array_element", "array_element"],
"override": ["insist on this value"],
"hash": {
"key1": 1,
"key2": 2
}
The rules for inheritance (where 'old' refers to the parent, and 'new' refers to the child):
* Simple values (strings, numbers, boolean):
* Replace the old value with the new value.
* Array values:
* Two arrays: replace the old array with the new array.
* One array and one simple value: add the simple value to the array.
* If property name is prefixed with "+": merge the old and new arrays.
* If property name is prefixed with "-": subtract new array from old array.
* Hash values:
* Hashes are always merged (the result includes the keys of both hashes). If there is a key in common, the new one overrides the old one.
* Mismatch:
* Although you can mix arrays and simple values, you cannot mix arrays with hashes or hashes with simple values. If you attempt to do so, it will fail to compile and give you an error message.
* Override:
* If property name is prefixed with "!": then ensure that new value is always used, regardless of old value. In this case, the override takes precedence over type checking, so you will never get a type mismatch.
NOTE: special property name prefixes, like "+", "-", or "!", are not included in the property name. These prefixes determine the merge strategy, but are stripped out when compiling the resulting JSON file.
Common configuration options
----------------------------------------
You can use the command `leap inspect` to see what options are available for a provider, node, service, or tag configuration. For example:
* `leap inspect common` -- show the options inherited by all nodes.
* `leap inspect --base common` -- show the common.json from `provider_base` without the local `common.json` inheritance applied.
* `leap inspect webapp` -- show all the options available for the service `webapp`.
Here are some of the more important options you should be aware of:
* `ip_address` -- Required for all nodes, no default.
* `ssh.port` -- The SSH port you want the node's OpenSSH server to bind to. This is also the default when trying to connect to a node, but if the node currently has OpenSSH running on a different port then run deploy with `--port` to override the `ssh.port` configuration value.
* `mosh.enabled` -- If set to `true`, then mosh will be installed on the server. The default is `false`.
Macros
----------------------------------------
When using evaluated ruby in a JSON configuration file, there are several special macros that are available. These are evaluated in the context of a node (available as the variable `self`).
The following methods are available to the evaluated ruby:
`variable.variable`
> Any variable defined or inherited by a particular node configuration is available by just referencing it using either hash notation or object field notation (e.g. `['domain']['public']` or `domain.public`). Circular references are not allowed, but otherwise it is OK to nest evaluated values in other evaluated values. If a value has not been defined, the hash notation will return nil but the field notation will raise an exception. Properties of services, tags, and the global provider can all be referenced the same way. For example, `global.services['openvpn'].x509.dh`.
`nodes`
> A hash of all nodes. This list can be filtered.
`nodes_like_me`
> A hash of nodes that have the same deployment tags as the current node (e.g. 'production' or 'local').
`global.services`
> A hash of all services, e.g. `global.services['openvpn']` would return the "openvpn" service.
`global.tags`
> A hash of all tags, e.g. `global.tags['production']` would return the "production" tag.
`global.provider`
> Can be used to access variables defined in `provider.json`, e.g. `global.provider.contacts.default`.
`file(filename)`
> Inserts the full contents of the file. If the file is an erb template, it is rendered. The filename can either be one of the pre-defined file symbols, or it can be a path relative to the "files" directory in your provider instance. E.g, `file :ca_cert` or `files 'ca/ca.crt'`.
`file_path(filename)`
> Ensures that the file will get rsynced to the node as an individual file. The value returned by `file_path` is the full path where this file will ultimately live when deploy to the node. e.g. `file_path :ca_cert` or `file_path 'branding/images/logo.png'`.
`secret(:symbol)`
> Returns the value of a secret in secrets.json (or creates it if necessary). E.g. `secret :couch_admin_password`
`hosts_file`
> Returns a data structure that puppet will use to generate /etc/hosts. Care is taken to use the local IP of other hosts when needed.
`known_hosts_file`
> Returns the lines needed in a SSH `known_hosts` file.
`stunnel_client(node_list, port, options={})`
> Returns a stunnel configuration data structure for the client side. Argument `node_list` is an `ObjectList` of nodes running stunnel servers. Argument `port` is the real port of the ultimate service running on the servers that the client wants to connect to.
`stunnel_server(port)`
> Generates a stunnel server entry. The `port` is the real port targeted service.
Hash tables
-----------------------------------------
The macros `nodes`, `nodes_like_me`, `global.services`, and `global.tags` all return a hash table of configuration objects (either nodes, services, or tags). There are several ways to filter and process these hash tables:
Access an element by name:
nodes['vpn1'] # returns node named 'vpn1'
global.services['openvpn'] # returns service named 'openvpn'
Create a new hash table by applying filters:
nodes[:public_dns => true] # all nodes where public_dns == true
nodes[:services => 'openvpn', 'location.country_code' => 'US'] # openvpn service OR in the US.
nodes[[:services, 'openvpn'], [:services, 'tor']] # services equal to openvpn OR tor
nodes[:services => 'openvpn'][:tags => 'production'] # openvpn AND production
nodes[:name => "!bob"] # all nodes that are NOT named "bob"
Create an array of values by selecting a single field:
nodes.field('location.name')
==> ['seattle', 'istanbul']
Create an array of hashes by selecting multiple fields:
nodes.fields('domain.full', 'ip_address')
==> [
{'domain_full' => 'red.bitmask.net', 'ip_address' => '1.1.1.1'},
{'domain_full' => 'blue.bitmask.net', 'ip_address' => '1.1.1.2'},
]
Create a new hash table of hashes, with only certain fields:
nodes.pick_fields('domain.full', 'ip_address')
==> {
"red" => {'domain_full' => 'red.bitmask.net', 'ip_address' => '1.1.1.1'},
"blue => {'domain_full' => 'blue.bitmask.net', 'ip_address' => '1.1.1.2'},
}
With `pick_fields`, if there is only one field, it will generate a simple hash table:
nodes.pick_fields('ip_address')
==> {
"red" => '1.1.1.1',
"blue => '1.1.1.2',
}
|