chef-provisioning-fog 0.26.1 → 0.26.3
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 +4 -4
- data/Gemfile +29 -8
- data/Rakefile +23 -12
- data/chef-provisioning-fog.gemspec +24 -27
- data/lib/chef/provider/fog_key_pair.rb +49 -53
- data/lib/chef/provider/scaleway_volume.rb +46 -48
- data/lib/chef/provisioning/driver_init/fog.rb +1 -1
- data/lib/chef/provisioning/fog_driver/driver.rb +646 -653
- data/lib/chef/provisioning/fog_driver/providers/aws.rb +411 -422
- data/lib/chef/provisioning/fog_driver/providers/aws/credentials.rb +88 -90
- data/lib/chef/provisioning/fog_driver/providers/cloudstack.rb +32 -34
- data/lib/chef/provisioning/fog_driver/providers/digitalocean.rb +98 -100
- data/lib/chef/provisioning/fog_driver/providers/google.rb +27 -34
- data/lib/chef/provisioning/fog_driver/providers/joyent.rb +53 -55
- data/lib/chef/provisioning/fog_driver/providers/openstack.rb +139 -146
- data/lib/chef/provisioning/fog_driver/providers/rackspace.rb +40 -44
- data/lib/chef/provisioning/fog_driver/providers/scaleway.rb +183 -189
- data/lib/chef/provisioning/fog_driver/providers/softlayer.rb +61 -64
- data/lib/chef/provisioning/fog_driver/providers/vcair.rb +72 -78
- data/lib/chef/provisioning/fog_driver/providers/xenserver.rb +56 -69
- data/lib/chef/provisioning/fog_driver/recipe_dsl.rb +11 -12
- data/lib/chef/provisioning/fog_driver/version.rb +1 -1
- data/lib/chef/resource/fog_key_pair.rb +8 -8
- data/lib/chef/resource/scaleway_volume.rb +8 -8
- data/spec/spec_helper.rb +7 -7
- data/spec/support/chef/provisioning/fog_driver/providers/testdriver.rb +3 -3
- data/spec/unit/chef/provisioning/fog_driver/driver_spec.rb +39 -38
- data/spec/unit/fog_driver_spec.rb +6 -8
- data/spec/unit/providers/aws/credentials_spec.rb +10 -10
- data/spec/unit/providers/rackspace_spec.rb +5 -6
- data/spec/unit/providers/scaleway_spec.rb +9 -9
- data/spec/unit/providers/softlayer.rb +7 -7
- metadata +6 -36
- data/README.md +0 -357
@@ -1,763 +1,756 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require
|
23
|
-
require
|
24
|
-
require
|
1
|
+
require "chef/provisioning"
|
2
|
+
require "chef/provisioning/fog_driver/recipe_dsl"
|
3
|
+
|
4
|
+
require "chef/provisioning/driver"
|
5
|
+
require "chef/provisioning/machine/windows_machine"
|
6
|
+
require "chef/provisioning/machine/unix_machine"
|
7
|
+
require "chef/provisioning/machine_spec"
|
8
|
+
require "chef/provisioning/convergence_strategy/install_msi"
|
9
|
+
require "chef/provisioning/convergence_strategy/install_sh"
|
10
|
+
require "chef/provisioning/convergence_strategy/install_cached"
|
11
|
+
require "chef/provisioning/convergence_strategy/no_converge"
|
12
|
+
require "chef/provisioning/transport/ssh"
|
13
|
+
require "chef/provisioning/transport/winrm"
|
14
|
+
require "chef/provisioning/fog_driver/version"
|
15
|
+
|
16
|
+
require "fog"
|
17
|
+
require "fog/core"
|
18
|
+
require "fog/compute"
|
19
|
+
require "socket"
|
20
|
+
require "etc"
|
21
|
+
require "time"
|
22
|
+
require "retryable"
|
23
|
+
require "cheffish/merged_config"
|
24
|
+
require "chef/provisioning/fog_driver/recipe_dsl"
|
25
25
|
|
26
26
|
class Chef
|
27
|
-
module Provisioning
|
28
|
-
module FogDriver
|
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
|
-
|
27
|
+
module Provisioning
|
28
|
+
module FogDriver
|
29
|
+
# Provisions cloud machines with the Fog driver.
|
30
|
+
#
|
31
|
+
# ## Fog Driver URLs
|
32
|
+
#
|
33
|
+
# All Chef Provisioning drivers use URLs to uniquely identify a driver's "bucket" of machines.
|
34
|
+
# Fog URLs are of the form fog:<provider>:<identifier:> - see individual providers
|
35
|
+
# for sample URLs.
|
36
|
+
#
|
37
|
+
# Identifier is generally something uniquely identifying the account. If multiple
|
38
|
+
# users can access the account, the identifier should be the same for all of
|
39
|
+
# them (do not use the username in these cases, use an account ID or auth server
|
40
|
+
# URL).
|
41
|
+
#
|
42
|
+
# In particular, the identifier should be specific enough that if you create a
|
43
|
+
# server with a driver with this URL, the server should be retrievable from
|
44
|
+
# the same URL *no matter what else changes*. For example, an AWS account ID
|
45
|
+
# is *not* enough for this--if you varied the region, you would no longer see
|
46
|
+
# your server in the list. Thus, AWS uses both the account ID and the region.
|
47
|
+
#
|
48
|
+
# ## Supporting a new Fog provider
|
49
|
+
#
|
50
|
+
# The Fog driver does not immediately support all Fog providers out of the box.
|
51
|
+
# Some minor work needs to be done to plug them into Chef.
|
52
|
+
#
|
53
|
+
# To add a new supported Fog provider, pick an appropriate identifier, go to
|
54
|
+
# from_provider and compute_options_for, and add the new provider in the case
|
55
|
+
# statements so that URLs for your Fog provider can be generated. If your
|
56
|
+
# cloud provider has environment variables or standard config files (like
|
57
|
+
# ~/.aws/credentials or ~/.aws/config), you can read those and merge that information
|
58
|
+
# in the compute_options_for function.
|
59
|
+
#
|
60
|
+
# ## Reference format
|
61
|
+
#
|
62
|
+
# All machines have a reference hash to find them. These are the keys used by
|
63
|
+
# the Fog provisioner:
|
64
|
+
#
|
65
|
+
# - driver_url: fog:<driver>:<unique_account_info>
|
66
|
+
# - server_id: the ID of the server so it can be found again
|
67
|
+
# - created_at: timestamp server was created
|
68
|
+
# - started_at: timestamp server was last started
|
69
|
+
# - is_windows, ssh_username, sudo: copied from machine_options
|
70
|
+
#
|
71
|
+
# ## Machine options
|
72
|
+
#
|
73
|
+
# Machine options (for allocation and readying the machine) include:
|
74
|
+
#
|
75
|
+
# - bootstrap_options: hash of options to pass to compute.servers.create
|
76
|
+
# - is_windows: true if windows. TODO detect this from ami?
|
77
|
+
# - create_timeout: the time to wait for the instance to boot to ssh (defaults to 180)
|
78
|
+
# - start_timeout: the time to wait for the instance to start (defaults to 180)
|
79
|
+
# - ssh_timeout: the time to wait for ssh to be available if the instance is detected as up (defaults to 20)
|
80
|
+
# - ssh_username: username to use for ssh
|
81
|
+
# - sudo: true to prefix all commands with "sudo"
|
82
|
+
# - transport_address_location: ssh into machine via `:public_ip`, `:private_ip`, or `:ip_addresses`
|
83
|
+
# - use_private_ip_for_ssh: (DEPRECATED and is replaced with `transport_address_location`) hint to use private floating_ip when available
|
84
|
+
# - convergence_options: hash of options for the convergence strategy
|
85
|
+
# - chef_client_timeout: the time to wait for chef-client to finish
|
86
|
+
# - chef_server - the chef server to point convergence at
|
87
|
+
#
|
88
|
+
# Example bootstrap_options for ec2:
|
89
|
+
#
|
90
|
+
# :bootstrap_options => {
|
91
|
+
# :image_id =>'ami-311f2b45',
|
92
|
+
# :flavor_id =>'t1.micro',
|
93
|
+
# :key_name => 'key-pair-name'
|
94
|
+
# }
|
95
|
+
#
|
96
|
+
class Driver < Provisioning::Driver
|
97
|
+
@@ip_pool_lock = Mutex.new
|
98
|
+
|
99
|
+
include Chef::Mixin::ShellOut
|
100
|
+
|
101
|
+
DEFAULT_OPTIONS = {
|
102
|
+
create_timeout: 180,
|
103
|
+
start_timeout: 180,
|
104
|
+
ssh_timeout: 20
|
105
|
+
}.freeze
|
106
|
+
|
107
|
+
RETRYABLE_ERRORS = [Fog::Compute::AWS::Error].freeze
|
108
|
+
RETRYABLE_OPTIONS = { tries: 12, sleep: 5, on: RETRYABLE_ERRORS }.freeze
|
109
|
+
|
110
|
+
class << self
|
111
|
+
alias __new__ new
|
112
|
+
|
113
|
+
def inherited(klass)
|
114
|
+
class << klass
|
115
|
+
alias_method :new, :__new__
|
116
|
+
end
|
117
|
+
end
|
116
118
|
end
|
117
|
-
end
|
118
|
-
end
|
119
119
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
120
|
+
@@registered_provider_classes = {}
|
121
|
+
def self.register_provider_class(name, driver)
|
122
|
+
@@registered_provider_classes[name] = driver
|
123
|
+
end
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
125
|
+
def self.provider_class_for(provider)
|
126
|
+
require "chef/provisioning/fog_driver/providers/#{provider.downcase}"
|
127
|
+
@@registered_provider_classes[provider]
|
128
|
+
end
|
129
129
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
130
|
+
def self.new(driver_url, config)
|
131
|
+
provider = driver_url.split(":")[1]
|
132
|
+
provider_class_for(provider).new(driver_url, config)
|
133
|
+
end
|
134
134
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
135
|
+
# Passed in a driver_url, and a config in the format of Driver.config.
|
136
|
+
def self.from_url(driver_url, config)
|
137
|
+
Driver.new(driver_url, config)
|
138
|
+
end
|
139
139
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
140
|
+
def self.canonicalize_url(driver_url, config)
|
141
|
+
_, provider, id = driver_url.split(":", 3)
|
142
|
+
config, id = provider_class_for(provider).compute_options_for(provider, id, config)
|
143
|
+
["fog:#{provider}:#{id}", config]
|
144
|
+
end
|
145
145
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
146
|
+
# Passed in a config which is *not* merged with driver_url (because we don't
|
147
|
+
# know what it is yet) but which has the same keys
|
148
|
+
def self.from_provider(provider, config)
|
149
|
+
# Figure out the options and merge them into the config
|
150
|
+
config, id = provider_class_for(provider).compute_options_for(provider, nil, config)
|
151
151
|
|
152
|
-
|
152
|
+
driver_url = "fog:#{provider}:#{id}"
|
153
153
|
|
154
|
-
|
155
|
-
|
154
|
+
Provisioning.driver_for_url(driver_url, config)
|
155
|
+
end
|
156
156
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
157
|
+
# Create a new Fog driver.
|
158
|
+
#
|
159
|
+
# ## Parameters
|
160
|
+
# driver_url - URL of driver. "fog:<provider>:<provider_id>"
|
161
|
+
# config - configuration. :driver_options, :keys, :key_paths and :log_level are used.
|
162
|
+
# driver_options is a hash with these possible options:
|
163
|
+
# - compute_options: the hash of options to Fog::Compute.new.
|
164
|
+
# - aws_config_file: aws config file (defaults: ~/.aws/credentials, ~/.aws/config)
|
165
|
+
# - aws_csv_file: aws csv credentials file downloaded from EC2 interface
|
166
|
+
# - aws_profile: profile name to use for credentials
|
167
|
+
# - aws_credentials: AWSCredentials object. (will be created for you by default)
|
168
|
+
# - log_level: :debug, :info, :warn, :error
|
169
|
+
def initialize(driver_url, config)
|
170
|
+
super(driver_url, config)
|
171
|
+
if config[:log_level] == :debug
|
172
|
+
Fog::Logger[:debug] = ::STDERR
|
173
|
+
Excon.defaults[:debug_request] = true
|
174
|
+
Excon.defaults[:debug_response] = true
|
175
|
+
end
|
176
|
+
end
|
177
177
|
|
178
|
-
|
179
|
-
|
180
|
-
|
178
|
+
def compute_options
|
179
|
+
JSON.parse(driver_options[:compute_options].to_h.to_json, symbolize_names: true) || {}
|
180
|
+
end
|
181
181
|
|
182
|
-
|
183
|
-
|
184
|
-
|
182
|
+
def provider
|
183
|
+
compute_options[:provider]
|
184
|
+
end
|
185
185
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
186
|
+
# Acquire a machine, generally by provisioning it. Returns a Machine
|
187
|
+
# object pointing at the machine, allowing useful actions like setup,
|
188
|
+
# converge, execute, file and directory.
|
189
|
+
def allocate_machine(action_handler, machine_spec, machine_options)
|
190
|
+
# If the server does not exist, create it
|
191
|
+
create_servers(action_handler, { machine_spec => machine_options }, Chef::ChefFS::Parallelizer.new(0))
|
192
|
+
machine_spec
|
193
|
+
end
|
194
194
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
195
|
+
def allocate_machines(action_handler, specs_and_options, parallelizer)
|
196
|
+
create_servers(action_handler, specs_and_options, parallelizer) do |machine_spec, _server|
|
197
|
+
yield machine_spec
|
198
|
+
end
|
199
|
+
specs_and_options.keys
|
200
|
+
end
|
201
201
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
202
|
+
def ready_machine(action_handler, machine_spec, machine_options)
|
203
|
+
server = server_for(machine_spec)
|
204
|
+
if server.nil?
|
205
|
+
raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
|
206
|
+
end
|
207
207
|
|
208
|
-
|
209
|
-
|
210
|
-
wait_until_ready(action_handler, machine_spec, machine_options, server)
|
211
|
-
|
212
|
-
converge_floating_ips(action_handler, machine_spec, machine_options, server)
|
213
|
-
|
214
|
-
begin
|
215
|
-
wait_for_transport(action_handler, machine_spec, machine_options, server)
|
216
|
-
rescue Fog::Errors::TimeoutError
|
217
|
-
# Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
|
218
|
-
if machine_spec.reference['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
|
219
|
-
raise
|
220
|
-
else
|
221
|
-
# Sometimes (on EC2) the machine comes up but gets stuck or has
|
222
|
-
# some other problem. If this is the case, we restart the server
|
223
|
-
# to unstick it. Reboot covers a multitude of sins.
|
224
|
-
Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
|
225
|
-
restart_server(action_handler, machine_spec, server)
|
208
|
+
# Start the server if needed, and wait for it to start
|
209
|
+
start_server(action_handler, machine_spec, server)
|
226
210
|
wait_until_ready(action_handler, machine_spec, machine_options, server)
|
227
|
-
wait_for_transport(action_handler, machine_spec, machine_options, server)
|
228
|
-
end
|
229
|
-
end
|
230
211
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
212
|
+
converge_floating_ips(action_handler, machine_spec, machine_options, server)
|
213
|
+
|
214
|
+
begin
|
215
|
+
wait_for_transport(action_handler, machine_spec, machine_options, server)
|
216
|
+
rescue Fog::Errors::TimeoutError
|
217
|
+
# Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
|
218
|
+
if machine_spec.reference["started_at"] || remaining_wait_time(machine_spec, machine_options) < -(10 * 60)
|
219
|
+
raise
|
220
|
+
else
|
221
|
+
# Sometimes (on EC2) the machine comes up but gets stuck or has
|
222
|
+
# some other problem. If this is the case, we restart the server
|
223
|
+
# to unstick it. Reboot covers a multitude of sins.
|
224
|
+
Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
|
225
|
+
restart_server(action_handler, machine_spec, server)
|
226
|
+
wait_until_ready(action_handler, machine_spec, machine_options, server)
|
227
|
+
wait_for_transport(action_handler, machine_spec, machine_options, server)
|
228
|
+
end
|
229
|
+
end
|
238
230
|
|
239
|
-
|
240
|
-
server = server_for(machine_spec)
|
241
|
-
if server
|
242
|
-
action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.reference['server_id']} at #{driver_url})" do
|
243
|
-
server.destroy
|
244
|
-
machine_spec.reference = nil
|
231
|
+
machine_for(machine_spec, machine_options, server)
|
245
232
|
end
|
246
|
-
end
|
247
|
-
strategy = ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
|
248
|
-
strategy.cleanup_convergence(action_handler, machine_spec)
|
249
|
-
end
|
250
233
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
action_handler.perform_action "stop machine #{machine_spec.name} (#{server.id} at #{driver_url})" do
|
255
|
-
server.stop
|
234
|
+
# Connect to machine without acquiring it
|
235
|
+
def connect_to_machine(machine_spec, machine_options)
|
236
|
+
machine_for(machine_spec, machine_options)
|
256
237
|
end
|
257
|
-
end
|
258
|
-
end
|
259
238
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
239
|
+
def destroy_machine(action_handler, machine_spec, machine_options)
|
240
|
+
server = server_for(machine_spec)
|
241
|
+
if server
|
242
|
+
action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.reference['server_id']} at #{driver_url})" do
|
243
|
+
server.destroy
|
244
|
+
machine_spec.reference = nil
|
245
|
+
end
|
246
|
+
end
|
247
|
+
strategy = ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
|
248
|
+
strategy.cleanup_convergence(action_handler, machine_spec)
|
249
|
+
end
|
267
250
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
transport
|
277
|
-
else
|
278
|
-
create_ssh_transport(machine_spec, machine_options, server)
|
279
|
-
end
|
280
|
-
end
|
251
|
+
def stop_machine(action_handler, machine_spec, _machine_options)
|
252
|
+
server = server_for(machine_spec)
|
253
|
+
if server
|
254
|
+
action_handler.perform_action "stop machine #{machine_spec.name} (#{server.id} at #{driver_url})" do
|
255
|
+
server.stop
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
281
259
|
|
282
|
-
|
283
|
-
|
284
|
-
|
260
|
+
def image_for(image_spec)
|
261
|
+
compute.images.get(image_spec.reference["image_id"])
|
262
|
+
end
|
285
263
|
|
286
|
-
|
287
|
-
|
288
|
-
|
264
|
+
def compute
|
265
|
+
@compute ||= Fog::Compute.new(compute_options)
|
266
|
+
end
|
289
267
|
|
290
|
-
|
268
|
+
# Not meant to be part of public interface
|
269
|
+
def transport_for(machine_spec, machine_options, server, action_handler = nil)
|
270
|
+
Chef::Log.debug("Creating transport for #{server}")
|
271
|
+
Chef::Log.debug("Machine Spec: #{machine_spec}")
|
272
|
+
if machine_spec.reference["is_windows"]
|
273
|
+
action_handler.report_progress "Waiting for admin password on #{machine_spec.name} to be ready (may take up to 15 minutes)..." if action_handler
|
274
|
+
transport = create_winrm_transport(machine_spec, machine_options, server)
|
275
|
+
action_handler.report_progress "Admin password available ..." if action_handler
|
276
|
+
transport
|
277
|
+
else
|
278
|
+
create_ssh_transport(machine_spec, machine_options, server)
|
279
|
+
end
|
280
|
+
end
|
291
281
|
|
292
|
-
|
293
|
-
|
294
|
-
|
282
|
+
def create_volume(_action_handler, _volume_spec, _volume_options)
|
283
|
+
raise "##create_volume not implemented in in #{self.class}"
|
284
|
+
end
|
295
285
|
|
296
|
-
|
297
|
-
|
298
|
-
|
286
|
+
def destroy_volume(_action_handler, _volume_spec, _volume_options)
|
287
|
+
raise "##destroy_volume not implemented in in #{self.class}"
|
288
|
+
end
|
299
289
|
|
300
|
-
|
301
|
-
specs_and_servers = servers_for(specs_and_options)
|
290
|
+
protected
|
302
291
|
|
303
|
-
|
304
|
-
|
305
|
-
# with create_many)
|
306
|
-
by_bootstrap_options = {}
|
307
|
-
specs_and_options.each do |machine_spec, machine_options|
|
308
|
-
server = specs_and_servers[machine_spec]
|
309
|
-
if server
|
310
|
-
server_state = server.respond_to?(:status) ? server.status : server.state
|
311
|
-
if %w(terminated archive DELETED).include?(server_state.downcase) # Can't come back from that
|
312
|
-
Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) is terminated. Recreating ..."
|
313
|
-
else
|
314
|
-
yield machine_spec, server if block_given?
|
315
|
-
next
|
316
|
-
end
|
317
|
-
elsif machine_spec.reference
|
318
|
-
Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.reference['server_id']} on #{driver_url}) no longer exists. Recreating ..."
|
292
|
+
def option_for(machine_options, key)
|
293
|
+
machine_options[:bootstrap_options][key] || DEFAULT_OPTIONS[key]
|
319
294
|
end
|
320
295
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
'driver_version' => FogDriver::VERSION,
|
325
|
-
'creator' => creator,
|
326
|
-
'allocated_at' => Time.now.to_i
|
327
|
-
)
|
296
|
+
def creator
|
297
|
+
raise "unsupported Fog provider #{provider} (please implement #creator)"
|
298
|
+
end
|
328
299
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
300
|
+
def create_servers(action_handler, specs_and_options, parallelizer)
|
301
|
+
specs_and_servers = servers_for(specs_and_options)
|
302
|
+
|
303
|
+
# Get the list of servers which exist, segmented by their bootstrap options
|
304
|
+
# (we will try to create a set of servers for each set of bootstrap options
|
305
|
+
# with create_many)
|
306
|
+
by_bootstrap_options = {}
|
307
|
+
specs_and_options.each do |machine_spec, machine_options|
|
308
|
+
server = specs_and_servers[machine_spec]
|
309
|
+
if server
|
310
|
+
server_state = server.respond_to?(:status) ? server.status : server.state
|
311
|
+
if %w{terminated archive DELETED}.include?(server_state.downcase) # Can't come back from that
|
312
|
+
Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) is terminated. Recreating ..."
|
313
|
+
else
|
314
|
+
yield machine_spec, server if block_given?
|
315
|
+
next
|
316
|
+
end
|
317
|
+
elsif machine_spec.reference
|
318
|
+
Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.reference['server_id']} on #{driver_url}) no longer exists. Recreating ..."
|
319
|
+
end
|
333
320
|
|
334
|
-
|
335
|
-
|
336
|
-
|
321
|
+
machine_spec.reference ||= {}
|
322
|
+
machine_spec.reference.update(
|
323
|
+
"driver_url" => driver_url,
|
324
|
+
"driver_version" => FogDriver::VERSION,
|
325
|
+
"creator" => creator,
|
326
|
+
"allocated_at" => Time.now.to_i
|
327
|
+
)
|
328
|
+
|
329
|
+
bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
|
330
|
+
machine_spec.reference["key_name"] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
|
331
|
+
by_bootstrap_options[bootstrap_options] ||= []
|
332
|
+
by_bootstrap_options[bootstrap_options] << machine_spec
|
333
|
+
|
334
|
+
# TODO: 2.0 We no longer support `use_private_ip_for_ssh`, only `transport_address_location
|
335
|
+
if machine_options[:use_private_ip_for_ssh]
|
336
|
+
unless @transport_address_location_warned
|
337
337
|
Chef::Log.warn("The machine option ':use_private_ip_for_ssh' has been deprecated, use ':transport_address_location'")
|
338
338
|
@transport_address_location_warned = true
|
339
|
+
end
|
340
|
+
machine_options = Cheffish::MergedConfig.new(machine_options, transport_address_location: :private_ip)
|
339
341
|
end
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
end
|
345
|
-
end
|
342
|
+
%w{is_windows ssh_username sudo transport_address_location ssh_gateway}.each do |key|
|
343
|
+
machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
|
344
|
+
end
|
345
|
+
end
|
346
346
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
347
|
+
# Create the servers in parallel
|
348
|
+
parallelizer.parallelize(by_bootstrap_options) do |bootstrap_options, machine_specs|
|
349
|
+
machine_description = if machine_specs.size == 1
|
350
|
+
"machine #{machine_specs.first.name}"
|
351
|
+
else
|
352
|
+
"machines #{machine_specs.map(&:name).join(', ')}"
|
353
|
+
end
|
354
|
+
description = ["creating #{machine_description} on #{driver_url}"]
|
355
|
+
bootstrap_options.each_pair { |key, value| description << " #{key}: #{value.inspect}" }
|
356
|
+
action_handler.report_progress description
|
357
|
+
if action_handler.should_perform_actions
|
358
|
+
# Actually create the servers
|
359
|
+
create_many_servers(machine_specs.size, bootstrap_options, parallelizer) do |server|
|
360
|
+
# Assign each one to a machine spec
|
361
|
+
machine_spec = machine_specs.pop
|
362
|
+
machine_options = specs_and_options[machine_spec]
|
363
|
+
machine_spec.reference["server_id"] = server.id
|
364
|
+
|
365
|
+
action_handler.performed_action "machine #{machine_spec.name} created as #{server.id} on #{driver_url}"
|
366
|
+
|
367
|
+
yield machine_spec, server if block_given?
|
368
|
+
end
|
369
|
+
|
370
|
+
unless machine_specs.empty?
|
371
|
+
raise "Not all machines were created by create_many_servers!"
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end.to_a
|
353
375
|
end
|
354
|
-
description = [ "creating #{machine_description} on #{driver_url}" ]
|
355
|
-
bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
|
356
|
-
action_handler.report_progress description
|
357
|
-
if action_handler.should_perform_actions
|
358
|
-
# Actually create the servers
|
359
|
-
create_many_servers(machine_specs.size, bootstrap_options, parallelizer) do |server|
|
360
376
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
machine_spec.reference['server_id'] = server.id
|
377
|
+
def create_many_servers(num_servers, bootstrap_options, parallelizer)
|
378
|
+
parallelizer.parallelize(1.upto(num_servers)) do |_i|
|
379
|
+
clean_bootstrap_options = Marshal.load(Marshal.dump(bootstrap_options)) # Prevent destructive operations on bootstrap_options.
|
365
380
|
|
366
|
-
|
381
|
+
server = compute.servers.create(clean_bootstrap_options)
|
382
|
+
yield server if block_given?
|
383
|
+
server
|
384
|
+
end.to_a
|
385
|
+
end
|
367
386
|
|
368
|
-
|
387
|
+
def start_server(action_handler, machine_spec, server)
|
388
|
+
# If it is stopping, wait for it to get out of "stopping" transition status before starting
|
389
|
+
server_state = server.respond_to?(:status) ? server.status : server.state
|
390
|
+
if server_state == "stopping"
|
391
|
+
action_handler.report_progress "wait for #{machine_spec.name} (#{server.id} on #{driver_url}) to finish stopping ..."
|
392
|
+
server.wait_for { server_state != "stopping" }
|
393
|
+
action_handler.report_progress "#{machine_spec.name} is now stopped"
|
369
394
|
end
|
370
|
-
|
371
|
-
|
372
|
-
|
395
|
+
if server_state == "stopped"
|
396
|
+
action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
|
397
|
+
server.start
|
398
|
+
machine_spec.reference["started_at"] = Time.now.to_i
|
399
|
+
end
|
400
|
+
machine_spec.save(action_handler)
|
373
401
|
end
|
374
402
|
end
|
375
|
-
end.to_a
|
376
|
-
end
|
377
403
|
|
378
|
-
|
379
|
-
|
380
|
-
|
404
|
+
def restart_server(action_handler, machine_spec, server)
|
405
|
+
action_handler.perform_action "restart machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
|
406
|
+
server.reboot
|
407
|
+
machine_spec.reference["started_at"] = Time.now.to_i
|
408
|
+
end
|
409
|
+
machine_spec.save(action_handler)
|
410
|
+
end
|
381
411
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
412
|
+
def remaining_wait_time(machine_spec, machine_options)
|
413
|
+
if machine_spec.reference["started_at"]
|
414
|
+
timeout = option_for(machine_options, :start_timeout) - (Time.now.utc - parse_time(machine_spec.reference["started_at"]))
|
415
|
+
else
|
416
|
+
timeout = option_for(machine_options, :create_timeout) - (Time.now.utc - parse_time(machine_spec.reference["allocated_at"]))
|
417
|
+
end
|
418
|
+
timeout > 0 ? timeout : 0.01
|
419
|
+
end
|
387
420
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
action_handler.report_progress "#{machine_spec.name} is now stopped"
|
395
|
-
end
|
396
|
-
if server_state == 'stopped'
|
397
|
-
action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
|
398
|
-
server.start
|
399
|
-
machine_spec.reference['started_at'] = Time.now.to_i
|
421
|
+
def parse_time(value)
|
422
|
+
if value.is_a?(String)
|
423
|
+
Time.parse(value)
|
424
|
+
else
|
425
|
+
Time.at(value)
|
426
|
+
end
|
400
427
|
end
|
401
|
-
machine_spec.save(action_handler)
|
402
|
-
end
|
403
|
-
end
|
404
428
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
429
|
+
def wait_until_ready(action_handler, machine_spec, machine_options, server)
|
430
|
+
unless server.ready?
|
431
|
+
if action_handler.should_perform_actions
|
432
|
+
Retryable.retryable(RETRYABLE_OPTIONS) do |retries, _exception|
|
433
|
+
action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be ready, API attempt #{retries + 1}/#{RETRYABLE_OPTIONS[:tries]} ..."
|
434
|
+
server.wait_for(remaining_wait_time(machine_spec, machine_options)) { ready? }
|
435
|
+
end
|
436
|
+
action_handler.report_progress "#{machine_spec.name} is now ready"
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
412
440
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
timeout > 0 ? timeout : 0.01
|
420
|
-
end
|
441
|
+
def wait_for_transport(action_handler, machine_spec, machine_options, server)
|
442
|
+
transport = transport_for(machine_spec, machine_options, server, action_handler)
|
443
|
+
unless transport.available?
|
444
|
+
if action_handler.should_perform_actions
|
445
|
+
Retryable.retryable(RETRYABLE_OPTIONS) do |retries, _exception|
|
446
|
+
action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be connectable (transport up and running), API attempt #{retries + 1}/#{RETRYABLE_OPTIONS[:tries]} ..."
|
421
447
|
|
422
|
-
|
423
|
-
if value.is_a?(String)
|
424
|
-
Time.parse(value)
|
425
|
-
else
|
426
|
-
Time.at(value)
|
427
|
-
end
|
428
|
-
end
|
448
|
+
_self = self
|
429
449
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
450
|
+
server.wait_for(remaining_wait_time(machine_spec, machine_options)) do
|
451
|
+
transport.available?
|
452
|
+
end
|
453
|
+
end
|
454
|
+
action_handler.report_progress "#{machine_spec.name} is now connectable"
|
455
|
+
end
|
436
456
|
end
|
437
|
-
action_handler.report_progress "#{machine_spec.name} is now ready"
|
438
457
|
end
|
439
|
-
end
|
440
|
-
end
|
441
458
|
|
442
|
-
|
459
|
+
def converge_floating_ips(action_handler, _machine_spec, machine_options, server)
|
460
|
+
pool = option_for(machine_options, :floating_ip_pool)
|
461
|
+
floating_ip = option_for(machine_options, :floating_ip)
|
462
|
+
attached_floating_ips = find_floating_ips(server, action_handler)
|
463
|
+
if pool
|
464
|
+
|
465
|
+
Chef::Log.debug "Attaching IP from pool #{pool}"
|
466
|
+
if !attached_floating_ips.empty?
|
467
|
+
Chef::Log.info "Server already assigned attached_floating_ips `#{attached_floating_ips}`"
|
468
|
+
elsif
|
469
|
+
action_handler.perform_action "Attaching floating IP from pool `#{pool}`" do
|
470
|
+
attach_ip_from_pool(server, pool)
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
elsif floating_ip
|
443
475
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
476
|
+
Chef::Log.debug "Attaching given IP #{floating_ip}"
|
477
|
+
if attached_floating_ips.include? floating_ip
|
478
|
+
Chef::Log.info "Address <#{floating_ip}> already allocated"
|
479
|
+
else
|
480
|
+
action_handler.perform_action "Attaching floating IP #{floating_ip}" do
|
481
|
+
attach_ip(server, floating_ip)
|
482
|
+
end
|
483
|
+
end
|
449
484
|
|
450
|
-
|
485
|
+
elsif !attached_floating_ips.empty?
|
451
486
|
|
452
|
-
|
453
|
-
|
487
|
+
# If nothing is assigned, lets remove any floating IPs
|
488
|
+
Chef::Log.debug "Missing :floating_ip_pool or :floating_ip, removing attached floating IPs"
|
489
|
+
action_handler.perform_action "Removing floating IPs #{attached_floating_ips}" do
|
490
|
+
attached_floating_ips.each do |ip|
|
491
|
+
server.disassociate_address(ip)
|
492
|
+
end
|
493
|
+
server.reload
|
454
494
|
end
|
455
495
|
end
|
456
|
-
action_handler.report_progress "#{machine_spec.name} is now connectable"
|
457
496
|
end
|
458
|
-
end
|
459
|
-
end
|
460
497
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
498
|
+
# Find all attached floating IPs from all networks
|
499
|
+
def find_floating_ips(server, action_handler)
|
500
|
+
floating_ips = []
|
501
|
+
Retryable.retryable(RETRYABLE_OPTIONS) do |retries, _exception|
|
502
|
+
action_handler.report_progress "Querying for floating IPs attached to server #{server.id}, API attempt #{retries + 1}/#{RETRYABLE_OPTIONS[:tries]} ..."
|
503
|
+
server.addresses.each do |_network, addrs|
|
504
|
+
addrs.each do |full_addr|
|
505
|
+
if full_addr["OS-EXT-IPS:type"] == "floating"
|
506
|
+
floating_ips << full_addr["addr"]
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
473
510
|
end
|
511
|
+
floating_ips
|
474
512
|
end
|
475
513
|
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
514
|
+
# Attach IP to machine from IP pool
|
515
|
+
# Code taken from kitchen-openstack driver
|
516
|
+
# https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb
|
517
|
+
def attach_ip_from_pool(server, pool)
|
518
|
+
@@ip_pool_lock.synchronize do
|
519
|
+
Chef::Log.info "Attaching floating IP from <#{pool}> pool"
|
520
|
+
free_addrs = compute.addresses.map do |i|
|
521
|
+
i.ip if i.fixed_ip.nil? && i.instance_id.nil? && i.pool == pool
|
522
|
+
end.compact
|
523
|
+
raise "No available IPs in pool <#{pool}>" if free_addrs.empty?
|
524
|
+
attach_ip(server, free_addrs[0])
|
484
525
|
end
|
485
526
|
end
|
486
527
|
|
487
|
-
|
488
|
-
|
489
|
-
#
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
server.disassociate_address(ip)
|
494
|
-
end
|
528
|
+
# Attach given IP to machine, assign it as public
|
529
|
+
# Code taken from kitchen-openstack driver
|
530
|
+
# https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb
|
531
|
+
def attach_ip(server, ip)
|
532
|
+
Chef::Log.info "Attaching floating IP <#{ip}>"
|
533
|
+
server.associate_address ip
|
495
534
|
server.reload
|
496
535
|
end
|
497
|
-
end
|
498
|
-
end
|
499
536
|
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
Retryable.retryable(RETRYABLE_OPTIONS) do |retries,exception|
|
504
|
-
action_handler.report_progress "Querying for floating IPs attached to server #{server.id}, API attempt #{retries+1}/#{RETRYABLE_OPTIONS[:tries]} ..."
|
505
|
-
server.addresses.each do |network, addrs|
|
506
|
-
addrs.each do | full_addr |
|
507
|
-
if full_addr['OS-EXT-IPS:type'] == 'floating'
|
508
|
-
floating_ips << full_addr['addr']
|
509
|
-
end
|
537
|
+
def symbolize_keys(options)
|
538
|
+
options.each_with_object({}) do |(key, value), result|
|
539
|
+
result[key.to_sym] = value
|
510
540
|
end
|
511
541
|
end
|
512
|
-
end
|
513
|
-
floating_ips
|
514
|
-
end
|
515
542
|
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
Chef::Log.info "Attaching floating IP from <#{pool}> pool"
|
522
|
-
free_addrs = compute.addresses.map do |i|
|
523
|
-
i.ip if i.fixed_ip.nil? && i.instance_id.nil? && i.pool == pool
|
524
|
-
end.compact
|
525
|
-
if free_addrs.empty?
|
526
|
-
raise RuntimeError, "No available IPs in pool <#{pool}>"
|
527
|
-
end
|
528
|
-
attach_ip(server, free_addrs[0])
|
529
|
-
end
|
530
|
-
end
|
531
|
-
|
532
|
-
# Attach given IP to machine, assign it as public
|
533
|
-
# Code taken from kitchen-openstack driver
|
534
|
-
# https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb
|
535
|
-
def attach_ip(server, ip)
|
536
|
-
Chef::Log.info "Attaching floating IP <#{ip}>"
|
537
|
-
server.associate_address ip
|
538
|
-
server.reload
|
539
|
-
end
|
540
|
-
|
541
|
-
def symbolize_keys(options)
|
542
|
-
options.inject({}) do |result,(key,value)|
|
543
|
-
result[key.to_sym] = value
|
544
|
-
result
|
545
|
-
end
|
546
|
-
end
|
547
|
-
|
548
|
-
def server_for(machine_spec)
|
549
|
-
if machine_spec.reference
|
550
|
-
compute.servers.get(machine_spec.reference['server_id'])
|
551
|
-
else
|
552
|
-
nil
|
553
|
-
end
|
554
|
-
end
|
543
|
+
def server_for(machine_spec)
|
544
|
+
if machine_spec.reference
|
545
|
+
compute.servers.get(machine_spec.reference["server_id"])
|
546
|
+
end
|
547
|
+
end
|
555
548
|
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
549
|
+
def servers_for(specs_and_options)
|
550
|
+
result = {}
|
551
|
+
specs_and_options.each do |machine_spec, _machine_options|
|
552
|
+
if machine_spec.reference
|
553
|
+
if machine_spec.reference["driver_url"] != driver_url
|
554
|
+
raise "Switching a machine's driver from #{machine_spec.reference['driver_url']} to #{driver_url} for is not currently supported! Use machine :destroy and then re-create the machine on the new driver."
|
555
|
+
end
|
556
|
+
result[machine_spec] = compute.servers.get(machine_spec.reference["server_id"])
|
557
|
+
else
|
558
|
+
result[machine_spec] = nil
|
559
|
+
end
|
562
560
|
end
|
563
|
-
result
|
564
|
-
else
|
565
|
-
result[machine_spec] = nil
|
561
|
+
result
|
566
562
|
end
|
567
|
-
end
|
568
|
-
result
|
569
|
-
end
|
570
563
|
|
571
|
-
|
564
|
+
@@chef_default_lock = Mutex.new
|
572
565
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
566
|
+
def overwrite_default_key_willy_nilly(action_handler, machine_spec)
|
567
|
+
if machine_spec.reference &&
|
568
|
+
Gem::Version.new(machine_spec.reference["driver_version"]) < Gem::Version.new("0.10")
|
569
|
+
return "metal_default"
|
570
|
+
end
|
578
571
|
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
572
|
+
driver = self
|
573
|
+
updated = @@chef_default_lock.synchronize do
|
574
|
+
Provisioning.inline_resource(action_handler) do
|
575
|
+
fog_key_pair "chef_default" do
|
576
|
+
driver driver
|
577
|
+
allow_overwrite true
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
if updated
|
582
|
+
# Only warn the first time
|
583
|
+
Chef::Log.warn("Using chef_default key, which is not shared between machines! It is recommended to create an AWS key pair with the fog_key_pair resource, and set :bootstrap_options => { :key_name => <key name> }")
|
585
584
|
end
|
585
|
+
"chef_default"
|
586
586
|
end
|
587
|
-
end
|
588
|
-
if updated
|
589
|
-
# Only warn the first time
|
590
|
-
Chef::Log.warn("Using chef_default key, which is not shared between machines! It is recommended to create an AWS key pair with the fog_key_pair resource, and set :bootstrap_options => { :key_name => <key name> }")
|
591
|
-
end
|
592
|
-
'chef_default'
|
593
|
-
end
|
594
587
|
|
595
|
-
|
596
|
-
|
588
|
+
def bootstrap_options_for(machine_spec, machine_options)
|
589
|
+
bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
|
597
590
|
|
598
|
-
|
591
|
+
bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
|
599
592
|
|
600
|
-
|
593
|
+
bootstrap_options[:name] ||= machine_spec.name
|
601
594
|
|
602
|
-
|
603
|
-
|
595
|
+
bootstrap_options
|
596
|
+
end
|
604
597
|
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
598
|
+
def default_tags(machine_spec, bootstrap_tags = {})
|
599
|
+
tags = {
|
600
|
+
"Name" => machine_spec.name,
|
601
|
+
"BootstrapId" => machine_spec.id,
|
602
|
+
"BootstrapHost" => Socket.gethostname,
|
603
|
+
"BootstrapUser" => Etc.getlogin
|
604
|
+
}
|
605
|
+
# User-defined tags override the ones we set
|
606
|
+
tags.merge(bootstrap_tags)
|
607
|
+
end
|
615
608
|
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
609
|
+
def machine_for(machine_spec, machine_options, server = nil)
|
610
|
+
server ||= server_for(machine_spec)
|
611
|
+
unless server
|
612
|
+
raise "Server for node #{machine_spec.name} has not been created!"
|
613
|
+
end
|
621
614
|
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
615
|
+
if machine_spec.reference["is_windows"]
|
616
|
+
Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, server), convergence_strategy_for(machine_spec, machine_options))
|
617
|
+
else
|
618
|
+
Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, server), convergence_strategy_for(machine_spec, machine_options))
|
619
|
+
end
|
620
|
+
end
|
628
621
|
|
629
|
-
|
630
|
-
|
631
|
-
|
622
|
+
def volume_for(_volume_spec)
|
623
|
+
raise "Immplement me in #{self.class.name}"
|
624
|
+
end
|
632
625
|
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
626
|
+
def convergence_strategy_for(machine_spec, machine_options)
|
627
|
+
# Defaults
|
628
|
+
unless machine_spec.reference
|
629
|
+
return ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
|
630
|
+
end
|
638
631
|
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
632
|
+
if machine_spec.reference["is_windows"]
|
633
|
+
ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
|
634
|
+
elsif machine_options[:cached_installer] == true
|
635
|
+
ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
|
636
|
+
else
|
637
|
+
ConvergenceStrategy::InstallSh.new(machine_options[:convergence_options], config)
|
638
|
+
end
|
639
|
+
end
|
647
640
|
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
641
|
+
# Get the private key for a machine - prioritize the server data, fall back to the
|
642
|
+
# the machine spec data, and if that doesn't work, raise an exception.
|
643
|
+
# @param [Hash] machine_spec Machine spec data
|
644
|
+
# @param [Hash] machine_options Machine options
|
645
|
+
# @param [Chef::Provisioning::Machine] server a Machine representing the server
|
646
|
+
# @return [String] PEM-encoded private key
|
647
|
+
def private_key_for(machine_spec, machine_options, server)
|
648
|
+
bootstrap_options = machine_options[:bootstrap_options] || {}
|
649
|
+
if server.respond_to?(:private_key) && server.private_key
|
650
|
+
server.private_key
|
651
|
+
elsif server.respond_to?(:key_name) && server.key_name
|
652
|
+
key = get_private_key(server.key_name)
|
653
|
+
unless key
|
654
|
+
raise "Server has key name '#{server.key_name}', but the corresponding private key was not found locally. Check if the key is in Chef::Config.private_key_paths: #{Chef::Config.private_key_paths.join(', ')}"
|
655
|
+
end
|
656
|
+
key
|
657
|
+
elsif machine_spec.reference["key_name"]
|
658
|
+
key = get_private_key(machine_spec.reference["key_name"])
|
659
|
+
unless key
|
660
|
+
raise "Server was created with key name '#{machine_spec.reference['key_name']}', but the corresponding private key was not found locally. Check if the key is in Chef::Config.private_key_paths: #{Chef::Config.private_key_paths.join(', ')}"
|
661
|
+
end
|
662
|
+
key
|
663
|
+
elsif bootstrap_options[:key_path]
|
664
|
+
IO.read(bootstrap_options[:key_path])
|
665
|
+
elsif bootstrap_options[:key_name]
|
666
|
+
get_private_key(bootstrap_options[:key_name])
|
667
|
+
elsif machine_options.key?(:ssh_options) && machine_options[:ssh_options].key?(:keys)
|
668
|
+
IO.read(machine_options[:ssh_options][:keys].first)
|
669
|
+
else
|
670
|
+
# TODO: make a way to suggest other keys to try ...
|
671
|
+
raise "No key found to connect to #{machine_spec.name} (#{machine_spec.reference.inspect})" \
|
672
|
+
" : machine_options -> (#{machine_options.inspect})!"
|
673
|
+
end
|
674
|
+
end
|
682
675
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
676
|
+
def ssh_options_for(machine_spec, machine_options, server)
|
677
|
+
result = {
|
678
|
+
auth_methods: ["publickey"],
|
679
|
+
host_key_alias: "#{server.id}.#{provider}"
|
680
|
+
}.merge(machine_options[:ssh_options] || {})
|
681
|
+
# Grab key_data from the user's config if not specified
|
682
|
+
unless result.key?(:key_data)
|
683
|
+
result[:keys_only] = true
|
684
|
+
result[:key_data] = [private_key_for(machine_spec, machine_options, server)]
|
685
|
+
end
|
686
|
+
result
|
687
|
+
end
|
695
688
|
|
696
|
-
|
697
|
-
|
698
|
-
|
689
|
+
def default_ssh_username
|
690
|
+
"root"
|
691
|
+
end
|
699
692
|
|
700
|
-
|
701
|
-
|
702
|
-
|
693
|
+
def create_winrm_transport(_machine_spec, _machine_options, _server)
|
694
|
+
raise "This provider doesn't know how to do that."
|
695
|
+
end
|
703
696
|
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
697
|
+
def create_ssh_transport(machine_spec, machine_options, server)
|
698
|
+
ssh_options = ssh_options_for(machine_spec, machine_options, server)
|
699
|
+
username = machine_spec.reference["ssh_username"] || default_ssh_username
|
700
|
+
if machine_options.key?(:ssh_username) && machine_options[:ssh_username] != machine_spec.reference["ssh_username"]
|
701
|
+
Chef::Log.warn("Server #{machine_spec.name} was created with SSH username #{machine_spec.reference['ssh_username']} and machine_options specifies username #{machine_options[:ssh_username]}. Using #{machine_spec.reference['ssh_username']}. Please edit the node and change the chef_provisioning.reference.ssh_username attribute if you want to change it.")
|
702
|
+
end
|
703
|
+
options = {}
|
704
|
+
if machine_spec.reference[:sudo] || (!machine_spec.reference.key?(:sudo) && username != "root")
|
705
|
+
options[:prefix] = "sudo "
|
706
|
+
end
|
714
707
|
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
708
|
+
remote_host = determine_remote_host(machine_spec, server)
|
709
|
+
if remote_host.nil? || remote_host.empty?
|
710
|
+
raise "Server #{server.id} has no private or public IP address!"
|
711
|
+
end
|
719
712
|
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
713
|
+
# Enable pty by default
|
714
|
+
options[:ssh_pty_enable] = true
|
715
|
+
if machine_options[:ssh_gateway]
|
716
|
+
options[:ssh_gateway] = machine_options[:ssh_gateway]
|
717
|
+
elsif machine_spec.reference.key?("ssh_gateway")
|
718
|
+
options[:ssh_gateway] = machine_spec.reference["ssh_gateway"]
|
719
|
+
end
|
727
720
|
|
728
|
-
|
729
|
-
|
721
|
+
Transport::SSH.new(remote_host, username, ssh_options, options, config)
|
722
|
+
end
|
730
723
|
|
731
|
-
|
732
|
-
|
733
|
-
|
724
|
+
def self.compute_options_for(provider, _id, _config)
|
725
|
+
raise "unsupported Fog provider #{provider}"
|
726
|
+
end
|
734
727
|
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
728
|
+
def determine_remote_host(machine_spec, server)
|
729
|
+
transport_address_location = (machine_spec.reference["transport_address_location"] || :none).to_sym
|
730
|
+
|
731
|
+
if machine_spec.reference["use_private_ip_for_ssh"]
|
732
|
+
# The machine_spec has the old config key, lets update it - a successful chef converge will save the machine_spec
|
733
|
+
# TODO in 2.0 get rid of this update
|
734
|
+
machine_spec.reference.delete("use_private_ip_for_ssh")
|
735
|
+
machine_spec.reference["transport_address_location"] = :private_ip
|
736
|
+
server.private_ip_address
|
737
|
+
elsif transport_address_location == :ip_addresses
|
738
|
+
server.ip_addresses.first
|
739
|
+
elsif transport_address_location == :private_ip
|
740
|
+
server.private_ip_address
|
741
|
+
elsif transport_address_location == :public_ip
|
742
|
+
server.public_ip_address
|
743
|
+
elsif !server.public_ip_address && server.private_ip_address
|
744
|
+
Chef::Log.warn("Server #{machine_spec.name} has no public floating_ip address. Using private floating_ip '#{server.private_ip_address}'. Set driver option 'transport_address_location' => :private_ip if this will always be the case ...")
|
745
|
+
server.private_ip_address
|
746
|
+
elsif server.public_ip_address
|
747
|
+
server.public_ip_address
|
748
|
+
else
|
749
|
+
raise "Server #{server.id} has no private or public IP address!"
|
750
|
+
# raise "Invalid 'transport_address_location'. They can only be 'public_ip', 'private_ip', or 'ip_addresses'."
|
751
|
+
end
|
752
|
+
end
|
758
753
|
end
|
759
754
|
end
|
760
755
|
end
|
761
756
|
end
|
762
|
-
end
|
763
|
-
end
|