cult 0.1.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|