cult 0.1.1.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +240 -0
- data/Rakefile +6 -0
- data/cult +1 -0
- data/cult.gemspec +38 -0
- data/doc/welcome.txt +1 -0
- data/exe/cult +86 -0
- data/lib/cult/artifact.rb +45 -0
- data/lib/cult/cli/common.rb +265 -0
- data/lib/cult/cli/console_cmd.rb +124 -0
- data/lib/cult/cli/cri_extensions.rb +84 -0
- data/lib/cult/cli/init_cmd.rb +116 -0
- data/lib/cult/cli/load.rb +26 -0
- data/lib/cult/cli/node_cmd.rb +205 -0
- data/lib/cult/cli/provider_cmd.rb +123 -0
- data/lib/cult/cli/role_cmd.rb +149 -0
- data/lib/cult/cli/task_cmd.rb +140 -0
- data/lib/cult/commander.rb +103 -0
- data/lib/cult/config.rb +22 -0
- data/lib/cult/definition.rb +112 -0
- data/lib/cult/driver.rb +88 -0
- data/lib/cult/drivers/common.rb +192 -0
- data/lib/cult/drivers/digital_ocean_driver.rb +179 -0
- data/lib/cult/drivers/linode_driver.rb +282 -0
- data/lib/cult/drivers/load.rb +26 -0
- data/lib/cult/drivers/script_driver.rb +27 -0
- data/lib/cult/drivers/vultr_driver.rb +217 -0
- data/lib/cult/named_array.rb +129 -0
- data/lib/cult/node.rb +62 -0
- data/lib/cult/project.rb +169 -0
- data/lib/cult/provider.rb +134 -0
- data/lib/cult/role.rb +213 -0
- data/lib/cult/skel.rb +85 -0
- data/lib/cult/task.rb +64 -0
- data/lib/cult/template.rb +92 -0
- data/lib/cult/transferable.rb +61 -0
- data/lib/cult/version.rb +3 -0
- data/lib/cult.rb +4 -0
- data/skel/.cultconsolerc +4 -0
- data/skel/.cultrc.erb +29 -0
- data/skel/README.md.erb +22 -0
- data/skel/keys/.keep +0 -0
- data/skel/nodes/.keep +0 -0
- data/skel/providers/.keep +0 -0
- data/skel/roles/all/role.json +4 -0
- data/skel/roles/all/tasks/00000-do-something-cool +27 -0
- data/skel/roles/bootstrap/files/cult-motd +45 -0
- data/skel/roles/bootstrap/role.json +4 -0
- data/skel/roles/bootstrap/tasks/00000-set-hostname +22 -0
- data/skel/roles/bootstrap/tasks/00001-add-cult-user +21 -0
- data/skel/roles/bootstrap/tasks/00002-install-cult-motd +9 -0
- metadata +183 -0
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'cult/skel'
|
2
|
+
require 'cult/commander'
|
3
|
+
|
4
|
+
require 'SecureRandom'
|
5
|
+
|
6
|
+
module Cult
|
7
|
+
module CLI
|
8
|
+
|
9
|
+
module_function
|
10
|
+
def node_cmd
|
11
|
+
node = Cri::Command.define do
|
12
|
+
optional_project
|
13
|
+
name 'node'
|
14
|
+
aliases 'nodes'
|
15
|
+
summary 'Manage nodes'
|
16
|
+
description <<~EOD.format_description
|
17
|
+
The node commands manipulate your local index of nodes. A node is
|
18
|
+
conceptually description of a server.
|
19
|
+
EOD
|
20
|
+
|
21
|
+
run(arguments: 0) do |opts, args, cmd|
|
22
|
+
puts cmd.help
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
node_ssh = Cri::Command.define do
|
27
|
+
name 'ssh'
|
28
|
+
usage 'ssh NODE'
|
29
|
+
summary 'Starts an interactive SSH shell to NODE'
|
30
|
+
description <<~EOD.format_description
|
31
|
+
EOD
|
32
|
+
|
33
|
+
run(arguments: 1) do |opts, args, cmd|
|
34
|
+
node = CLI.fetch_item(args[0], from: Node)
|
35
|
+
exec "ssh", "#{node.user}@#{node.host}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
node.add_command(node_ssh)
|
39
|
+
|
40
|
+
node_create = Cri::Command.define do
|
41
|
+
name 'create'
|
42
|
+
aliases 'new'
|
43
|
+
usage 'create [options] NAME...'
|
44
|
+
summary 'Create a new node'
|
45
|
+
description <<~EOD.format_description
|
46
|
+
This command creates a new node specification. With --bootstrap,
|
47
|
+
it'll also provision it, so it'll actually exist out there.
|
48
|
+
|
49
|
+
The newly created node will have all the roles listed in --role. If
|
50
|
+
none are specified, it'll have the role "all". If no name is
|
51
|
+
provided, it will be named for its role(s).
|
52
|
+
|
53
|
+
If multiple names are provided, a new node is created for each name
|
54
|
+
given.
|
55
|
+
|
56
|
+
The --count option lets you create an arbitrary amount of new nodes.
|
57
|
+
The nodes will be identical, except they'll be named with increasing
|
58
|
+
sequential numbers, like:
|
59
|
+
|
60
|
+
> web-01, web-02
|
61
|
+
|
62
|
+
And so forth. The --count option is incompatible with multiple names
|
63
|
+
given on the command line. If --count is specified with one name, the
|
64
|
+
name will become the prefix for all nodes created. If --count is
|
65
|
+
specified with no names, the prefix will be generated from the role
|
66
|
+
names used, as discussed above.
|
67
|
+
EOD
|
68
|
+
|
69
|
+
required :r, :role, 'Specify possibly multiple roles',
|
70
|
+
multiple: true
|
71
|
+
required :n, :count, 'Generates <value> number of nodes'
|
72
|
+
|
73
|
+
required :p, :provider, 'Provider'
|
74
|
+
required :Z, :zone, 'Provider zone'
|
75
|
+
required :I, :image, 'Provider image'
|
76
|
+
required :S, :size, 'Provider instance size'
|
77
|
+
|
78
|
+
flag :b, :bootstrap, 'Bring up node'
|
79
|
+
|
80
|
+
run(arguments: 0..-1) do |opts, args, cmd|
|
81
|
+
random_suffix = ->(basename) do
|
82
|
+
begin
|
83
|
+
suffix = CLI.unique_id
|
84
|
+
CLI.fetch_item("#{basename}-#{suffix}", from: Node, exist: false)
|
85
|
+
rescue CLIError
|
86
|
+
retry
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
generate_sequenced_names = ->(name, n) do
|
91
|
+
result = []
|
92
|
+
result.push(random_suffix.(name)) until result.size == n
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
96
|
+
unless opts[:count].nil? || opts[:count].match(/^\d+$/)
|
97
|
+
fail CLIError, "--count must be an integer"
|
98
|
+
end
|
99
|
+
|
100
|
+
names = args.dup
|
101
|
+
|
102
|
+
roles = opts[:role] ? CLI.fetch_items(opts[:role], from: Role) : []
|
103
|
+
|
104
|
+
if roles.empty?
|
105
|
+
roles = CLI.fetch_items('all', from: Role)
|
106
|
+
if names.empty?
|
107
|
+
begin
|
108
|
+
names.push CLI.fetch_item('node', from: Node, exist: false)
|
109
|
+
rescue
|
110
|
+
names.push random_suffix.('node')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if names.size > 1 && opts[:count]
|
116
|
+
fail CLIError, "cannot specify both --count and more than one name"
|
117
|
+
end
|
118
|
+
|
119
|
+
if names.empty? && !roles.empty?
|
120
|
+
names.push roles.map(&:name).sort.join('-')
|
121
|
+
end
|
122
|
+
|
123
|
+
if opts[:count]
|
124
|
+
names = generate_sequenced_names.(names[0], opts[:count].to_i)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Makes sure they're all new.
|
128
|
+
names = names.map do |name|
|
129
|
+
CLI.fetch_item(name, from: Node, exist: false)
|
130
|
+
end
|
131
|
+
|
132
|
+
provider = if opts.key?(:provider)
|
133
|
+
CLI.fetch_item(opts[:provider], from: Provider)
|
134
|
+
else
|
135
|
+
Cult.project.default_provider
|
136
|
+
end
|
137
|
+
|
138
|
+
# Use --size if it was specified, otherwise pull the
|
139
|
+
# provider's default.
|
140
|
+
node_spec = %i(size image zone).map do |m|
|
141
|
+
value = opts[m] || provider.definition["default_#{m}"]
|
142
|
+
fail CLIError, "No #{m} specified (and no default)" if value.nil?
|
143
|
+
[m, value]
|
144
|
+
end.to_h
|
145
|
+
|
146
|
+
nodes = names.map do |name|
|
147
|
+
data = {
|
148
|
+
name: name,
|
149
|
+
roles: roles.map(&:name)
|
150
|
+
}
|
151
|
+
|
152
|
+
Node.from_data!(Cult.project, data).tap do |node|
|
153
|
+
if opts[:bootstrap]
|
154
|
+
prov_data = provider.provision!(name: node.name,
|
155
|
+
image: node_spec[:image],
|
156
|
+
size: node_spec[:size],
|
157
|
+
zone: node_spec[:zone],
|
158
|
+
ssh_key_files: '/Users/mike/.ssh/id_rsa.pub')
|
159
|
+
File.write(Cult.project.dump_name(node.state_path),
|
160
|
+
Cult.project.dump_object(prov_data))
|
161
|
+
|
162
|
+
c = Commander.new(project: Cult.project, node: node)
|
163
|
+
c.bootstrap!
|
164
|
+
c.install!(node)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
171
|
+
node.add_command node_create
|
172
|
+
|
173
|
+
node_list = Cri::Command.define do
|
174
|
+
name 'list'
|
175
|
+
aliases 'ls'
|
176
|
+
summary 'List nodes'
|
177
|
+
description <<~EOD.format_description
|
178
|
+
This command lists the nodes in the project.
|
179
|
+
EOD
|
180
|
+
|
181
|
+
required :r, :role, 'List only nodes which include <value>',
|
182
|
+
multiple: true
|
183
|
+
|
184
|
+
run(arguments: 0..1) do |opts, args, cmd|
|
185
|
+
nodes = args.empty? ? Cult.project.nodes
|
186
|
+
: CLI.fetch_items(*args, from: Node)
|
187
|
+
|
188
|
+
if opts[:role]
|
189
|
+
roles = CLI.fetch_items(opts[:role], from: Role)
|
190
|
+
nodes = nodes.select do |n|
|
191
|
+
roles.any? { |role| n.has_role?(role) }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
nodes.each do |node|
|
196
|
+
puts "Node: #{node.inspect}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
node.add_command node_list
|
201
|
+
|
202
|
+
return node
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'cult/drivers/load'
|
2
|
+
|
3
|
+
module Cult
|
4
|
+
module CLI
|
5
|
+
|
6
|
+
module_function
|
7
|
+
def provider_cmd
|
8
|
+
provider = Cri::Command.define do
|
9
|
+
optional_project
|
10
|
+
name 'provider'
|
11
|
+
aliases 'providers'
|
12
|
+
summary 'Provider Commands'
|
13
|
+
description <<~EOD.format_description
|
14
|
+
A provider is a VPS service. Cult ships with drivers for quite a few
|
15
|
+
services, (which can be listed with `cult provider drivers`).
|
16
|
+
|
17
|
+
The commands here actually set up your environment with provider
|
18
|
+
accounts. Regarding terminology:
|
19
|
+
|
20
|
+
A "driver" is an interface to a third party service you probably pay
|
21
|
+
for, for example, "mikes-kvm-warehouse" would be a driver that knows
|
22
|
+
how to interact with the commercial VPS provider "Mike's KVM
|
23
|
+
Warehouse".
|
24
|
+
|
25
|
+
A "provider" is a configured account on a service, which uses a
|
26
|
+
driver to get things done. For example "Bob's Account at Mike's
|
27
|
+
KVM Warehouse".
|
28
|
+
|
29
|
+
In a lot the common case, you'll be using one provider, which is using
|
30
|
+
a driver of the same name.
|
31
|
+
EOD
|
32
|
+
|
33
|
+
run(arguments: 0) do |opts, args, cmd|
|
34
|
+
puts cmd.help
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
provider_list = Cri::Command.define do
|
40
|
+
name 'list'
|
41
|
+
aliases 'ls'
|
42
|
+
summary 'List Providers'
|
43
|
+
description <<~EOD.format_description
|
44
|
+
Lists Providers for this project. If --driver is specified, it only
|
45
|
+
lists Providers which employ that driver.
|
46
|
+
EOD
|
47
|
+
required :d, :driver, "Restrict list to providers using DRIVER"
|
48
|
+
|
49
|
+
run(arguments: 0..1) do |opts, args, cmd|
|
50
|
+
providers = Cult.project.providers
|
51
|
+
|
52
|
+
# Filtering
|
53
|
+
providers = providers.all(args[0]) if args[0]
|
54
|
+
|
55
|
+
if opts[:driver]
|
56
|
+
driver_cls = Cult.project.drivers[opts[:driver]]
|
57
|
+
providers = providers.select do |p|
|
58
|
+
p.driver.is_a?(driver_cls)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
providers.each do |p|
|
63
|
+
printf "%-20s %-s\n", p.name, Cult.project.relative_path(p.path)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
provider.add_command(provider_list)
|
69
|
+
|
70
|
+
|
71
|
+
provider_avail = Cri::Command.define do
|
72
|
+
optional_project
|
73
|
+
name 'drivers'
|
74
|
+
summary 'list available drivers'
|
75
|
+
description <<~EOD.format_description
|
76
|
+
Displays a list of all available drivers, by their name, and list of
|
77
|
+
gem dependencies.
|
78
|
+
EOD
|
79
|
+
|
80
|
+
run(arguments: 0) do |opts, args, cmd|
|
81
|
+
Cult::Drivers.all.each do |p|
|
82
|
+
printf "%-20s %-s\n", p.driver_name, p.required_gems
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
provider.add_command(provider_avail)
|
87
|
+
|
88
|
+
|
89
|
+
provider_create = Cri::Command.define do
|
90
|
+
name 'create'
|
91
|
+
aliases 'new'
|
92
|
+
usage 'create NAME'
|
93
|
+
summary 'creates a new provider for your project'
|
94
|
+
required :d, :driver, 'Specify driver, if different than NAME'
|
95
|
+
description <<~EOD.format_description
|
96
|
+
Creates a new provider for the project. There are a few ways this
|
97
|
+
can be specified, for example
|
98
|
+
|
99
|
+
cult provider create mikes-kvm-warehouse
|
100
|
+
|
101
|
+
Will set up a provider account using 'mikes-kvm-warehouse' as both
|
102
|
+
the driver type and the local provider name.
|
103
|
+
|
104
|
+
If you need the two to be separate, for example, if you have multiple
|
105
|
+
accounts at Mike's KVM Warehouse, you can specify a driver name with
|
106
|
+
--driver, and an independent provider name.
|
107
|
+
EOD
|
108
|
+
|
109
|
+
run(arguments: 1) do |opts, args, cmd|
|
110
|
+
name, _ = *args
|
111
|
+
driver = CLI.fetch_item(opts[:driver] || name, from: Driver)
|
112
|
+
name = CLI.fetch_item(name, from: Provider, exist: false)
|
113
|
+
|
114
|
+
puts [driver, name].inspect
|
115
|
+
end
|
116
|
+
end
|
117
|
+
provider.add_command(provider_create)
|
118
|
+
|
119
|
+
|
120
|
+
provider
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Cult
|
4
|
+
module CLI
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def role_cmd
|
8
|
+
role = Cri::Command.define do
|
9
|
+
optional_project
|
10
|
+
name 'role'
|
11
|
+
aliases 'roles'
|
12
|
+
summary 'Manage roles'
|
13
|
+
description <<~EOD.format_description
|
14
|
+
A role defines what a node does. The easiest way to think about it
|
15
|
+
is just a directory full of scripts (tasks).
|
16
|
+
|
17
|
+
A role can include an arbitrary number of other roles. For example,
|
18
|
+
you may have two roles `rat-site' and `tempo-site', which both depend
|
19
|
+
on a common role `web-server'. In this case, `web-server' would be
|
20
|
+
the set of tasks that install the web server through the package
|
21
|
+
manager, set up a base configuration, and allow ports 80 and 443
|
22
|
+
through the firewall.
|
23
|
+
|
24
|
+
Both `rat-site' and `tempo-site' would declare that they depend on
|
25
|
+
`web-server' by listing it in their `includes' array in role.json.
|
26
|
+
Their tasks would then only consist of dropping a configuration file,
|
27
|
+
TLS keys and certificates into `/etc/your-httpd.d`.
|
28
|
+
|
29
|
+
Composability is the mindset behind roles. Cult assumes, by default,
|
30
|
+
that roles are written in a way to compose well with each other if
|
31
|
+
they find themselves on the same node. That is not always possible,
|
32
|
+
(thus the `conflicts' key exists in `role.json'), but is the goal.
|
33
|
+
You should write tasks with that in mind. For example, dropping
|
34
|
+
files into `/etc/your-httpd.d` instead of re-writing
|
35
|
+
`/etc/your-httpd.conf`. With this setup, a node could include both
|
36
|
+
`rat-site` and `tempo-site` roles and be happily serving both sites.
|
37
|
+
|
38
|
+
By default, `cult init` generates two root roles that don't depend on
|
39
|
+
anything else: `all` and `bootstrap`. The `bootstrap` role exists
|
40
|
+
to get a node from a clean OS install to a configuration to be
|
41
|
+
managed by the settings in `all'. Theoretically, if you're happy
|
42
|
+
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 `all/role.json` to
|
44
|
+
"root".
|
45
|
+
|
46
|
+
The tasks in the `all` role are considered shared amongst all roles.
|
47
|
+
However, the only thing special about the `all` role is that Cult
|
48
|
+
assumes roles and nodes without an explicit `includes` setting belong
|
49
|
+
to all.
|
50
|
+
EOD
|
51
|
+
|
52
|
+
run(arguments: 0) do |opts, args, cmd|
|
53
|
+
puts cmd.help
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
role_create = Cri::Command.define do
|
59
|
+
name 'create'
|
60
|
+
aliases 'new'
|
61
|
+
summary 'creates a new role'
|
62
|
+
usage 'create [options] NAME'
|
63
|
+
description <<~EOD.format_description
|
64
|
+
Creates a new role names NAME, which will then be available under
|
65
|
+
$CULT_PROJECT/roles/$NAME
|
66
|
+
EOD
|
67
|
+
|
68
|
+
required :r, :roles, 'this role depends on another role',
|
69
|
+
multiple: true
|
70
|
+
|
71
|
+
run(arguments: 1) do |opts, args, cmd|
|
72
|
+
name = CLI.fetch_item(args[0], from: Role, exist: false)
|
73
|
+
|
74
|
+
role = Role.by_name(Cult.project, name)
|
75
|
+
data = {}
|
76
|
+
|
77
|
+
if opts[:roles]
|
78
|
+
data[:includes] = CLI.fetch_items(opts[:roles],
|
79
|
+
from: Role).map(&:name)
|
80
|
+
end
|
81
|
+
FileUtils.mkdir_p(role.path)
|
82
|
+
File.write(Cult.project.dump_name(role.definition_file),
|
83
|
+
Cult.project.dump_object(data))
|
84
|
+
|
85
|
+
FileUtils.mkdir_p(File.join(role.path, "files"))
|
86
|
+
File.write(File.join(role.path, "files", ".keep"), '')
|
87
|
+
|
88
|
+
FileUtils.mkdir_p(File.join(role.path, "tasks"))
|
89
|
+
File.write(File.join(role.path, "tasks", ".keep"), '')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
role.add_command role_create
|
93
|
+
|
94
|
+
|
95
|
+
role_destroy = Cri::Command.define do
|
96
|
+
name 'destroy'
|
97
|
+
aliases 'delete', 'rm'
|
98
|
+
usage 'destroy ROLES...'
|
99
|
+
summary 'Destroy role ROLE'
|
100
|
+
description <<~EOD.format_description
|
101
|
+
Destroys all roles specified.
|
102
|
+
EOD
|
103
|
+
|
104
|
+
run(arguments: 1..-1) do |opts, args, cmd|
|
105
|
+
roles = args.map do |role_name|
|
106
|
+
CLI.fetch_items(role_name, from: Role)
|
107
|
+
end.flatten
|
108
|
+
|
109
|
+
roles.each do |role|
|
110
|
+
if CLI.yes_no?("Delete role #{role.name} (#{role.path})?",
|
111
|
+
default: :no)
|
112
|
+
FileUtils.rm_rf(role.path)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
role.add_command(role_destroy)
|
118
|
+
|
119
|
+
|
120
|
+
role_list = Cri::Command.define do
|
121
|
+
name 'list'
|
122
|
+
aliases 'ls'
|
123
|
+
usage 'list [ROLES...]'
|
124
|
+
summary 'List existing roles'
|
125
|
+
description <<~EOD.format_description
|
126
|
+
Lists roles in this project. By default, lists all roles. If one or
|
127
|
+
more ROLES are specified, only lists those
|
128
|
+
EOD
|
129
|
+
|
130
|
+
run(arguments: 0..-1) do |opts, args, cmd|
|
131
|
+
roles = Cult.project.roles
|
132
|
+
unless args.empty?
|
133
|
+
roles = CLI.fetch_items(*args, from: Role)
|
134
|
+
end
|
135
|
+
|
136
|
+
roles.each do |r|
|
137
|
+
fmt = "%-20s %s\n"
|
138
|
+
printf fmt, r.name, r.build_order.map(&:name).join(', ')
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
role.add_command(role_list)
|
144
|
+
|
145
|
+
|
146
|
+
role
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'cult/task'
|
2
|
+
require 'cult/role'
|
3
|
+
|
4
|
+
module Cult
|
5
|
+
module CLI
|
6
|
+
module_function
|
7
|
+
def task_cmd
|
8
|
+
task = Cri::Command.define do
|
9
|
+
optional_project
|
10
|
+
name 'task'
|
11
|
+
aliases 'tasks'
|
12
|
+
summary 'Task Manipulation'
|
13
|
+
usage 'task [command]'
|
14
|
+
description <<~EOD.format_description
|
15
|
+
|
16
|
+
EOD
|
17
|
+
|
18
|
+
run(arguments: 0) do |opts, args, cmd|
|
19
|
+
puts cmd.help
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
task_reserial = Cri::Command.define do
|
26
|
+
name 'resequence'
|
27
|
+
summary 'Resequences task serial numbers'
|
28
|
+
|
29
|
+
flag :A, :all, 'Reserial all roles'
|
30
|
+
flag :G, :'git-add', '`git add` each change'
|
31
|
+
required :r, :role, 'Roles to resequence (multiple)',
|
32
|
+
multiple: true
|
33
|
+
|
34
|
+
description <<~EOD.format_description
|
35
|
+
Resequences the serial numbers in each task provided with --roles,
|
36
|
+
or all roles with --all. You cannot supply both --all and specify
|
37
|
+
--roles.
|
38
|
+
|
39
|
+
A resequence isn't something to do lightly once you have deployed
|
40
|
+
nodes. This will be elaborated on in the future. It's probably
|
41
|
+
a good idea to do this in a development branch and test out the
|
42
|
+
results.
|
43
|
+
|
44
|
+
The --git-add option will execute `git add` for each rename made.
|
45
|
+
This will make your status contain a bunch of neat renames, instead of
|
46
|
+
a lot of deleted and untracked files.
|
47
|
+
|
48
|
+
This command respects the global --yes flag.
|
49
|
+
EOD
|
50
|
+
|
51
|
+
|
52
|
+
run(arguments: 0) do |opts, args, cmd|
|
53
|
+
if opts[:all] && Array(opts[:role]).size != 0
|
54
|
+
fail CLIError, "can't supply -A and also a list of roles"
|
55
|
+
end
|
56
|
+
|
57
|
+
roles = if opts[:all]
|
58
|
+
Cult.project.roles
|
59
|
+
elsif opts[:role]
|
60
|
+
CLI.fetch_items(opts[:role], from: Role)
|
61
|
+
else
|
62
|
+
fail CLIError, "no roles specified with --role or --all"
|
63
|
+
end
|
64
|
+
|
65
|
+
roles.each do |role|
|
66
|
+
puts "Resequencing role: `#{role.name}'"
|
67
|
+
tasks = role.tasks.sort_by do |task|
|
68
|
+
# This makes sure we don't change order for duplicate serials
|
69
|
+
[task.serial, task.name]
|
70
|
+
end
|
71
|
+
|
72
|
+
renames = tasks.map.with_index do |task, i|
|
73
|
+
if task.serial != i
|
74
|
+
new_task = Task.from_serial_and_name(role,
|
75
|
+
serial: i,
|
76
|
+
name: task.name)
|
77
|
+
[task, new_task]
|
78
|
+
end
|
79
|
+
end.compact.to_h
|
80
|
+
|
81
|
+
next if renames.empty?
|
82
|
+
|
83
|
+
unless Cult::CLI.yes?
|
84
|
+
renames.each do |src, dst|
|
85
|
+
puts "rename #{Cult.project.relative_path(src.path)} " +
|
86
|
+
"-> #{Cult.project.relative_path(dst.path)}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
if Cult::CLI.yes_no?("Execute renames?")
|
91
|
+
renames.each do |src, dst|
|
92
|
+
FileUtils.mv(src.path, dst.path)
|
93
|
+
if opts[:'git-add']
|
94
|
+
`git add #{src.path}; git add #{dst.path}`
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
task.add_command(task_reserial)
|
102
|
+
|
103
|
+
|
104
|
+
task_sanity = Cri::Command.define do
|
105
|
+
name 'sanity'
|
106
|
+
summary 'checks task files for numbering sanity'
|
107
|
+
description <<~EOD.format_description
|
108
|
+
TODO: Document (and do something!)
|
109
|
+
EOD
|
110
|
+
|
111
|
+
run do |opts, args, cmd|
|
112
|
+
puts 'checking sanity...'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
task.add_command task_sanity
|
116
|
+
|
117
|
+
|
118
|
+
task_create = Cri::Command.define do
|
119
|
+
name 'create'
|
120
|
+
aliases 'new'
|
121
|
+
usage 'create [options] DESCRIPTION'
|
122
|
+
summary 'create a new task for ROLE with a proper serial'
|
123
|
+
description <<~EOD.format_description
|
124
|
+
EOD
|
125
|
+
|
126
|
+
required :r, :role, 'role for task. defaults to "all"'
|
127
|
+
flag :e, :edit, 'open generated task file in your $EDITOR'
|
128
|
+
|
129
|
+
run do |opts, args, cmd|
|
130
|
+
english = args.join " "
|
131
|
+
opts[:roles] ||= 'all'
|
132
|
+
puts [english, opts[:roles], opts[:edit]].inspect
|
133
|
+
end
|
134
|
+
end
|
135
|
+
task.add_command task_create
|
136
|
+
|
137
|
+
task
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|