cult 0.1.3.pre → 0.1.4.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +53 -47
- data/cult.gemspec +6 -5
- data/exe/cult +19 -4
- data/lib/cult/cli/common.rb +1 -2
- data/lib/cult/cli/console_cmd.rb +2 -2
- data/lib/cult/cli/cri_extensions.rb +66 -13
- data/lib/cult/cli/init_cmd.rb +6 -7
- data/lib/cult/cli/node_cmd.rb +233 -67
- data/lib/cult/cli/provider_cmd.rb +16 -13
- data/lib/cult/cli/role_cmd.rb +25 -26
- data/lib/cult/cli/task_cmd.rb +13 -13
- data/lib/cult/commander.rb +53 -17
- data/lib/cult/commander_sync.rb +29 -0
- data/lib/cult/definition.rb +21 -49
- data/lib/cult/driver.rb +1 -1
- data/lib/cult/drivers/common.rb +12 -11
- data/lib/cult/drivers/digital_ocean_driver.rb +2 -2
- data/lib/cult/drivers/virtual_box_driver.rb +156 -0
- data/lib/cult/drivers/vultr_driver.rb +3 -3
- data/lib/cult/named_array.rb +103 -15
- data/lib/cult/node.rb +139 -12
- data/lib/cult/paramap.rb +209 -0
- data/lib/cult/project.rb +2 -17
- data/lib/cult/provider.rb +3 -1
- data/lib/cult/role.rb +12 -8
- data/lib/cult/task.rb +73 -45
- data/lib/cult/template.rb +3 -4
- data/lib/cult/transaction.rb +11 -5
- data/lib/cult/user_refinements.rb +1 -1
- data/lib/cult/version.rb +1 -1
- data/lib/cult.rb +32 -3
- data/skel/roles/{all → base}/role.json +0 -0
- data/skel/roles/{all/tasks/00000-do-something-cool → base/tasks/000-do-something-cool} +0 -0
- data/skel/roles/{all/tasks/sync → base/tasks/sync-host-map} +5 -5
- data/skel/roles/base/tasks/sync-leader-of +11 -0
- data/skel/roles/bootstrap/files/cult-motd +15 -3
- data/skel/roles/bootstrap/tasks/{00000-set-hostname → 000-set-hostname} +1 -1
- data/skel/roles/bootstrap/tasks/{00001-add-cult-user → 001-add-cult-user} +0 -6
- data/skel/roles/bootstrap/tasks/002-disable-root-user +7 -0
- data/skel/roles/bootstrap/tasks/{00002-install-cult-motd → 002-install-cult-motd} +1 -1
- metadata +29 -11
- data/lib/cult/cli/fleet_cmd.rb +0 -37
data/lib/cult/cli/node_cmd.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require '
|
1
|
+
require 'securerandom'
|
2
2
|
require 'fileutils'
|
3
|
+
require 'json'
|
3
4
|
|
4
5
|
module Cult
|
5
6
|
module CLI
|
@@ -16,65 +17,105 @@ module Cult
|
|
16
17
|
conceptually description of a server.
|
17
18
|
EOD
|
18
19
|
|
19
|
-
run(arguments:
|
20
|
+
run(arguments: none) do |opts, args, cmd|
|
20
21
|
puts cmd.help
|
22
|
+
exit
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
node_ssh = Cri::Command.define do
|
25
27
|
name 'ssh'
|
26
|
-
usage 'ssh NODE'
|
27
|
-
summary 'Starts an
|
28
|
+
usage 'ssh /NODE+/ [command...]'
|
29
|
+
summary 'Starts an SSH shell to NODE'
|
30
|
+
|
31
|
+
flag :i, :interactive, "Force interactive mode"
|
32
|
+
flag :I, :'non-interactive', "Force non-interactive mode"
|
33
|
+
|
28
34
|
description <<~EOD.format_description
|
35
|
+
With no additional arguments, initiates an interactive SSH connection
|
36
|
+
to a node, authenticated with the node's public key.
|
37
|
+
|
38
|
+
Additional arguments are passed to the 'ssh' command to allow for
|
39
|
+
scripting or running one-off commands on the node.
|
40
|
+
|
41
|
+
By default, cult assumes an interactive SSH session when no extra
|
42
|
+
SSH arguments are passed, and a non-interactive session otherwise.
|
43
|
+
You can force this behavior one way or the other with --interactive
|
44
|
+
or --non-interactive.
|
45
|
+
|
46
|
+
Cult will run SSH commands over all matching nodes in parallel if it
|
47
|
+
considers your command non-interactive.
|
29
48
|
EOD
|
30
49
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
50
|
+
esc = ->(s) { Shellwords.escape(s) }
|
51
|
+
|
52
|
+
run(arguments: 1 .. unlimited) do |opts, args, cmd|
|
53
|
+
if opts[:interactive] && opts[:'non-interactive']
|
54
|
+
fail CLIError, "can't specify --interactive and --non-interactive"
|
55
|
+
end
|
56
|
+
|
57
|
+
interactive = opts[:interactive] ||
|
58
|
+
(opts[:'non-interactive'] && false)
|
59
|
+
|
60
|
+
nodes = CLI.fetch_items(args[0], from: Node)
|
61
|
+
|
62
|
+
# With args, we'll assume it's a non-interactive session and run them
|
63
|
+
# in parallel, otherwise, we'll assume it's interactive and force
|
64
|
+
# them to run one at a time.
|
65
|
+
ssh_extra = args[1 .. -1]
|
66
|
+
interactive ||= ssh_extra.empty?
|
67
|
+
concurrent = interactive || nodes.size == 1 ? 1 : nil
|
68
|
+
|
69
|
+
Cult.paramap(nodes, concurrent: concurrent) do |node|
|
70
|
+
ssh_args = 'ssh', '-i', esc.(node.ssh_private_key_file),
|
71
|
+
'-p', esc.(node.ssh_port.to_s),
|
72
|
+
'-o', "UserKnownHostsFile=#{esc.(node.ssh_known_hosts_file)}",
|
73
|
+
esc.("#{node.user}@#{node.host}")
|
74
|
+
ssh_args += ssh_extra
|
75
|
+
# We used to use exec here, but with paramap, the forked process
|
76
|
+
# has to live to long enough to report it's return value.
|
77
|
+
system(*ssh_args)
|
78
|
+
exit if interactive
|
79
|
+
end
|
35
80
|
end
|
36
81
|
end
|
37
82
|
node.add_command(node_ssh)
|
38
83
|
|
39
|
-
|
40
|
-
name '
|
41
|
-
|
42
|
-
usage 'create [options] NAME...'
|
84
|
+
node_new = Cri::Command.define do
|
85
|
+
name 'new'
|
86
|
+
usage 'new -r /ROLE+/ [options] NAME0 NAME1 ...'
|
43
87
|
summary 'Create a new node'
|
44
88
|
description <<~EOD.format_description
|
45
89
|
This command creates a new node specification and then creates it with
|
46
90
|
your provider.
|
47
91
|
|
48
92
|
The newly created node will have all the roles listed in --role. If
|
49
|
-
none are specified, it'll have the role "
|
50
|
-
provided, it will be named
|
93
|
+
none are specified, it'll have the role "base". If no name is
|
94
|
+
provided, it will be named after its role(s).
|
51
95
|
|
52
96
|
If multiple names are provided, a new node is created for each name
|
53
|
-
given.
|
97
|
+
given. The --count option is incompatible with multiple names given
|
98
|
+
on the command line.
|
54
99
|
|
55
100
|
The --count option lets you create an arbitrary amount of new nodes.
|
56
|
-
The nodes will be identical, except they'll be named with
|
57
|
-
|
101
|
+
The nodes will be identical, except they'll be named with arbitrary
|
102
|
+
random suffixes, like:
|
58
103
|
|
59
|
-
> web-
|
104
|
+
> web-fjfowhs7, web-48pqee6v
|
60
105
|
|
61
|
-
And so forth.
|
62
|
-
given on the command line. If --count is specified with one name, the
|
63
|
-
name will become the prefix for all nodes created. If --count is
|
64
|
-
specified with no names, the prefix will be generated from the role
|
65
|
-
names used, as discussed above.
|
106
|
+
And so forth.
|
66
107
|
EOD
|
67
108
|
|
68
|
-
required :r, :role, 'Specify possibly multiple
|
109
|
+
required :r, :role, 'Specify possibly multiple /ROLE+/',
|
69
110
|
multiple: true
|
70
111
|
required :n, :count, 'Generates <value> number of nodes'
|
71
112
|
|
72
|
-
required :p, :provider, '
|
113
|
+
required :p, :provider, 'Use /PROVIDER/ to create the node'
|
73
114
|
required :Z, :zone, 'Provider zone'
|
74
115
|
required :I, :image, 'Provider image'
|
75
116
|
required :S, :size, 'Provider instance size'
|
76
117
|
|
77
|
-
run(arguments:
|
118
|
+
run(arguments: unlimited) do |opts, args, cmd|
|
78
119
|
random_suffix = ->(basename) do
|
79
120
|
begin
|
80
121
|
suffix = CLI.unique_id
|
@@ -85,42 +126,32 @@ module Cult
|
|
85
126
|
end
|
86
127
|
|
87
128
|
generate_sequenced_names = ->(name, n) do
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
unless opts[:count].nil? || opts[:count].match(/^\d+$/)
|
94
|
-
fail CLIError, "--count must be an integer"
|
129
|
+
(0...n).map do
|
130
|
+
random_suffix.(name)
|
131
|
+
end
|
95
132
|
end
|
96
133
|
|
97
134
|
names = args.dup
|
98
135
|
|
99
|
-
|
100
|
-
|
101
|
-
if roles.empty?
|
102
|
-
roles = CLI.fetch_items('all', from: Role)
|
103
|
-
if names.empty?
|
104
|
-
begin
|
105
|
-
names.push CLI.fetch_item('node', from: Node, exist: false)
|
106
|
-
rescue
|
107
|
-
names.push random_suffix.('node')
|
108
|
-
end
|
109
|
-
end
|
136
|
+
unless opts[:count].nil? || opts[:count].match(/^\d+$/)
|
137
|
+
fail CLIError, "--count must be an integer"
|
110
138
|
end
|
111
139
|
|
112
140
|
if names.size > 1 && opts[:count]
|
113
141
|
fail CLIError, "cannot specify both --count and more than one name"
|
114
142
|
end
|
115
143
|
|
116
|
-
|
117
|
-
names.push roles.map(&:name).sort.join('-')
|
118
|
-
end
|
144
|
+
roles = CLI.fetch_items(opts[:role] || 'base', from: Role)
|
119
145
|
|
120
|
-
if
|
121
|
-
names
|
146
|
+
if names.empty?
|
147
|
+
names.push roles.map(&:name).join("-")
|
148
|
+
opts[:count] ||= 1
|
122
149
|
end
|
123
150
|
|
151
|
+
names = opts[:count] ? generate_sequenced_names.(names[0],
|
152
|
+
opts[:count].to_i)
|
153
|
+
: names
|
154
|
+
|
124
155
|
# Makes sure they're all new.
|
125
156
|
names = names.map do |name|
|
126
157
|
CLI.fetch_item(name, from: Node, exist: false)
|
@@ -140,36 +171,40 @@ module Cult
|
|
140
171
|
[m, value]
|
141
172
|
end.to_h
|
142
173
|
|
143
|
-
|
174
|
+
Cult.paramap(names) do |name|
|
144
175
|
data = {
|
145
176
|
name: name,
|
146
177
|
roles: roles.map(&:name)
|
147
178
|
}
|
148
179
|
|
149
180
|
Node.from_data!(Cult.project, data).tap do |node|
|
181
|
+
puts "Provisioning #{node.name}..."
|
150
182
|
prov_data = provider.provision!(name: node.name,
|
151
183
|
image: node_spec[:image],
|
152
184
|
size: node_spec[:size],
|
153
185
|
zone: node_spec[:zone],
|
154
186
|
ssh_public_key: node.ssh_public_key_file)
|
155
187
|
prov_data['provider'] = provider.name
|
156
|
-
File.write(
|
157
|
-
Cult.project.dump_object(prov_data))
|
188
|
+
File.write(node.state_path, JSON.pretty_generate(prov_data))
|
158
189
|
|
159
190
|
c = Commander.new(project: Cult.project, node: node)
|
191
|
+
puts "Bootstrapping #{node.name}..."
|
160
192
|
c.bootstrap!
|
193
|
+
|
194
|
+
puts "Installing roles for #{node.name}..."
|
161
195
|
c.install!(node)
|
196
|
+
|
197
|
+
puts "Node installed: #{node.name}"
|
162
198
|
end
|
163
199
|
end
|
164
200
|
|
165
201
|
end
|
166
202
|
end
|
167
|
-
node.add_command
|
203
|
+
node.add_command(node_new)
|
168
204
|
|
169
|
-
|
170
|
-
name '
|
171
|
-
|
172
|
-
usage 'destroy NODE'
|
205
|
+
node_rm = Cri::Command.define do
|
206
|
+
name 'rm'
|
207
|
+
usage 'rm /NODE+/ ...'
|
173
208
|
summary 'Destroy nodes'
|
174
209
|
description <<~EOD.format_description
|
175
210
|
Destroys all nodes named NODE, or match the pattern described by
|
@@ -181,9 +216,10 @@ module Cult
|
|
181
216
|
be prompted before each destroy.
|
182
217
|
EOD
|
183
218
|
|
184
|
-
run(arguments: 1
|
219
|
+
run(arguments: 1 .. unlimited) do |opts, args, cmd|
|
185
220
|
nodes = CLI.fetch_items(args, from: Node)
|
186
|
-
|
221
|
+
concurrent = CLI.yes? ? :max : 1
|
222
|
+
Cult.paramap(nodes, concurrent: concurrent) do |node|
|
187
223
|
if CLI.yes_no?("Destroy node `#{node}`?")
|
188
224
|
puts "destroying #{node}"
|
189
225
|
begin
|
@@ -197,15 +233,16 @@ module Cult
|
|
197
233
|
fail unless node.path.match(/#{Regexp.escape(node.name)}/)
|
198
234
|
FileUtils.rm_rf(node.path)
|
199
235
|
end
|
236
|
+
nil
|
200
237
|
end
|
201
238
|
end
|
202
239
|
end
|
203
|
-
node.add_command(
|
240
|
+
node.add_command(node_rm)
|
204
241
|
|
205
|
-
|
206
|
-
name '
|
207
|
-
aliases 'ls'
|
242
|
+
node_ls = Cri::Command.define do
|
243
|
+
name 'ls'
|
208
244
|
summary 'List nodes'
|
245
|
+
usage 'ls /NODE+/ ...'
|
209
246
|
description <<~EOD.format_description
|
210
247
|
This command lists the nodes in the project.
|
211
248
|
EOD
|
@@ -213,9 +250,9 @@ module Cult
|
|
213
250
|
required :r, :role, 'List only nodes which include <value>',
|
214
251
|
multiple: true
|
215
252
|
|
216
|
-
run(arguments:
|
253
|
+
run(arguments: unlimited) do |opts, args, cmd|
|
217
254
|
nodes = args.empty? ? Cult.project.nodes
|
218
|
-
: CLI.fetch_items(
|
255
|
+
: CLI.fetch_items(args, from: Node)
|
219
256
|
|
220
257
|
if opts[:role]
|
221
258
|
roles = CLI.fetch_items(opts[:role], from: Role)
|
@@ -224,12 +261,141 @@ module Cult
|
|
224
261
|
end
|
225
262
|
end
|
226
263
|
|
264
|
+
Cult.paramap(nodes) do |node|
|
265
|
+
role_string = node.build_order.map do |role|
|
266
|
+
if node.zone_leader?(role)
|
267
|
+
Rainbow('*' + role.name).cyan
|
268
|
+
else
|
269
|
+
'=' + role.name
|
270
|
+
end
|
271
|
+
end.join(', ')
|
272
|
+
|
273
|
+
puts "#{node.name}\t#{node.provider&.name}\t" +
|
274
|
+
"#{node.zone}\t#{node.addr(:public)}\t#{node.addr(:private)}\t" +
|
275
|
+
"#{role_string}"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
node.add_command(node_ls)
|
280
|
+
|
281
|
+
|
282
|
+
node_sync = Cri::Command.define do
|
283
|
+
name 'sync'
|
284
|
+
usage 'sync /NODE+/ ...'
|
285
|
+
summary 'Synchronize host information across fleet'
|
286
|
+
description <<~EOD.format_description
|
287
|
+
Computes, pre-processes, and executes "sync" tasks on every NODE,
|
288
|
+
or all nodes if none are specified.
|
289
|
+
|
290
|
+
Sync tasks are tasks that begin with 'sync-'. They are meant to
|
291
|
+
process dynamic information about the fleet well after a node has
|
292
|
+
been created. Typically, you'll run `cult node sync` to let each
|
293
|
+
instance know about its new neighborhood after you add or remove
|
294
|
+
new nodes.
|
295
|
+
|
296
|
+
Sync tasks can optionally specify a "pass", with "sync-P0-..." or
|
297
|
+
"sync-P1-...". When `cult node sync` executes, it ensures that:
|
298
|
+
|
299
|
+
1. On a given node, all tasks in the current pass are executed
|
300
|
+
sequentially, in role and asciibetical order.
|
301
|
+
|
302
|
+
2. Across the fleet, nodes which have tasks to run in a given pass
|
303
|
+
are run concurently with each other.
|
304
|
+
|
305
|
+
3. The entire fleet (or NODE selection) synchonizes between passes.
|
306
|
+
"Pass 0" has run on EVERY node (across any role boundaries)
|
307
|
+
before "Pass 1" is started on ANY node.
|
308
|
+
|
309
|
+
Sync tasks without a specified pass are implicitly in "Pass 0".
|
310
|
+
|
311
|
+
The sync can be restricted to a specified set of passes with the
|
312
|
+
--pass option. Note that this skips dependent passes.
|
313
|
+
|
314
|
+
The sync can be restricted to a specified set of CONCRETE role tasks
|
315
|
+
with the --role option. No dependencies are considered: Cult
|
316
|
+
calculates the tasks it would've ran, then removes all tasks not
|
317
|
+
belonging to a role given to --roles
|
318
|
+
EOD
|
319
|
+
|
320
|
+
required :R, :role, "Skip sync tasks not in /ROLE/. Can be specified " +
|
321
|
+
"more than once.",
|
322
|
+
multiple: true
|
323
|
+
|
324
|
+
required :P, :pass, "Only execute PASS. Can be specified more than " +
|
325
|
+
"once.",
|
326
|
+
multiple: true
|
327
|
+
|
328
|
+
run(arguments: unlimited) do |opts, args, cmd|
|
329
|
+
nodes = args.empty? ? Cult.project.nodes
|
330
|
+
: CLI.fetch_items(args, from: Node)
|
331
|
+
roles = opts[:role].nil? ? Cult.project.roles
|
332
|
+
: CLI.fetch_items(opts[:role], from: Role)
|
333
|
+
c = CommanderSync.new(project: Cult.project, nodes: nodes)
|
334
|
+
passes = opts[:pass] ? opts[:pass].map(&:to_i) : nil
|
335
|
+
c.sync!(roles: roles, passes: passes)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
node.add_command(node_sync)
|
339
|
+
|
340
|
+
node_ping = Cri::Command.define do
|
341
|
+
name 'ping'
|
342
|
+
summary 'Check the responsiveness of each node'
|
343
|
+
usage 'ping /NODE+/'
|
344
|
+
|
345
|
+
flag :d, :destroy, 'Destroy nodes that are not responding.'
|
346
|
+
|
347
|
+
description <<~EOD.format_description
|
348
|
+
Connects to each node and reports health information.
|
349
|
+
EOD
|
350
|
+
|
351
|
+
run(arguments: unlimited) do |opts, args, cmd|
|
352
|
+
nodes = args.empty? ? Cult.project.nodes
|
353
|
+
: CLI.fetch_items(args, from: Node)
|
354
|
+
Cult.paramap(nodes) do |node|
|
355
|
+
c = Commander.new(project: Cult.project, node: node)
|
356
|
+
if (r = c.ping)
|
357
|
+
puts Rainbow(node.name).green + ": #{r}"
|
358
|
+
nil
|
359
|
+
else
|
360
|
+
puts Rainbow(node.name).red + ": Unreachable"
|
361
|
+
node
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
node.add_command(node_ping)
|
367
|
+
|
368
|
+
node_addr = Cri::Command.define do
|
369
|
+
name 'addr'
|
370
|
+
aliases 'ip'
|
371
|
+
summary 'print IP address of node'
|
372
|
+
usage 'addr [/NODE+/ ...]'
|
373
|
+
flag :p, :private, 'Print private address'
|
374
|
+
flag :'6', :ipv6, 'Print ipv6 address'
|
375
|
+
flag :'4', :ipv4, 'Print ipv4 address'
|
376
|
+
description <<~EOD.format_description
|
377
|
+
EOD
|
378
|
+
|
379
|
+
run(arguments: unlimited) do |opts, args, cmd|
|
380
|
+
prot = Cult.project.default_ip_protocol
|
381
|
+
if opts[:ipv4] && opts[:ipv6]
|
382
|
+
fail CLIError, "can't specify --ipv4 and --ipv6"
|
383
|
+
end
|
384
|
+
|
385
|
+
prot = :ipv6 if opts[:ipv6]
|
386
|
+
prot = :ipv4 if opts[:ipv4]
|
387
|
+
|
388
|
+
priv = opts[:private] ? :private: :public
|
389
|
+
|
390
|
+
nodes = args.empty? ? Cult.project.nodes
|
391
|
+
: CLI.fetch_items(args, from: Node)
|
227
392
|
nodes.each do |node|
|
228
|
-
puts
|
393
|
+
puts node.addr(priv, prot)
|
229
394
|
end
|
230
395
|
end
|
231
396
|
end
|
232
|
-
node.add_command
|
397
|
+
node.add_command(node_addr)
|
398
|
+
|
233
399
|
|
234
400
|
return node
|
235
401
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'cult/drivers/load'
|
2
|
+
require 'json'
|
2
3
|
|
3
4
|
module Cult
|
4
5
|
module CLI
|
@@ -9,7 +10,7 @@ module Cult
|
|
9
10
|
optional_project
|
10
11
|
name 'provider'
|
11
12
|
aliases 'providers'
|
12
|
-
summary 'Provider
|
13
|
+
summary 'Provider commands'
|
13
14
|
description <<~EOD.format_description
|
14
15
|
A provider is a VPS service. Cult ships with drivers for quite a few
|
15
16
|
services, (which can be listed with `cult provider drivers`).
|
@@ -30,15 +31,16 @@ module Cult
|
|
30
31
|
a driver of the same name.
|
31
32
|
EOD
|
32
33
|
|
33
|
-
run(arguments:
|
34
|
+
run(arguments: none) do |opts, args, cmd|
|
34
35
|
puts cmd.help
|
36
|
+
exit
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
40
|
|
39
|
-
|
40
|
-
name '
|
41
|
-
|
41
|
+
provider_ls = Cri::Command.define do
|
42
|
+
name 'ls'
|
43
|
+
usage 'ls [/PROVIDER+/ ...]'
|
42
44
|
summary 'List Providers'
|
43
45
|
description <<~EOD.format_description
|
44
46
|
Lists Providers for this project. If --driver is specified, it only
|
@@ -46,7 +48,7 @@ module Cult
|
|
46
48
|
EOD
|
47
49
|
required :d, :driver, "Restrict list to providers using DRIVER"
|
48
50
|
|
49
|
-
run(arguments: 0..1) do |opts, args, cmd|
|
51
|
+
run(arguments: 0 .. 1) do |opts, args, cmd|
|
50
52
|
providers = Cult.project.providers
|
51
53
|
|
52
54
|
# Filtering
|
@@ -65,7 +67,7 @@ module Cult
|
|
65
67
|
|
66
68
|
end
|
67
69
|
end
|
68
|
-
provider.add_command(
|
70
|
+
provider.add_command(provider_ls)
|
69
71
|
|
70
72
|
|
71
73
|
provider_avail = Cri::Command.define do
|
@@ -77,7 +79,7 @@ module Cult
|
|
77
79
|
gem dependencies.
|
78
80
|
EOD
|
79
81
|
|
80
|
-
run(arguments:
|
82
|
+
run(arguments: none) do |opts, args, cmd|
|
81
83
|
Cult::Drivers.all.each do |p|
|
82
84
|
printf "%-20s %-s\n", p.driver_name, p.required_gems
|
83
85
|
end
|
@@ -86,10 +88,9 @@ module Cult
|
|
86
88
|
provider.add_command(provider_avail)
|
87
89
|
|
88
90
|
|
89
|
-
|
90
|
-
name '
|
91
|
-
|
92
|
-
usage 'create NAME'
|
91
|
+
provider_new = Cri::Command.define do
|
92
|
+
name 'new'
|
93
|
+
usage 'new NAME'
|
93
94
|
summary 'creates a new provider for your project'
|
94
95
|
required :d, :driver, 'Specify driver, if different than NAME'
|
95
96
|
description <<~EOD.format_description
|
@@ -111,10 +112,12 @@ module Cult
|
|
111
112
|
driver = CLI.fetch_item(opts[:driver] || name, from: Driver)
|
112
113
|
name = CLI.fetch_item(name, from: Provider, exist: false)
|
113
114
|
|
115
|
+
puts JSON.pretty_generate(driver.setup!)
|
116
|
+
fail "FIXME"
|
114
117
|
puts [driver, name].inspect
|
115
118
|
end
|
116
119
|
end
|
117
|
-
provider.add_command(
|
120
|
+
provider.add_command(provider_new)
|
118
121
|
|
119
122
|
|
120
123
|
provider
|
data/lib/cult/cli/role_cmd.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'json'
|
2
3
|
|
3
4
|
module Cult
|
4
5
|
module CLI
|
@@ -36,28 +37,28 @@ module Cult
|
|
36
37
|
`rat-site` and `tempo-site` roles and be happily serving both sites.
|
37
38
|
|
38
39
|
By default, `cult init` generates two root roles that don't depend on
|
39
|
-
anything else: `
|
40
|
+
anything else: `base` and `bootstrap`. The `bootstrap` role exists
|
40
41
|
to get a node from a clean OS install to a configuration to be
|
41
|
-
managed by the settings in `
|
42
|
+
managed by the settings in `base'. Theoretically, if you're happy
|
42
43
|
doing all deploys as the root user, you don't need a `bootstrap` role
|
43
|
-
at all: Delete it and set the `user` key in `
|
44
|
+
at all: Delete it and set the `user` key in `base/role.json` to
|
44
45
|
"root".
|
45
46
|
|
46
|
-
The tasks in the `
|
47
|
-
However, the only thing special about the `
|
47
|
+
The tasks in the `base` role are considered shared amongst all roles.
|
48
|
+
However, the only thing special about the `base` role is that Cult
|
48
49
|
assumes roles and nodes without an explicit `includes` setting belong
|
49
|
-
to
|
50
|
+
to `base`.
|
50
51
|
EOD
|
51
52
|
|
52
|
-
run(arguments:
|
53
|
+
run(arguments: none) do |opts, args, cmd|
|
53
54
|
puts cmd.help
|
55
|
+
exit
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
57
59
|
|
58
|
-
|
59
|
-
name '
|
60
|
-
aliases 'new'
|
60
|
+
role_new = Cri::Command.define do
|
61
|
+
name 'new'
|
61
62
|
summary 'creates a new role'
|
62
63
|
usage 'create [options] NAME'
|
63
64
|
description <<~EOD.format_description
|
@@ -65,7 +66,7 @@ module Cult
|
|
65
66
|
$CULT_PROJECT/roles/$NAME
|
66
67
|
EOD
|
67
68
|
|
68
|
-
required :r, :roles, 'this role depends on another
|
69
|
+
required :r, :roles, 'this role depends on another /ROLE+/ (multiple)',
|
69
70
|
multiple: true
|
70
71
|
|
71
72
|
run(arguments: 1) do |opts, args, cmd|
|
@@ -79,8 +80,8 @@ module Cult
|
|
79
80
|
from: Role).map(&:name)
|
80
81
|
end
|
81
82
|
FileUtils.mkdir_p(role.path)
|
82
|
-
File.write(
|
83
|
-
|
83
|
+
File.write(role.definition_file,
|
84
|
+
JSON.pretty_generate(data))
|
84
85
|
|
85
86
|
FileUtils.mkdir_p(File.join(role.path, "files"))
|
86
87
|
File.write(File.join(role.path, "files", ".keep"), '')
|
@@ -89,19 +90,18 @@ module Cult
|
|
89
90
|
File.write(File.join(role.path, "tasks", ".keep"), '')
|
90
91
|
end
|
91
92
|
end
|
92
|
-
role.add_command
|
93
|
+
role.add_command(role_new)
|
93
94
|
|
94
95
|
|
95
|
-
|
96
|
-
name '
|
97
|
-
|
98
|
-
usage 'destroy ROLES...'
|
96
|
+
role_rm = Cri::Command.define do
|
97
|
+
name 'rm'
|
98
|
+
usage 'rm /ROLE+/ ...'
|
99
99
|
summary 'Destroy role ROLE'
|
100
100
|
description <<~EOD.format_description
|
101
101
|
Destroys all roles specified.
|
102
102
|
EOD
|
103
103
|
|
104
|
-
run(arguments: 1
|
104
|
+
run(arguments: 1 .. unlimited) do |opts, args, cmd|
|
105
105
|
roles = args.map do |role_name|
|
106
106
|
CLI.fetch_items(role_name, from: Role)
|
107
107
|
end.flatten
|
@@ -114,20 +114,19 @@ module Cult
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
end
|
117
|
-
role.add_command(
|
117
|
+
role.add_command(role_rm)
|
118
118
|
|
119
119
|
|
120
|
-
|
121
|
-
name '
|
122
|
-
|
123
|
-
usage 'list [ROLES...]'
|
120
|
+
role_ls = Cri::Command.define do
|
121
|
+
name 'ls'
|
122
|
+
usage 'ls [/ROLE+/ ...]'
|
124
123
|
summary 'List existing roles'
|
125
124
|
description <<~EOD.format_description
|
126
125
|
Lists roles in this project. By default, lists all roles. If one or
|
127
126
|
more ROLES are specified, only lists those
|
128
127
|
EOD
|
129
128
|
|
130
|
-
run(arguments:
|
129
|
+
run(arguments: unlimited) do |opts, args, cmd|
|
131
130
|
roles = Cult.project.roles
|
132
131
|
unless args.empty?
|
133
132
|
roles = CLI.fetch_items(*args, from: Role)
|
@@ -140,7 +139,7 @@ module Cult
|
|
140
139
|
|
141
140
|
end
|
142
141
|
end
|
143
|
-
role.add_command(
|
142
|
+
role.add_command(role_ls)
|
144
143
|
|
145
144
|
|
146
145
|
role
|