cult 0.1.1.pre → 0.1.2.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 +48 -28
- data/cult +1 -1
- data/cult.gemspec +4 -4
- data/doc/images/masthead@0.5x.png +0 -0
- data/exe/cult +2 -5
- data/lib/cult/artifact.rb +2 -1
- data/lib/cult/bundle.rb +26 -0
- data/lib/cult/cli/common.rb +2 -0
- data/lib/cult/cli/console_cmd.rb +11 -11
- data/lib/cult/cli/cri_extensions.rb +1 -2
- data/lib/cult/cli/fleet_cmd.rb +37 -0
- data/lib/cult/cli/init_cmd.rb +0 -2
- data/lib/cult/cli/node_cmd.rb +54 -22
- data/lib/cult/cli/task_cmd.rb +25 -9
- data/lib/cult/commander.rb +78 -52
- data/lib/cult/definition.rb +4 -7
- data/lib/cult/driver.rb +1 -1
- data/lib/cult/drivers/common.rb +8 -21
- data/lib/cult/drivers/digital_ocean_driver.rb +41 -48
- data/lib/cult/drivers/linode_driver.rb +12 -19
- data/lib/cult/drivers/load.rb +0 -3
- data/lib/cult/drivers/vultr_driver.rb +33 -44
- data/lib/cult/named_array.rb +62 -14
- data/lib/cult/node.rb +43 -8
- data/lib/cult/project.rb +0 -8
- data/lib/cult/project_context.rb +23 -0
- data/lib/cult/provider.rb +0 -3
- data/lib/cult/role.rb +30 -50
- data/lib/cult/singleton_instances.rb +43 -0
- data/lib/cult/skel.rb +2 -2
- data/lib/cult/task.rb +30 -8
- data/lib/cult/template.rb +18 -70
- data/lib/cult/transaction.rb +44 -0
- data/lib/cult/transferable.rb +2 -5
- data/lib/cult/user_refinements.rb +65 -0
- data/lib/cult/version.rb +1 -1
- data/lib/cult.rb +26 -0
- data/skel/roles/all/tasks/sync +24 -0
- data/skel/roles/bootstrap/files/cult-motd +1 -1
- metadata +19 -14
- data/lib/cult/config.rb +0 -22
- data/lib/cult/drivers/script_driver.rb +0 -27
- data/skel/keys/.keep +0 -0
data/lib/cult/commander.rb
CHANGED
@@ -2,33 +2,9 @@ require 'net/ssh'
|
|
2
2
|
require 'net/scp'
|
3
3
|
require 'shellwords'
|
4
4
|
require 'rainbow'
|
5
|
-
require '
|
6
|
-
require 'rubygems/package/tar_writer'
|
5
|
+
require 'securerandom'
|
7
6
|
|
8
7
|
module Cult
|
9
|
-
|
10
|
-
class Bundle
|
11
|
-
attr_reader :tar
|
12
|
-
def initialize(io, &block)
|
13
|
-
@tar = Gem::Package::TarWriter.new(io)
|
14
|
-
if block_given?
|
15
|
-
begin
|
16
|
-
yield self
|
17
|
-
ensure
|
18
|
-
@tar.close
|
19
|
-
@tar = nil
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def add_file(project, role, node, transferable)
|
25
|
-
data = transferable.contents(project, role, node, pwd: role.path)
|
26
|
-
tar.add_file(transferable.remote_path, transferable.file_mode) do |io|
|
27
|
-
io.write(data)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
8
|
class Commander
|
33
9
|
attr_reader :project
|
34
10
|
attr_reader :node
|
@@ -42,59 +18,109 @@ module Cult
|
|
42
18
|
Shellwords.escape(s)
|
43
19
|
end
|
44
20
|
|
45
|
-
def
|
21
|
+
def send_tar(io, ssh)
|
22
|
+
filename = SecureRandom.hex + ".tar"
|
23
|
+
puts "Uploading bundle: #{filename}"
|
24
|
+
scp = Net::SCP.new(ssh)
|
25
|
+
scp.upload!(io, filename)
|
26
|
+
ssh.exec! "tar -xf #{esc(filename)} && rm #{esc(filename)}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_build_tar(role)
|
46
30
|
io = StringIO.new
|
47
31
|
Bundle.new(io) do |bundle|
|
48
32
|
puts "Building bundle..."
|
49
33
|
role.build_order.each do |r|
|
50
|
-
(r.artifacts + r.
|
34
|
+
(r.artifacts + r.build_tasks).each do |transferable|
|
51
35
|
bundle.add_file(project, r, node, transferable)
|
52
36
|
end
|
53
37
|
end
|
54
38
|
end
|
55
|
-
filename = "cult-#{role.name}.tar"
|
56
|
-
puts "Uploading bundle #{filename}..."
|
57
39
|
|
58
|
-
scp = Net::SCP.new(ssh)
|
59
40
|
io.rewind
|
60
|
-
|
61
|
-
|
41
|
+
io
|
42
|
+
end
|
43
|
+
|
44
|
+
def exec_remote!(ssh:, role:, task:)
|
45
|
+
token = SecureRandom.hex
|
46
|
+
task_bin = role.relative_path(task.path)
|
47
|
+
|
48
|
+
puts "Executing: #{task.remote_path}"
|
49
|
+
res = ssh.exec! <<~BASH
|
50
|
+
cd #{esc(role.remote_path)}; \
|
51
|
+
./#{esc(task_bin)} && \
|
52
|
+
echo #{esc(token)}
|
53
|
+
BASH
|
54
|
+
|
55
|
+
if res.chomp.end_with?(token)
|
56
|
+
res = res.gsub(token, '')
|
57
|
+
puts Rainbow(res.gsub(/^/, ' ')).darkgray.italic
|
58
|
+
true
|
59
|
+
else
|
60
|
+
puts Rainbow(res).red
|
61
|
+
puts "Failed"
|
62
|
+
false
|
63
|
+
end
|
62
64
|
end
|
63
65
|
|
64
66
|
def install!(role)
|
65
|
-
connect(user: role.
|
66
|
-
|
67
|
+
connect(user: role.user) do |ssh|
|
68
|
+
io = create_build_tar(role)
|
69
|
+
send_tar(io, ssh)
|
67
70
|
|
68
71
|
role.build_order.each do |r|
|
69
72
|
puts "Installing role: #{Rainbow(r.name).blue}"
|
70
|
-
|
71
|
-
|
72
|
-
puts "Executing: #{t.remote_path}"
|
73
|
-
task_bin = r.relative_path(t.path)
|
74
|
-
res = ssh.exec! <<~BASH
|
75
|
-
cd #{esc(working_dir)}; \
|
76
|
-
if [ ! -f ./#{esc(task_bin)}.success ]; then \
|
77
|
-
touch ./#{esc(task_bin)}.attempt && \
|
78
|
-
./#{esc(task_bin)} && \
|
79
|
-
mv ./#{esc(task_bin)}.attempt ./#{esc(task_bin)}.success; \
|
80
|
-
fi
|
81
|
-
BASH
|
82
|
-
unless res.empty?
|
83
|
-
puts Rainbow(res.gsub(/^/, ' ')).darkgray.italic
|
84
|
-
end
|
73
|
+
r.build_tasks.each do |task|
|
74
|
+
exec_remote!(ssh: ssh, role: r, task: task)
|
85
75
|
end
|
86
76
|
end
|
87
77
|
end
|
88
78
|
end
|
89
79
|
|
80
|
+
def find_sync_tasks
|
81
|
+
r = []
|
82
|
+
node.build_order.each do |role|
|
83
|
+
role.event_tasks.each do |task|
|
84
|
+
r << task if task.name == 'sync'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
r
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_sync_tar
|
91
|
+
io = StringIO.new
|
92
|
+
Bundle.new(io) do |bundle|
|
93
|
+
find_sync_tasks.each do |task|
|
94
|
+
bundle.add_file(project, task.role, node, task)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
io.rewind
|
99
|
+
io
|
100
|
+
end
|
101
|
+
|
102
|
+
def sync!
|
103
|
+
connect do |ssh|
|
104
|
+
io = create_sync_tar
|
105
|
+
send_tar(io, ssh)
|
106
|
+
find_sync_tasks.each do |task|
|
107
|
+
exec_remote!(ssh: ssh, role: task.role, task: task)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
90
112
|
def bootstrap!
|
91
113
|
bootstrap_role = CLI.fetch_item('bootstrap', from: Role)
|
92
114
|
install!(bootstrap_role)
|
93
115
|
end
|
94
116
|
|
95
|
-
def connect(user
|
96
|
-
|
97
|
-
|
117
|
+
def connect(user: nil, &block)
|
118
|
+
user ||= node.user
|
119
|
+
puts "Connecting with user=#{user}, key=#{node.ssh_private_key_file}"
|
120
|
+
Net::SSH.start(node.host,
|
121
|
+
user,
|
122
|
+
keys_only: true,
|
123
|
+
keys: [node.ssh_private_key_file]) do |ssh|
|
98
124
|
yield ssh
|
99
125
|
end
|
100
126
|
end
|
data/lib/cult/definition.rb
CHANGED
@@ -2,23 +2,20 @@ require 'yaml'
|
|
2
2
|
require 'json'
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
|
-
require 'cult/template'
|
6
|
-
|
7
5
|
module Cult
|
8
6
|
class Definition
|
9
7
|
attr_reader :object
|
10
8
|
attr_reader :bag
|
11
9
|
|
12
10
|
extend Forwardable
|
13
|
-
def_delegators :object, :definition_parameters,
|
11
|
+
def_delegators :object, :definition_parameters,
|
12
|
+
:definition_path,
|
14
13
|
:definition_parents
|
15
14
|
|
16
|
-
|
17
15
|
def initialize(object)
|
18
16
|
@object = object
|
19
17
|
end
|
20
18
|
|
21
|
-
|
22
19
|
def inspect
|
23
20
|
"\#<#{self.class.name} " +
|
24
21
|
"object: #{object.inspect}, " +
|
@@ -63,8 +60,8 @@ module Cult
|
|
63
60
|
@bag ||= begin
|
64
61
|
result = {}
|
65
62
|
filenames.each do |filename|
|
66
|
-
erb = Template.new(definition_parameters)
|
67
|
-
contents = erb.process(File.read(filename))
|
63
|
+
erb = ::Cult::Template.new(project: nil, **definition_parameters)
|
64
|
+
contents = erb.process(File.read(filename), filename: filename)
|
68
65
|
result.merge! decoder_for(filename).call(contents)
|
69
66
|
end
|
70
67
|
result
|
data/lib/cult/driver.rb
CHANGED
data/lib/cult/drivers/common.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'net/ssh'
|
3
3
|
|
4
|
+
require 'cult/transaction'
|
5
|
+
|
4
6
|
module Cult
|
5
7
|
module Drivers
|
6
|
-
|
7
8
|
module Common
|
9
|
+
|
8
10
|
module ClassMethods
|
9
11
|
# Lets us write a method "something_map" that returns {'ident' => ...},
|
10
12
|
# and also get a function "something" that returns the keys.
|
@@ -37,11 +39,6 @@ module Cult
|
|
37
39
|
end
|
38
40
|
|
39
41
|
|
40
|
-
def self.included(cls)
|
41
|
-
cls.extend(ClassMethods)
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
42
|
# works with with_id_mapping to convert a human-readible/normalized key
|
46
43
|
# to the id the backend service expects. Allows '=value' to force a
|
47
44
|
# literal value, and gives better error messages.
|
@@ -81,21 +78,6 @@ module Cult
|
|
81
78
|
end
|
82
79
|
|
83
80
|
|
84
|
-
# Enter this block once a node has been created. It makes sure it's
|
85
|
-
# destroyed if there's an error later in the procedure.
|
86
|
-
def rollback_on_error(id:, &block)
|
87
|
-
begin
|
88
|
-
yield
|
89
|
-
rescue Exception => e
|
90
|
-
begin
|
91
|
-
destroy!(id: id)
|
92
|
-
ensure
|
93
|
-
raise e
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
|
99
81
|
def slugify(s)
|
100
82
|
s.gsub(/[^a-z0-9]+/i, '-').gsub(/(^\-)|(-\z)/, '').downcase
|
101
83
|
end
|
@@ -187,6 +169,11 @@ module Cult
|
|
187
169
|
end
|
188
170
|
end
|
189
171
|
|
172
|
+
def self.included(cls)
|
173
|
+
cls.extend(ClassMethods)
|
174
|
+
cls.include(::Cult::Transaction)
|
175
|
+
end
|
176
|
+
|
190
177
|
end
|
191
178
|
end
|
192
179
|
end
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'cult/driver'
|
2
|
-
require 'cult/drivers/common'
|
3
|
-
require 'net/ssh'
|
4
1
|
require 'json'
|
5
2
|
|
6
3
|
module Cult
|
@@ -9,8 +6,6 @@ module Cult
|
|
9
6
|
class DigitalOceanDriver < ::Cult::Driver
|
10
7
|
self.required_gems = 'droplet_kit'
|
11
8
|
|
12
|
-
include Common
|
13
|
-
|
14
9
|
attr_reader :client
|
15
10
|
|
16
11
|
def initialize(api_key:)
|
@@ -49,24 +44,13 @@ module Cult
|
|
49
44
|
with_id_mapping :zones_map
|
50
45
|
|
51
46
|
|
52
|
-
def ssh_keys
|
53
|
-
client.ssh_keys.all.to_a.map(&:to_h)
|
54
|
-
end
|
55
|
-
memoize :ssh_keys
|
56
|
-
|
57
47
|
|
58
48
|
def upload_ssh_key(file:)
|
59
49
|
key = ssh_key_info(file: file)
|
60
50
|
# If we already have one with this fingerprint, use it.
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
ssh_keys_dememo!
|
65
|
-
client.ssh_keys.create \
|
66
|
-
DropletKit::SSHKey.new(fingerprint: key[:fingerprint],
|
67
|
-
public_key: key[:data],
|
68
|
-
name: key[:name])
|
69
|
-
end
|
51
|
+
dk_key = DropletKit::SSHKey.new(public_key: key[:data],
|
52
|
+
name: "Cult: #{key[:name]}")
|
53
|
+
client.ssh_keys.create(dk_key).id
|
70
54
|
end
|
71
55
|
|
72
56
|
|
@@ -80,39 +64,48 @@ module Cult
|
|
80
64
|
end
|
81
65
|
|
82
66
|
|
83
|
-
def destroy!(id:)
|
67
|
+
def destroy!(id:, ssh_key_id: nil)
|
84
68
|
client.droplets.delete(id: id)
|
69
|
+
destroy_ssh_key!(ssh_key_id: ssh_key_id) if ssh_key_id
|
85
70
|
end
|
86
71
|
|
72
|
+
def destroy_ssh_key!(ssh_key_id:)
|
73
|
+
client.ssh_keys.delete(id: ssh_key_id)
|
74
|
+
end
|
87
75
|
|
88
|
-
def provision!(name:, size:, zone:, image:,
|
89
|
-
|
90
|
-
upload_ssh_key(file:
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
76
|
+
def provision!(name:, size:, zone:, image:, ssh_public_key:)
|
77
|
+
transaction do |xac|
|
78
|
+
ssh_key_id = upload_ssh_key(file: ssh_public_key)
|
79
|
+
xac.rollback do
|
80
|
+
destroy_ssh_key!(id: ssh_key_id)
|
81
|
+
end
|
82
|
+
|
83
|
+
begin
|
84
|
+
params = {
|
85
|
+
name: name,
|
86
|
+
size: fetch_mapped(name: :size, from: sizes_map, key: size),
|
87
|
+
image: fetch_mapped(name: :image, from: images_map, key: image),
|
88
|
+
region: fetch_mapped(name: :zone, from: zones_map, key: zone),
|
89
|
+
ssh_keys: [ssh_key_id],
|
90
|
+
|
91
|
+
private_networking: true,
|
92
|
+
ipv6: true
|
93
|
+
}
|
94
|
+
rescue KeyError => e
|
95
|
+
fail ArgumentError, "Invalid argument: #{e.message}"
|
96
|
+
end
|
97
|
+
|
98
|
+
droplet = DropletKit::Droplet.new(params)
|
99
|
+
|
100
|
+
if droplet.nil?
|
101
|
+
fail "Droplet was nil: #{params.inspect}"
|
102
|
+
end
|
109
103
|
|
110
|
-
if droplet.nil?
|
111
|
-
fail "Droplet was nil: #{params.inspect}"
|
112
|
-
end
|
113
|
-
|
114
|
-
rollback_on_error(id: droplet.id) do
|
115
104
|
droplet = client.droplets.create(droplet)
|
105
|
+
xac.rollback do
|
106
|
+
destroy!(id: droplet.id)
|
107
|
+
end
|
108
|
+
|
116
109
|
droplet = await_creation(droplet)
|
117
110
|
|
118
111
|
ipv4_public = droplet.networks.v4.find {|n| n.type == 'public' }
|
@@ -126,8 +119,8 @@ module Cult
|
|
126
119
|
size: size,
|
127
120
|
zone: zone,
|
128
121
|
image: image,
|
129
|
-
|
130
|
-
|
122
|
+
|
123
|
+
ssh_key_id: ssh_key_id,
|
131
124
|
|
132
125
|
id: droplet.id,
|
133
126
|
created_at: droplet.created_at,
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'cult/driver'
|
2
|
-
require 'cult/cli/common'
|
3
|
-
|
4
1
|
require 'securerandom'
|
5
2
|
require 'time'
|
6
3
|
|
@@ -48,8 +45,6 @@ module Cult
|
|
48
45
|
class LinodeDriver < ::Cult::Driver
|
49
46
|
self.required_gems = 'linode'
|
50
47
|
|
51
|
-
include Common
|
52
|
-
|
53
48
|
SWAP_SIZE = 256
|
54
49
|
|
55
50
|
attr_reader :client
|
@@ -124,30 +119,29 @@ module Cult
|
|
124
119
|
end
|
125
120
|
|
126
121
|
|
127
|
-
def destroy!(id:)
|
122
|
+
def destroy!(id:, ssh_key_id: [])
|
128
123
|
client.linode.delete(linodeid: id, skipchecks: true)
|
129
124
|
end
|
130
125
|
|
131
126
|
|
132
|
-
def provision!(name:, size:, zone:, image:,
|
127
|
+
def provision!(name:, size:, zone:, image:, ssh_public_key:)
|
133
128
|
sizeid = fetch_mapped(name: :size, from: sizes_map, key: size)
|
134
129
|
imageid = fetch_mapped(name: :image, from: images_map, key: image)
|
135
130
|
zoneid = fetch_mapped(name: :zone, from: zones_map, key: zone)
|
136
131
|
disksize = disk_size_for_size(size)
|
137
132
|
|
138
|
-
|
139
|
-
|
133
|
+
transaction do |xac|
|
134
|
+
linodeid = client.linode.create(datacenterid: zoneid,
|
135
|
+
planid: sizeid).linodeid
|
136
|
+
xac.rollback do
|
137
|
+
destroy!(id: linodeid)
|
138
|
+
end
|
140
139
|
|
141
|
-
rollback_on_error(id: linodeid) do
|
142
140
|
# We give it a name early so we can find it in the Web UI if anything
|
143
141
|
# goes wrong.
|
144
142
|
client.linode.update(linodeid: linodeid, label: name)
|
145
143
|
client.linode.ip.addprivate(linodeid: linodeid)
|
146
144
|
|
147
|
-
ssh_keys = Array(ssh_key_files).map do |file|
|
148
|
-
ssh_key_info(file: file)
|
149
|
-
end
|
150
|
-
|
151
145
|
# You shouldn't run meaningful swap, but this makes the Web UI not
|
152
146
|
# scare you, and apparently Linux runs better with ANY swap,
|
153
147
|
# regardless of how small. We've matched the small size the Linode
|
@@ -165,15 +159,15 @@ module Cult
|
|
165
159
|
# Linode's max length is 128, generates longer than that to
|
166
160
|
# no get the fixed == and truncates.
|
167
161
|
rootpass: SecureRandom.base64(100)[0...128],
|
168
|
-
rootsshkey:
|
162
|
+
rootsshkey: ssh_key_info(file: ssh_public_key)[:data],
|
169
163
|
size: disksize - SWAP_SIZE
|
170
164
|
}
|
171
165
|
|
172
166
|
diskid = client.linode.disk.createfromdistribution(params).diskid
|
173
167
|
|
174
168
|
|
175
|
-
# We don't have to reference the config specifically: It'll be the
|
176
|
-
# configuration that exists, so it'll be used.
|
169
|
+
# We don't have to reference the config specifically: It'll be the
|
170
|
+
# only configuration that exists, so it'll be used.
|
177
171
|
client.linode.config.create(linodeid: linodeid,
|
178
172
|
kernelid: latest_kernel_id,
|
179
173
|
disklist: "#{diskid},#{swapid}",
|
@@ -200,8 +194,6 @@ module Cult
|
|
200
194
|
size: size,
|
201
195
|
zone: zone,
|
202
196
|
image: image,
|
203
|
-
ssh_key_files: ssh_keys.map{|k| k[:file]},
|
204
|
-
ssh_keys: ssh_keys.map{|k| k[:fingerprint]},
|
205
197
|
|
206
198
|
id: linodeid,
|
207
199
|
created_at: Time.now.iso8601,
|
@@ -213,6 +205,7 @@ module Cult
|
|
213
205
|
meta: {}
|
214
206
|
}
|
215
207
|
end
|
208
|
+
|
216
209
|
end
|
217
210
|
|
218
211
|
|
data/lib/cult/drivers/load.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'cult/driver'
|
2
|
-
require 'cult/drivers/common'
|
3
|
-
|
4
1
|
require 'net/ssh'
|
5
2
|
require 'time'
|
6
3
|
|
@@ -9,8 +6,6 @@ module Cult
|
|
9
6
|
class VultrDriver < ::Cult::Driver
|
10
7
|
self.required_gems = 'vultr'
|
11
8
|
|
12
|
-
include Common
|
13
|
-
|
14
9
|
attr_reader :api_key
|
15
10
|
|
16
11
|
def initialize(api_key:)
|
@@ -90,26 +85,11 @@ module Cult
|
|
90
85
|
with_api_key :sizes_map
|
91
86
|
|
92
87
|
|
93
|
-
def ssh_keys
|
94
|
-
Vultr::SSHKey.list[:result].values
|
95
|
-
end
|
96
|
-
memoize :ssh_keys
|
97
|
-
with_api_key :ssh_keys
|
98
|
-
|
99
88
|
|
100
89
|
def upload_ssh_key(file:)
|
101
90
|
key = ssh_key_info(file: file)
|
102
|
-
|
103
|
-
|
104
|
-
exist
|
105
|
-
else
|
106
|
-
ssh_keys_dememo!
|
107
|
-
Vultr::SSHKey.create(name: "Cult: #{key[:name]}",
|
108
|
-
ssh_key: key[:data])[:result]
|
109
|
-
end
|
110
|
-
|
111
|
-
vkey["fingerprint"] = key[:fingerprint]
|
112
|
-
vkey
|
91
|
+
Vultr::SSHKey.create(name: "Cult: #{key[:name]}",
|
92
|
+
ssh_key: key[:data])[:result]["SSHKEYID"]
|
113
93
|
end
|
114
94
|
with_api_key :upload_ssh_key
|
115
95
|
|
@@ -121,34 +101,43 @@ module Cult
|
|
121
101
|
end
|
122
102
|
|
123
103
|
|
124
|
-
def destroy!(id:)
|
104
|
+
def destroy!(id:, ssh_key_id: nil)
|
125
105
|
Vultr::Server.destroy(SUBID: id)
|
106
|
+
destroy_ssh_key!(ssh_key_id: ssh_key_id) if ssh_key_id
|
126
107
|
end
|
127
108
|
with_api_key :destroy!
|
128
109
|
|
110
|
+
def destroy_ssh_key!(ssh_key_id:)
|
111
|
+
Vultr::SSHKey.destroy(SSHKEYID: ssh_key_id)
|
112
|
+
end
|
113
|
+
with_api_key :destroy_ssh_key!
|
129
114
|
|
130
|
-
def provision!(name:, size:, zone:, image:, ssh_key_files:)
|
131
|
-
keys = Array(ssh_key_files).map do |filename|
|
132
|
-
upload_ssh_key(file: filename)
|
133
|
-
end
|
134
|
-
|
135
|
-
sizeid = fetch_mapped(name: :size, from: sizes_map, key: size)
|
136
|
-
imageid = fetch_mapped(name: :image, from: images_map, key: image)
|
137
|
-
zoneid = fetch_mapped(name: :zone, from: zones_map, key: zone)
|
138
115
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
hostname: name,
|
146
|
-
SSHKEYID: keys.map{|v| v["SSHKEYID"] }
|
147
|
-
.join(','))
|
116
|
+
def provision!(name:, size:, zone:, image:, ssh_public_key:)
|
117
|
+
transaction do |xac|
|
118
|
+
ssh_key_id = upload_ssh_key(file: ssh_public_key)
|
119
|
+
xac.rollback do
|
120
|
+
destroy_ssh_key!(ssh_key_id: ssh_key_id)
|
121
|
+
end
|
148
122
|
|
149
|
-
|
123
|
+
sizeid = fetch_mapped(name: :size, from: sizes_map, key: size)
|
124
|
+
imageid = fetch_mapped(name: :image, from: images_map, key: image)
|
125
|
+
zoneid = fetch_mapped(name: :zone, from: zones_map, key: zone)
|
126
|
+
|
127
|
+
r = Vultr::Server.create(DCID: zoneid,
|
128
|
+
OSID: imageid,
|
129
|
+
VPSPLANID: sizeid,
|
130
|
+
enable_ipv6: 'yes',
|
131
|
+
enable_private_network: 'yes',
|
132
|
+
label: name,
|
133
|
+
hostname: name,
|
134
|
+
SSHKEYID: ssh_key_id)
|
135
|
+
|
136
|
+
subid = r[:result]["SUBID"]
|
137
|
+
xac.rollback do
|
138
|
+
destroy!(id: subid)
|
139
|
+
end
|
150
140
|
|
151
|
-
rollback_on_error(id: subid) do
|
152
141
|
# Wait until it's active, it won't have an IP until then
|
153
142
|
backoff_loop do
|
154
143
|
r = Vultr::Server.list(SUBID: subid)[:result]
|
@@ -166,8 +155,8 @@ module Cult
|
|
166
155
|
size: size,
|
167
156
|
zone: zone,
|
168
157
|
image: image,
|
169
|
-
|
170
|
-
|
158
|
+
|
159
|
+
ssh_key_id: ssh_key_id,
|
171
160
|
|
172
161
|
id: subid,
|
173
162
|
created_at: Time.now.iso8601,
|