knife-ec2 0.18.0 → 0.18.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,56 +1,56 @@
1
- #
2
- # Author:: Adam Jacob (<adam@chef.io>)
3
- # Author:: Daniel DeLeo (<dan@chef.io>)
4
- # Author:: Seth Chisamore (<schisamo@chef.io>)
5
- # Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
6
- # License:: Apache License, Version 2.0
7
- #
8
- # Licensed under the Apache License, Version 2.0 (the "License");
9
- # you may not use this file except in compliance with the License.
10
- # You may obtain a copy of the License at
11
- #
12
- # http://www.apache.org/licenses/LICENSE-2.0
13
- #
14
- # Unless required by applicable law or agreed to in writing, software
15
- # distributed under the License is distributed on an "AS IS" BASIS,
16
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
- # See the License for the specific language governing permissions and
18
- # limitations under the License.
19
- #
20
-
21
- require 'bundler'
22
- Bundler::GemHelper.install_tasks
23
-
24
- # require 'rubygems'
25
- # require 'rake/gempackagetask'
26
- require 'rdoc/task'
27
-
28
- begin
29
- require 'sdoc'
30
- require 'rdoc/task'
31
-
32
- RDoc::Task.new do |rdoc|
33
- rdoc.title = 'Chef Ruby API Documentation'
34
- rdoc.main = 'README.rdoc'
35
- rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
36
- rdoc.template = 'direct' # lighter template
37
- rdoc.rdoc_files.include('README.rdoc', 'LICENSE', 'spec/tiny_server.rb', 'lib/**/*.rb')
38
- rdoc.rdoc_dir = 'rdoc'
39
- end
40
- rescue LoadError
41
- puts 'sdoc is not available. (sudo) gem install sdoc to generate rdoc documentation.'
42
- end
43
-
44
- begin
45
- require 'rspec/core/rake_task'
46
-
47
- task :default => :spec
48
-
49
- desc 'Run all specs in spec directory'
50
- RSpec::Core::RakeTask.new(:spec) do |t|
51
- t.pattern = 'spec/unit/**/*_spec.rb'
52
- end
53
-
54
- rescue LoadError
55
- STDERR.puts "\n*** RSpec not available. (sudo) gem install rspec to run unit tests. ***\n\n"
56
- end
1
+ #
2
+ # Author:: Adam Jacob (<adam@chef.io>)
3
+ # Author:: Daniel DeLeo (<dan@chef.io>)
4
+ # Author:: Seth Chisamore (<schisamo@chef.io>)
5
+ # Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ require 'bundler'
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ # require 'rubygems'
25
+ # require 'rake/gempackagetask'
26
+ require 'rdoc/task'
27
+
28
+ begin
29
+ require 'sdoc'
30
+ require 'rdoc/task'
31
+
32
+ RDoc::Task.new do |rdoc|
33
+ rdoc.title = 'Chef Ruby API Documentation'
34
+ rdoc.main = 'README.rdoc'
35
+ rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
36
+ rdoc.template = 'direct' # lighter template
37
+ rdoc.rdoc_files.include('README.rdoc', 'LICENSE', 'spec/tiny_server.rb', 'lib/**/*.rb')
38
+ rdoc.rdoc_dir = 'rdoc'
39
+ end
40
+ rescue LoadError
41
+ puts 'sdoc is not available. (sudo) gem install sdoc to generate rdoc documentation.'
42
+ end
43
+
44
+ begin
45
+ require 'rspec/core/rake_task'
46
+
47
+ task :default => :spec
48
+
49
+ desc 'Run all specs in spec directory'
50
+ RSpec::Core::RakeTask.new(:spec) do |t|
51
+ t.pattern = 'spec/unit/**/*_spec.rb'
52
+ end
53
+
54
+ rescue LoadError
55
+ STDERR.puts "\n*** RSpec not available. (sudo) gem install rspec to run unit tests. ***\n\n"
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.18.2
@@ -1,1577 +1,1588 @@
1
- #
2
- # Author:: Adam Jacob (<adam@chef.io>)
3
- # Author:: Seth Chisamore (<schisamo@chef.io>)
4
- # Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
5
- # License:: Apache License, Version 2.0
6
- #
7
- # Licensed under the Apache License, Version 2.0 (the "License");
8
- # you may not use this file except in compliance with the License.
9
- # You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing, software
14
- # distributed under the License is distributed on an "AS IS" BASIS,
15
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- # See the License for the specific language governing permissions and
17
- # limitations under the License.
18
- #
19
-
20
- require 'chef/knife/ec2_base'
21
- require 'chef/knife/s3_source'
22
- require 'chef/knife/winrm_base'
23
- require 'chef/knife/bootstrap_windows_base'
24
-
25
- class Chef
26
- class Knife
27
- class Ec2ServerCreate < Knife
28
-
29
- include Knife::Ec2Base
30
- include Knife::WinrmBase
31
- include Knife::BootstrapWindowsBase
32
- deps do
33
- require 'tempfile'
34
- require 'fog/aws'
35
- require 'uri'
36
- require 'readline'
37
- require 'chef/json_compat'
38
- require 'chef/knife/bootstrap'
39
- Chef::Knife::Bootstrap.load_deps
40
- end
41
-
42
- banner "knife ec2 server create (options)"
43
-
44
- attr_accessor :initial_sleep_delay
45
- attr_reader :server
46
-
47
- option :flavor,
48
- :short => "-f FLAVOR",
49
- :long => "--flavor FLAVOR",
50
- :description => "The flavor of server (m1.small, m1.medium, etc)",
51
- :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f }
52
-
53
- option :image,
54
- :short => "-I IMAGE",
55
- :long => "--image IMAGE",
56
- :description => "The AMI for the server",
57
- :proc => Proc.new { |i| Chef::Config[:knife][:image] = i }
58
-
59
- option :iam_instance_profile,
60
- :long => "--iam-profile NAME",
61
- :description => "The IAM instance profile to apply to this instance."
62
-
63
- option :security_groups,
64
- :short => "-G X,Y,Z",
65
- :long => "--groups X,Y,Z",
66
- :description => "The security groups for this server; not allowed when using VPC",
67
- :proc => Proc.new { |groups| groups.split(',') }
68
-
69
- option :security_group_ids,
70
- :long => "--security-group-ids 'X,Y,Z'",
71
- :description => "The security group ids for this server; required when using VPC. Provide values in format --security-group-ids 'X,Y,Z'. [DEPRECATED] This option will be removed in future release. Use the new --security-group-id option. ",
72
- :proc => Proc.new { |security_group_ids|
73
- ui.warn('[DEPRECATED] This option will be removed in future release. Use the new --security-group-id option multiple times when specifying multiple groups for e.g. -g sg-e985168d -g sg-e7f06383 -g sg-ec1b7e88.')
74
- if security_group_ids.gsub(' ', '').split(',').size > 1
75
- Chef::Config[:knife][:security_group_ids] = security_group_ids.gsub(' ', '').split(',')
76
- else
77
- Chef::Config[:knife][:security_group_ids] ||= []
78
- Chef::Config[:knife][:security_group_ids].push(security_group_ids)
79
- Chef::Config[:knife][:security_group_ids]
80
- end
81
- }
82
-
83
- option :security_group_id,
84
- :short => "-g SECURITY_GROUP_ID",
85
- :long => "--security-group-id ID",
86
- :description => "The security group id for this server; required when using VPC. Use the --security-group-id option multiple times when specifying multiple groups for e.g. -g sg-e985168d -g sg-e7f06383 -g sg-ec1b7e88.",
87
- :proc => Proc.new { |security_group_id|
88
- Chef::Config[:knife][:security_group_ids] ||= []
89
- Chef::Config[:knife][:security_group_ids].push(security_group_id)
90
- Chef::Config[:knife][:security_group_ids]
91
- }
92
-
93
- option :associate_eip,
94
- :long => "--associate-eip IP_ADDRESS",
95
- :description => "Associate existing elastic IP address with instance after launch"
96
-
97
- option :dedicated_instance,
98
- :long => "--dedicated_instance",
99
- :description => "Launch as a Dedicated instance (VPC ONLY)"
100
-
101
- option :placement_group,
102
- :long => "--placement-group PLACEMENT_GROUP",
103
- :description => "The placement group to place a cluster compute instance",
104
- :proc => Proc.new { |pg| Chef::Config[:knife][:placement_group] = pg }
105
-
106
- option :primary_eni,
107
- :long => "--primary-eni ENI_ID",
108
- :description => "Specify a pre-existing eni to use when building the instance."
109
-
110
- option :tags,
111
- :short => "-T T=V[,T=V,...]",
112
- :long => "--tags Tag=Value[,Tag=Value...]",
113
- :description => "The tags for this server. [DEPRECATED] Use --aws-tag instead.",
114
- :proc => Proc.new { |v|
115
- Chef::Log.warn("[DEPRECATED] --tags option is deprecated. Use --aws-tag option instead.")
116
- v
117
- }
118
-
119
- option :availability_zone,
120
- :short => "-Z ZONE",
121
- :long => "--availability-zone ZONE",
122
- :description => "The Availability Zone",
123
- :proc => Proc.new { |key| Chef::Config[:knife][:availability_zone] = key }
124
-
125
- option :chef_node_name,
126
- :short => "-N NAME",
127
- :long => "--node-name NAME",
128
- :description => "The Chef node name for your new node",
129
- :proc => Proc.new { |key| Chef::Config[:knife][:chef_node_name] = key }
130
-
131
- option :ssh_key_name,
132
- :short => "-S KEY",
133
- :long => "--ssh-key KEY",
134
- :description => "The AWS SSH key id",
135
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_key_name] = key }
136
-
137
- option :ssh_user,
138
- :short => "-x USERNAME",
139
- :long => "--ssh-user USERNAME",
140
- :description => "The ssh username",
141
- :default => "root"
142
-
143
- option :ssh_password,
144
- :short => "-P PASSWORD",
145
- :long => "--ssh-password PASSWORD",
146
- :description => "The ssh password"
147
-
148
- option :ssh_port,
149
- :short => "-p PORT",
150
- :long => "--ssh-port PORT",
151
- :description => "The ssh port",
152
- :default => "22",
153
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
154
-
155
- option :ssh_gateway,
156
- :short => "-w GATEWAY",
157
- :long => "--ssh-gateway GATEWAY",
158
- :description => "The ssh gateway server. Any proxies configured in your ssh config are automatically used by default.",
159
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
160
-
161
- option :ssh_gateway_identity,
162
- :long => "--ssh-gateway-identity IDENTITY_FILE",
163
- :description => "The private key for ssh gateway server",
164
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway_identity] = key }
165
-
166
- option :identity_file,
167
- :short => "-i IDENTITY_FILE",
168
- :long => "--identity-file IDENTITY_FILE",
169
- :description => "The SSH identity file used for authentication"
170
-
171
- option :prerelease,
172
- :long => "--prerelease",
173
- :description => "Install the pre-release chef gems"
174
-
175
- option :bootstrap_version,
176
- :long => "--bootstrap-version VERSION",
177
- :description => "The version of Chef to install",
178
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
179
-
180
- option :bootstrap_proxy,
181
- :long => "--bootstrap-proxy PROXY_URL",
182
- :description => "The proxy server for the node being bootstrapped",
183
- :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
184
-
185
- option :distro,
186
- :short => "-d DISTRO",
187
- :long => "--distro DISTRO",
188
- :description => "Bootstrap a distro using a template. [DEPRECATED] Use --bootstrap-template option instead.",
189
- :proc => Proc.new { |v|
190
- Chef::Log.warn("[DEPRECATED] -d / --distro option is deprecated. Use --bootstrap-template option instead.")
191
- v
192
- }
193
-
194
- option :template_file,
195
- :long => "--template-file TEMPLATE",
196
- :description => "Full path to location of template to use. [DEPRECATED] Use -t / --bootstrap-template option instead.",
197
- :proc => Proc.new { |v|
198
- Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use -t / --bootstrap-template option instead.")
199
- v
200
- }
201
-
202
- option :bootstrap_template,
203
- :long => "--bootstrap-template TEMPLATE",
204
- :description => "Bootstrap Chef using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates."
205
-
206
- option :ebs_size,
207
- :long => "--ebs-size SIZE",
208
- :description => "The size of the EBS volume in GB, for EBS-backed instances"
209
-
210
- option :ebs_optimized,
211
- :long => "--ebs-optimized",
212
- :description => "Enabled optimized EBS I/O"
213
-
214
- option :ebs_no_delete_on_term,
215
- :long => "--ebs-no-delete-on-term",
216
- :description => "Do not delete EBS volume on instance termination"
217
-
218
- option :run_list,
219
- :short => "-r RUN_LIST",
220
- :long => "--run-list RUN_LIST",
221
- :description => "Comma separated list of roles/recipes to apply",
222
- :proc => lambda { |o| o.split(/[\s,]+/) },
223
- :default => []
224
-
225
- option :secret,
226
- :long => "--secret ",
227
- :description => "The secret key to use to encrypt data bag item values",
228
- :proc => lambda { |s| Chef::Config[:knife][:secret] = s }
229
-
230
- option :secret_file,
231
- :long => "--secret-file SECRET_FILE",
232
- :description => "A file containing the secret key to use to encrypt data bag item values",
233
- :proc => lambda { |sf| Chef::Config[:knife][:secret_file] = sf }
234
-
235
- option :s3_secret,
236
- :long => '--s3-secret S3_SECRET_URL',
237
- :description => 'S3 URL (e.g. s3://bucket/file) for the encrypted_data_bag_secret_file',
238
- :proc => lambda { |url| Chef::Config[:knife][:s3_secret] = url }
239
-
240
- option :subnet_id,
241
- :long => "--subnet SUBNET-ID",
242
- :description => "create node in this Virtual Private Cloud Subnet ID (implies VPC mode)",
243
- :proc => Proc.new { |key| Chef::Config[:knife][:subnet_id] = key }
244
-
245
- option :private_ip_address,
246
- :long => "--private-ip-address IP-ADDRESS",
247
- :description => "allows to specify the private IP address of the instance in VPC mode",
248
- :proc => Proc.new { |ip| Chef::Config[:knife][:private_ip_address] = ip }
249
-
250
- option :host_key_verify,
251
- :long => "--[no-]host-key-verify",
252
- :description => "Verify host key, enabled by default.",
253
- :boolean => true,
254
- :default => true
255
-
256
- option :bootstrap_protocol,
257
- :long => "--bootstrap-protocol protocol",
258
- :description => "protocol to bootstrap windows servers. options: winrm/ssh",
259
- :proc => Proc.new { |key| Chef::Config[:knife][:bootstrap_protocol] = key },
260
- :default => nil
261
-
262
- option :fqdn,
263
- :long => "--fqdn FQDN",
264
- :description => "Pre-defined FQDN. This is used for Kerberos Authentication purpose only",
265
- :proc => Proc.new { |key| Chef::Config[:knife][:fqdn] = key },
266
- :default => nil
267
-
268
- option :aws_user_data,
269
- :long => "--user-data USER_DATA_FILE",
270
- :short => "-u USER_DATA_FILE",
271
- :description => "The EC2 User Data file to provision the instance with",
272
- :proc => Proc.new { |m| Chef::Config[:knife][:aws_user_data] = m },
273
- :default => nil
274
-
275
- option :hint,
276
- :long => "--hint HINT_NAME[=HINT_FILE]",
277
- :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
278
- :proc => Proc.new { |h|
279
- Chef::Config[:knife][:hints] ||= {}
280
- name, path = h.split("=")
281
- Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
282
- }
283
-
284
- option :ephemeral,
285
- :long => "--ephemeral EPHEMERAL_DEVICES",
286
- :description => "Comma separated list of device locations (eg - /dev/sdb) to map ephemeral devices",
287
- :proc => lambda { |o| o.split(/[\s,]+/) },
288
- :default => []
289
-
290
- option :server_connect_attribute,
291
- :long => "--server-connect-attribute ATTRIBUTE",
292
- :short => "-a ATTRIBUTE",
293
- :description => "The EC2 server attribute to use for the SSH connection if necessary, e.g. public_ip_address or private_ip_address.",
294
- :default => nil
295
-
296
- option :associate_public_ip,
297
- :long => "--associate-public-ip",
298
- :description => "Associate public ip to VPC instance.",
299
- :boolean => true,
300
- :default => false
301
-
302
- option :ebs_volume_type,
303
- :long => "--ebs-volume-type TYPE",
304
- :description => "Possible values are standard (magnetic) | io1 | gp2 | sc1 | st1. Default is gp2",
305
- :proc => Proc.new { |key| Chef::Config[:knife][:ebs_volume_type] = key },
306
- :default => "gp2"
307
-
308
- option :ebs_provisioned_iops,
309
- :long => "--provisioned-iops IOPS",
310
- :description => "IOPS rate, only used when ebs volume type is 'io1'",
311
- :proc => Proc.new { |key| Chef::Config[:knife][:provisioned_iops] = key },
312
- :default => nil
313
-
314
- option :auth_timeout,
315
- :long => "--windows-auth-timeout MINUTES",
316
- :description => "The maximum time in minutes to wait to for authentication over the transport to the node to succeed. The default value is 25 minutes.",
317
- :default => 25
318
-
319
- option :validation_key_url,
320
- :long => "--validation-key-url URL",
321
- :description => "Path to the validation key",
322
- :proc => proc { |m| Chef::Config[:validation_key_url] = m }
323
-
324
- option :ebs_encrypted,
325
- :long => "--ebs-encrypted",
326
- :description => "Enables EBS volume encryption",
327
- :boolean => true,
328
- :default => false
329
-
330
- option :spot_price,
331
- :long => "--spot-price PRICE",
332
- :description => "The maximum hourly USD price for the instance",
333
- :default => nil
334
-
335
- option :spot_request_type,
336
- :long => "--spot-request-type TYPE",
337
- :description => "The Spot Instance request type. Possible values are 'one-time' and 'persistent', default value is 'one-time'",
338
- :default => "one-time"
339
-
340
- option :spot_wait_mode,
341
- :long => "--spot-wait-mode MODE",
342
- :description =>
343
- "Whether we should wait for spot request fulfillment. Could be 'wait', 'exit', or " \
344
- "'prompt' (default). For any of the above mentioned choices, ('wait') - if the " \
345
- "instance does not get allocated before the command itself times-out or ('exit') the " \
346
- "user needs to manually bootstrap the instance in the future after it gets allocated.",
347
- :default => "prompt"
348
-
349
- option :aws_connection_timeout,
350
- :long => "--aws-connection-timeout MINUTES",
351
- :description => "The maximum time in minutes to wait to for aws connection. Default is 10 min",
352
- :proc => proc {|t| t = t.to_i * 60; Chef::Config[:aws_connection_timeout] = t},
353
- :default => 600
354
-
355
- option :node_ssl_verify_mode,
356
- :long => "--node-ssl-verify-mode [peer|none]",
357
- :description => "Whether or not to verify the SSL cert for all HTTPS requests.",
358
- :proc => Proc.new { |v|
359
- valid_values = ["none", "peer"]
360
- unless valid_values.include?(v)
361
- raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
362
- end
363
- }
364
-
365
- option :node_verify_api_cert,
366
- :long => "--[no-]node-verify-api-cert",
367
- :description => "Verify the SSL cert for HTTPS requests to the Chef server API.",
368
- :boolean => true
369
-
370
- option :bootstrap_no_proxy,
371
- :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]",
372
- :description => "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode",
373
- :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np }
374
-
375
- option :bootstrap_url,
376
- :long => "--bootstrap-url URL",
377
- :description => "URL to a custom installation script",
378
- :proc => Proc.new { |u| Chef::Config[:knife][:bootstrap_url] = u }
379
-
380
- option :bootstrap_install_command,
381
- :long => "--bootstrap-install-command COMMANDS",
382
- :description => "Custom command to install chef-client",
383
- :proc => Proc.new { |ic| Chef::Config[:knife][:bootstrap_install_command] = ic }
384
-
385
- option :bootstrap_wget_options,
386
- :long => "--bootstrap-wget-options OPTIONS",
387
- :description => "Add options to wget when installing chef-client",
388
- :proc => Proc.new { |wo| Chef::Config[:knife][:bootstrap_wget_options] = wo }
389
-
390
- option :bootstrap_curl_options,
391
- :long => "--bootstrap-curl-options OPTIONS",
392
- :description => "Add options to curl when install chef-client",
393
- :proc => Proc.new { |co| Chef::Config[:knife][:bootstrap_curl_options] = co }
394
-
395
- option :bootstrap_vault_file,
396
- :long => '--bootstrap-vault-file VAULT_FILE',
397
- :description => 'A JSON file with a list of vault(s) and item(s) to be updated'
398
-
399
- option :bootstrap_vault_json,
400
- :long => '--bootstrap-vault-json VAULT_JSON',
401
- :description => 'A JSON string with the vault(s) and item(s) to be updated'
402
-
403
- option :bootstrap_vault_item,
404
- :long => '--bootstrap-vault-item VAULT_ITEM',
405
- :description => 'A single vault and item to update as "vault:item"',
406
- :proc => Proc.new { |i|
407
- (vault, item) = i.split(/:/)
408
- Chef::Config[:knife][:bootstrap_vault_item] ||= {}
409
- Chef::Config[:knife][:bootstrap_vault_item][vault] ||= []
410
- Chef::Config[:knife][:bootstrap_vault_item][vault].push(item)
411
- Chef::Config[:knife][:bootstrap_vault_item]
412
- }
413
-
414
- option :use_sudo_password,
415
- :long => "--use-sudo-password",
416
- :description => "Execute the bootstrap via sudo with password",
417
- :boolean => false
418
-
419
- option :forward_agent,
420
- :short => "-A",
421
- :long => "--forward-agent",
422
- :description => "Enable SSH agent forwarding",
423
- :boolean => true
424
-
425
- option :create_ssl_listener,
426
- :long => "--[no-]create-ssl-listener",
427
- :description => "Create ssl listener, enabled by default.",
428
- :boolean => true,
429
- :default => true
430
-
431
- option :network_interfaces,
432
- :short => '-n',
433
- :long => '--attach-network-interface ENI1,ENI2',
434
- :description => 'Attach additional network interfaces during bootstrap',
435
- :proc => proc { |nics| nics.split(',') }
436
-
437
- option :classic_link_vpc_id,
438
- :long => "--classic-link-vpc-id VPC_ID",
439
- :description => "Enable ClassicLink connection with a VPC"
440
-
441
- option :classic_link_vpc_security_group_ids,
442
- :long => "--classic-link-vpc-security-groups-ids X,Y,Z",
443
- :description => "Comma-separated list of security group ids for ClassicLink",
444
- :proc => Proc.new { |groups| groups.split(',') }
445
-
446
- option :disable_api_termination,
447
- :long => "--disable-api-termination",
448
- :description => "Disable termination of the instance using the Amazon EC2 console, CLI and API.",
449
- :boolean => true,
450
- :default => false
451
-
452
- option :volume_tags,
453
- :long => "--volume-tags Tag=Value[,Tag=Value...]",
454
- :description => "Tag the Root volume",
455
- :proc => Proc.new { |volume_tags| volume_tags.split(',') }
456
-
457
- option :tag_node_in_chef,
458
- :long => "--tag-node-in-chef",
459
- :description => "Flag for tagging node in ec2 and chef both. [DEPRECATED] Use --chef-tag instead.",
460
- :proc => Proc.new { |v|
461
- Chef::Log.warn("[DEPRECATED] --tag-node-in-chef option is deprecated. Use --chef-tag option instead.")
462
- v
463
- },
464
- :boolean => true,
465
- :default => false
466
-
467
- option :instance_initiated_shutdown_behavior,
468
- :long => "--instance-initiated-shutdown-behavior SHUTDOWN_BEHAVIOR",
469
- :description => "Indicates whether an instance stops or terminates when you initiate shutdown from the instance. Possible values are 'stop' and 'terminate', default is 'stop'."
470
-
471
- option :chef_tag,
472
- :long => "--chef-tag CHEF_TAG",
473
- :description => "Use to tag the node in chef server; Provide --chef-tag option multiple times when specifying multiple tags e.g. --chef-tag tag1 --chef-tag tag2.",
474
- :proc => Proc.new { |chef_tag|
475
- Chef::Config[:knife][:chef_tag] ||= []
476
- Chef::Config[:knife][:chef_tag].push(chef_tag)
477
- Chef::Config[:knife][:chef_tag]
478
- }
479
-
480
- option :aws_tag,
481
- :long => "--aws-tag AWS_TAG",
482
- :description => "AWS tag for this server; Use the --aws-tag option multiple times when specifying multiple tags e.g. --aws-tag key1=value1 --aws-tag key2=value2.",
483
- :proc => Proc.new { |aws_tag|
484
- Chef::Config[:knife][:aws_tag] ||= []
485
- Chef::Config[:knife][:aws_tag].push(aws_tag)
486
- Chef::Config[:knife][:aws_tag]
487
- }
488
-
489
- def run
490
- $stdout.sync = true
491
- validate!
492
-
493
- requested_elastic_ip = config[:associate_eip] if config[:associate_eip]
494
-
495
- # For VPC EIP assignment we need the allocation ID so fetch full EIP details
496
- elastic_ip = connection.addresses.detect{|addr| addr if addr.public_ip == requested_elastic_ip}
497
-
498
- if locate_config_value(:spot_price)
499
- server_def = create_server_def
500
- server_def[:groups] = server_def[:security_group_ids] if vpc_mode?
501
- spot_request = connection.spot_requests.create(server_def)
502
- msg_pair("Spot Request ID", spot_request.id)
503
- msg_pair("Spot Request Type", spot_request.request_type)
504
- msg_pair("Spot Price", spot_request.price)
505
-
506
- case config[:spot_wait_mode]
507
- when 'prompt', '', nil
508
- wait_msg = "Do you want to wait for Spot Instance Request fulfillment? (Y/N) \n"
509
- wait_msg += "Y - Wait for Spot Instance request fulfillment\n"
510
- wait_msg += "N - Do not wait for Spot Instance request fulfillment. "
511
- wait_msg += ui.color("[WARN :: Request would be alive on AWS ec2 side but execution of Chef Bootstrap on the target instance will get skipped.]\n", :red, :bold)
512
- wait_msg += ui.color("\n[WARN :: For any of the above mentioned choices, (Y) - if the instance does not get allocated before the command itself times-out or (N) - user decides to exit, then in both cases user needs to manually bootstrap the instance in the future after it gets allocated.]\n\n", :cyan, :bold)
513
- confirm(wait_msg)
514
- when 'wait'
515
- # wait for the node and run Chef bootstrap
516
- when 'exit'
517
- ui.color("The 'exit' option was specified for --spot-wait-mode, exiting.", :cyan)
518
- exit
519
- else
520
- raise "Invalid value for --spot-wait-mode: '#{config[:spot_wait_mode]}', " \
521
- "valid values: wait, exit, prompt"
522
- end
523
-
524
- print ui.color("Waiting for Spot Request fulfillment: ", :cyan)
525
- spot_request.wait_for do
526
- @spinner ||= %w{| / - \\}
527
- print "\b" + @spinner.rotate!.first
528
- ready?
529
- end
530
- puts("\n")
531
- @server = connection.servers.get(spot_request.instance_id)
532
- else
533
- begin
534
- @server = connection.servers.create(create_server_def)
535
- rescue => error
536
- error.message.sub("download completed, but downloaded file not found", "Verify that you have public internet access.")
537
- ui.error error.message
538
- Chef::Log.debug("#{error.backtrace.join("\n")}")
539
- exit
540
- end
541
- end
542
-
543
- hashed_tags={}
544
- tags.map{ |t| key,val=t.split('='); hashed_tags[key]=val} unless tags.nil?
545
-
546
- # Always set the Name tag
547
- unless hashed_tags.keys.include? "Name"
548
- if locate_config_value(:chef_node_name)
549
- hashed_tags["Name"] = evaluate_node_name(locate_config_value(:chef_node_name))
550
- else
551
- hashed_tags["Name"] = server.id
552
- end
553
- end
554
-
555
- printed_aws_tags = hashed_tags.map{ |tag, val| "#{tag}: #{val}" }.join(", ")
556
-
557
- hashed_volume_tags={}
558
- volume_tags = locate_config_value(:volume_tags)
559
- volume_tags.map{ |t| key,val=t.split('='); hashed_volume_tags[key]=val} unless volume_tags.nil?
560
- printed_volume_tags = hashed_volume_tags.map{ |tag, val| "#{tag}: #{val}" }.join(", ")
561
-
562
- msg_pair("Instance ID", @server.id)
563
- msg_pair("Flavor", @server.flavor_id)
564
- msg_pair("Image", @server.image_id)
565
- msg_pair("Region", connection.instance_variable_get(:@region))
566
- msg_pair("Availability Zone", @server.availability_zone)
567
-
568
- # If we don't specify a security group or security group id, Fog will
569
- # pick the appropriate default one. In case of a VPC we don't know the
570
- # default security group id at this point unless we look it up, hence
571
- # 'default' is printed if no id was specified.
572
- printed_security_groups = "default"
573
- printed_security_groups = @server.groups.join(", ") if @server.groups
574
- msg_pair("Security Groups", printed_security_groups) unless vpc_mode? or (@server.groups.nil? and @server.security_group_ids)
575
-
576
- printed_security_group_ids = "default"
577
- printed_security_group_ids = @server.security_group_ids.join(", ") if @server.security_group_ids
578
- msg_pair("Security Group Ids", printed_security_group_ids) if vpc_mode? or @server.security_group_ids
579
-
580
- msg_pair("IAM Profile", locate_config_value(:iam_instance_profile))
581
-
582
- msg_pair("AWS Tags", printed_aws_tags)
583
- msg_pair("Volume Tags", printed_volume_tags)
584
- msg_pair("SSH Key", @server.key_name)
585
-
586
- print "\n#{ui.color("Waiting for EC2 to create the instance", :magenta)}"
587
-
588
- # wait for instance to come up before acting against it
589
- @server.wait_for(locate_config_value(:aws_connection_timeout)) { print "."; ready? }
590
-
591
- puts("\n")
592
-
593
- # occasionally 'ready?' isn't, so retry a couple times if needed.
594
- tries = 6
595
- begin
596
- create_tags(hashed_tags) unless hashed_tags.empty?
597
- create_volume_tags(hashed_volume_tags) unless hashed_volume_tags.empty?
598
- associate_eip(elastic_ip) if config[:associate_eip]
599
- enable_classic_link(config[:classic_link_vpc_id], config[:classic_link_vpc_security_group_ids]) if config[:classic_link_vpc_id]
600
- rescue Fog::Compute::AWS::NotFound, Fog::Errors::Error
601
- raise if (tries -= 1) <= 0
602
- ui.warn("server not ready, retrying tag application (retries left: #{tries})")
603
- sleep 5
604
- retry
605
- end
606
-
607
- attach_nics if config[:network_interfaces]
608
-
609
- if vpc_mode?
610
- msg_pair("Subnet ID", @server.subnet_id)
611
- msg_pair("Tenancy", @server.tenancy)
612
- if config[:associate_public_ip]
613
- msg_pair("Public DNS Name", @server.dns_name)
614
- end
615
- if elastic_ip
616
- msg_pair("Public IP Address", @server.public_ip_address)
617
- end
618
- else
619
- msg_pair("Public DNS Name", @server.dns_name)
620
- msg_pair("Public IP Address", @server.public_ip_address)
621
- msg_pair("Private DNS Name", @server.private_dns_name)
622
- end
623
- msg_pair("Private IP Address", @server.private_ip_address)
624
-
625
- if Chef::Config[:knife][:validation_key_url]
626
- download_validation_key(validation_key_path)
627
- Chef::Config[:validation_key] = validation_key_path
628
- end
629
-
630
- #Check if Server is Windows or Linux
631
- if is_image_windows?
632
- protocol = locate_config_value(:bootstrap_protocol)
633
- protocol ||= 'winrm'
634
- if protocol == 'winrm'
635
- load_winrm_deps
636
- print "\n#{ui.color("Waiting for winrm access to become available", :magenta)}"
637
- print(".") until tcp_test_winrm(ssh_connect_host, locate_config_value(:winrm_port)) {
638
- sleep 10
639
- puts("done")
640
- }
641
- else
642
- print "\n#{ui.color("Waiting for sshd access to become available", :magenta)}"
643
- #If FreeSSHd, winsshd etc are available
644
- print(".") until tcp_test_ssh(ssh_connect_host, config[:ssh_port]) {
645
- sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10)
646
- puts("done")
647
- }
648
- ssh_override_winrm
649
- end
650
- bootstrap_for_windows_node(@server, ssh_connect_host).run
651
- else
652
- print "\n#{ui.color("Waiting for sshd access to become available", :magenta)}"
653
- wait_for_sshd(ssh_connect_host)
654
- ssh_override_winrm
655
- bootstrap_for_linux_node(@server, ssh_connect_host).run
656
- end
657
-
658
- puts "\n"
659
- msg_pair("Instance ID", @server.id)
660
- msg_pair("Flavor", @server.flavor_id)
661
- msg_pair("Placement Group", @server.placement_group) unless @server.placement_group.nil?
662
- msg_pair("Image", @server.image_id)
663
- msg_pair("Region", connection.instance_variable_get(:@region))
664
- msg_pair("Availability Zone", @server.availability_zone)
665
- msg_pair("Security Groups", printed_security_groups) unless vpc_mode? or (@server.groups.nil? and @server.security_group_ids)
666
- msg_pair("Security Group Ids", printed_security_group_ids) if vpc_mode? or @server.security_group_ids
667
- msg_pair("IAM Profile", locate_config_value(:iam_instance_profile)) if locate_config_value(:iam_instance_profile)
668
- msg_pair("Primary ENI", locate_config_value(:primary_eni)) if locate_config_value(:primary_eni)
669
- msg_pair("AWS Tags", printed_aws_tags)
670
- msg_pair("Chef Tags", locate_config_value(:chef_tag)) if locate_config_value(:chef_tag)
671
- msg_pair("SSH Key", @server.key_name)
672
- msg_pair("Root Device Type", @server.root_device_type)
673
- msg_pair("Root Volume Tags", printed_volume_tags)
674
- if @server.root_device_type == "ebs"
675
- device_map = @server.block_device_mapping.first
676
- msg_pair("Root Volume ID", device_map['volumeId'])
677
- msg_pair("Root Device Name", device_map['deviceName'])
678
- msg_pair("Root Device Delete on Terminate", device_map['deleteOnTermination'])
679
- msg_pair("Standard or Provisioned IOPS", device_map['volumeType'])
680
- msg_pair("IOPS rate", device_map['iops'])
681
-
682
- print "\n#{ui.color("Block devices", :magenta)}\n"
683
- print "#{ui.color("===========================", :magenta)}\n"
684
- @server.block_device_mapping.each do |device_map|
685
- msg_pair("Device Name", device_map['deviceName'])
686
- msg_pair("Volume ID", device_map['volumeId'])
687
- msg_pair("Delete on Terminate", device_map['deleteOnTermination'].to_s)
688
- msg_pair("Standard or Provisioned IOPS", device_map['volumeType'])
689
- msg_pair("IOPS rate", device_map['iops'])
690
- print "\n"
691
- end
692
- print "#{ui.color("===========================", :magenta)}\n"
693
-
694
- if config[:ebs_size]
695
- if ami.block_device_mapping.first['volumeSize'].to_i < config[:ebs_size].to_i
696
- volume_too_large_warning = "#{config[:ebs_size]}GB " +
697
- "EBS volume size is larger than size set in AMI of " +
698
- "#{ami.block_device_mapping.first['volumeSize']}GB.\n" +
699
- "Use file system tools to make use of the increased volume size."
700
- msg_pair("Warning", volume_too_large_warning, :yellow)
701
- end
702
- end
703
- end
704
- if config[:ebs_optimized]
705
- msg_pair("EBS is Optimized", @server.ebs_optimized.to_s)
706
- end
707
- if vpc_mode?
708
- msg_pair("Subnet ID", @server.subnet_id)
709
- msg_pair("Tenancy", @server.tenancy)
710
- if config[:associate_public_ip]
711
- msg_pair("Public DNS Name", @server.dns_name)
712
- end
713
- else
714
- msg_pair("Public DNS Name", @server.dns_name)
715
- msg_pair("Public IP Address", @server.public_ip_address)
716
- msg_pair("Private DNS Name", @server.private_dns_name)
717
- end
718
- msg_pair("Private IP Address", @server.private_ip_address)
719
- msg_pair("Environment", config[:environment] || '_default')
720
- msg_pair("Run List", (config[:run_list] || []).join(', '))
721
- if config[:first_boot_attributes] || config[:first_boot_attributes_from_file]
722
- msg_pair("JSON Attributes",config[:first_boot_attributes] || config[:first_boot_attributes_from_file])
723
- end
724
- end
725
-
726
- def default_bootstrap_template
727
- is_image_windows? ? 'windows-chef-client-msi' : 'chef-full'
728
- end
729
-
730
- def validation_key_path
731
- @validation_key_path ||= begin
732
- if URI(Chef::Config[:knife][:validation_key_url]).scheme == 'file'
733
- URI(Chef::Config[:knife][:validation_key_url]).path
734
- else
735
- validation_key_tmpfile.path
736
- end
737
- end
738
- end
739
-
740
- def validation_key_tmpfile
741
- @validation_key_tmpfile ||= Tempfile.new('validation_key')
742
- end
743
-
744
- def download_validation_key(tempfile)
745
- Chef::Log.debug 'Downloading validation key ' \
746
- "<#{Chef::Config[:knife][:validation_key_url]}> to file " \
747
- "<#{tempfile}>"
748
-
749
- case URI(Chef::Config[:knife][:validation_key_url]).scheme
750
- when 's3'
751
- File.open(tempfile, 'w') { |f| f.write(s3_validation_key) }
752
- end
753
- end
754
-
755
- def s3_validation_key
756
- @s3_validation_key ||= begin
757
- Chef::Knife::S3Source.fetch(Chef::Config[:knife][:validation_key_url])
758
- end
759
- end
760
-
761
- def s3_secret
762
- @s3_secret ||= begin
763
- return false unless locate_config_value(:s3_secret)
764
- Chef::Knife::S3Source.fetch(locate_config_value(:s3_secret))
765
- end
766
- end
767
-
768
- def bootstrap_common_params(bootstrap)
769
- bootstrap.config[:run_list] = config[:run_list]
770
- bootstrap.config[:policy_group] = locate_config_value(:policy_group)
771
- bootstrap.config[:policy_name] = locate_config_value(:policy_name)
772
- bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
773
- bootstrap.config[:distro] = locate_config_value(:distro) || default_bootstrap_template
774
- # setting bootstrap_template value to template_file for backward compatibility
775
- bootstrap.config[:template_file] = locate_config_value(:template_file) || locate_config_value(:bootstrap_template)
776
- bootstrap.config[:environment] = locate_config_value(:environment)
777
- bootstrap.config[:prerelease] = config[:prerelease]
778
- bootstrap.config[:first_boot_attributes] = locate_config_value(:first_boot_attributes)
779
- bootstrap.config[:first_boot_attributes_from_file] = locate_config_value(:first_boot_attributes_from_file)
780
- bootstrap.config[:encrypted_data_bag_secret] = s3_secret || locate_config_value(:secret)
781
- bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:secret_file)
782
- # retrieving the secret from S3 is unique to knife-ec2, so we need to set "command line secret" to the value fetched from S3
783
- # When linux vm is spawned, the chef's secret option proc function sets the value "command line secret" and this value is used by
784
- # chef's code to check if secret option is passed through command line or not
785
- Chef::Knife::DataBagSecretOptions.set_cl_secret(s3_secret) if locate_config_value(:s3_secret)
786
- bootstrap.config[:secret] = s3_secret || locate_config_value(:secret)
787
- bootstrap.config[:secret_file] = locate_config_value(:secret_file)
788
- bootstrap.config[:node_ssl_verify_mode] = locate_config_value(:node_ssl_verify_mode)
789
- bootstrap.config[:node_verify_api_cert] = locate_config_value(:node_verify_api_cert)
790
- bootstrap.config[:bootstrap_no_proxy] = locate_config_value(:bootstrap_no_proxy)
791
- bootstrap.config[:bootstrap_url] = locate_config_value(:bootstrap_url)
792
- bootstrap.config[:bootstrap_install_command] = locate_config_value(:bootstrap_install_command)
793
- bootstrap.config[:bootstrap_wget_options] = locate_config_value(:bootstrap_wget_options)
794
- bootstrap.config[:bootstrap_curl_options] = locate_config_value(:bootstrap_curl_options)
795
- bootstrap.config[:bootstrap_vault_file] = locate_config_value(:bootstrap_vault_file)
796
- bootstrap.config[:bootstrap_vault_json] = locate_config_value(:bootstrap_vault_json)
797
- bootstrap.config[:bootstrap_vault_item] = locate_config_value(:bootstrap_vault_item)
798
- bootstrap.config[:use_sudo_password] = locate_config_value(:use_sudo_password)
799
- bootstrap.config[:yes] = locate_config_value(:yes)
800
- bootstrap.config[:tags] = config[:chef_tag] if locate_config_value(:chef_tag)
801
- # Modify global configuration state to ensure hint gets set by
802
- # knife-bootstrap
803
- Chef::Config[:knife][:hints] ||= {}
804
- Chef::Config[:knife][:hints]["ec2"] ||= {}
805
- bootstrap
806
- end
807
-
808
- def fetch_server_fqdn(ip_addr)
809
- require 'resolv'
810
- Resolv.getname(ip_addr)
811
- end
812
-
813
- def bootstrap_for_windows_node(server, fqdn)
814
- if locate_config_value(:bootstrap_protocol) == 'winrm' || locate_config_value(:bootstrap_protocol) == nil
815
- if locate_config_value(:kerberos_realm)
816
- #Fetch AD/WINS based fqdn if any for Kerberos-based Auth
817
- fqdn = locate_config_value(:fqdn) || fetch_server_fqdn(server.private_ip_address)
818
- end
819
- bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
820
- bootstrap.config[:winrm_user] = locate_config_value(:winrm_user)
821
- bootstrap.config[:winrm_password] = windows_password
822
- bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport)
823
- bootstrap.config[:kerberos_keytab_file] = locate_config_value(:kerberos_keytab_file)
824
- bootstrap.config[:kerberos_realm] = locate_config_value(:kerberos_realm)
825
- bootstrap.config[:kerberos_service] = locate_config_value(:kerberos_service)
826
- bootstrap.config[:ca_trust_file] = locate_config_value(:ca_trust_file)
827
- bootstrap.config[:winrm_port] = locate_config_value(:winrm_port)
828
- bootstrap.config[:auth_timeout] = locate_config_value(:auth_timeout)
829
- bootstrap.config[:winrm_ssl_verify_mode] = locate_config_value(:winrm_ssl_verify_mode)
830
- elsif locate_config_value(:bootstrap_protocol) == 'ssh'
831
- bootstrap = Chef::Knife::BootstrapWindowsSsh.new
832
- bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
833
- bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
834
- bootstrap.config[:ssh_port] = locate_config_value(:ssh_port)
835
- bootstrap.config[:identity_file] = locate_config_value(:identity_file)
836
- bootstrap.config[:no_host_key_verify] = locate_config_value(:no_host_key_verify)
837
- bootstrap.config[:forward_agent] = locate_config_value(:forward_agent)
838
- else
839
- ui.error("Unsupported Bootstrapping Protocol. Supported : winrm, ssh")
840
- exit 1
841
- end
842
- bootstrap.name_args = [fqdn]
843
- bootstrap.config[:msi_url] = locate_config_value(:msi_url)
844
- bootstrap.config[:install_as_service] = locate_config_value(:install_as_service)
845
- bootstrap.config[:session_timeout] = locate_config_value(:session_timeout)
846
- bootstrap.config[:tags] = config[:tags] if locate_config_value(:tag_node_in_chef)
847
-
848
- if locate_config_value(:chef_node_name)
849
- bootstrap.config[:chef_node_name] = evaluate_node_name(locate_config_value(:chef_node_name))
850
- else
851
- bootstrap.config[:chef_node_name] = server.id
852
- end
853
- bootstrap_common_params(bootstrap)
854
- end
855
-
856
- def bootstrap_for_linux_node(server,ssh_host)
857
- bootstrap = Chef::Knife::Bootstrap.new
858
- bootstrap.name_args = [ssh_host]
859
- bootstrap.config[:ssh_user] = config[:ssh_user]
860
- bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
861
- bootstrap.config[:ssh_port] = config[:ssh_port]
862
- bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
863
- bootstrap.config[:identity_file] = config[:identity_file]
864
- bootstrap.config[:tags] = config[:tags] if locate_config_value(:tag_node_in_chef)
865
-
866
- if locate_config_value(:chef_node_name)
867
- bootstrap.config[:chef_node_name] = evaluate_node_name(locate_config_value(:chef_node_name))
868
- else
869
- bootstrap.config[:chef_node_name] = server.id
870
- end
871
- bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
872
- # may be needed for vpc_mode
873
- bootstrap.config[:host_key_verify] = config[:host_key_verify]
874
- bootstrap_common_params(bootstrap)
875
- end
876
-
877
- def vpc_mode?
878
- # Amazon Virtual Private Cloud requires a subnet_id. If
879
- # present, do a few things differently
880
- !!locate_config_value(:subnet_id)
881
- end
882
-
883
- def ami
884
- @ami ||= connection.images.get(locate_config_value(:image))
885
- end
886
-
887
- def validate!
888
- if Chef::Config[:knife].keys.include? :aws_ssh_key_id
889
- Chef::Config[:knife][:ssh_key_name] = Chef::Config[:knife][:aws_ssh_key_id] if !Chef::Config[:knife][:ssh_key_name]
890
- Chef::Config[:knife].delete(:aws_ssh_key_id)
891
- ui.warn("Use of aws_ssh_key_id option in knife.rb config is deprecated, use ssh_key_name option instead.")
892
- end
893
-
894
- super([:image, :ssh_key_name, :aws_access_key_id, :aws_secret_access_key])
895
-
896
- validate_nics! if locate_config_value(:network_interfaces)
897
-
898
- if ami.nil?
899
- ui.error("You have not provided a valid image (AMI) value.")
900
- exit 1
901
- end
902
-
903
- if vpc_mode? and !!config[:security_groups]
904
- ui.error("You are using a VPC, security groups specified with '-G' are not allowed, specify one or more security group ids with '-g' instead.")
905
- exit 1
906
- end
907
-
908
- if !vpc_mode? and !!config[:private_ip_address]
909
- ui.error("You can only specify a private IP address if you are using VPC.")
910
- exit 1
911
- end
912
-
913
- if config[:dedicated_instance] and !vpc_mode?
914
- ui.error("You can only specify a Dedicated Instance if you are using VPC.")
915
- exit 1
916
- end
917
-
918
- if !vpc_mode? and config[:associate_public_ip]
919
- ui.error("--associate-public-ip option only applies to VPC instances, and you have not specified a subnet id.")
920
- exit 1
921
- end
922
-
923
- if config[:associate_eip]
924
- eips = connection.addresses.collect{|addr| addr if addr.domain == eip_scope}.compact
925
-
926
- unless eips.detect{|addr| addr.public_ip == config[:associate_eip] && addr.server_id == nil}
927
- ui.error("Elastic IP requested is not available.")
928
- exit 1
929
- end
930
- end
931
-
932
- if config[:ebs_provisioned_iops] and config[:ebs_volume_type] != 'io1'
933
- ui.error("--provisioned-iops option is only supported for volume type of 'io1'")
934
- exit 1
935
- end
936
-
937
- if config[:ebs_volume_type] == 'io1' and config[:ebs_provisioned_iops].nil?
938
- ui.error("--provisioned-iops option is required when using volume type of 'io1'")
939
- exit 1
940
- end
941
-
942
- if config[:ebs_volume_type] and ! %w(gp2 io1 standard).include?(config[:ebs_volume_type])
943
- ui.error("--ebs-volume-type must be 'standard' or 'io1' or 'gp2'")
944
- msg opt_parser
945
- exit 1
946
- end
947
-
948
- if config[:security_groups] && config[:security_groups].class == String
949
- ui.error("Invalid value type for knife[:security_groups] in knife configuration file (i.e knife.rb). Type should be array. e.g - knife[:security_groups] = ['sgroup1']")
950
- exit 1
951
- end
952
-
953
- # Validation for security_group_ids passed through knife.rb. It will raise error if values are not provided in Array.
954
- if locate_config_value(:security_group_ids) && locate_config_value(:security_group_ids).class == String
955
- ui.error("Invalid value type for knife[:security_group_ids] in knife configuration file (i.e knife.rb). Type should be array. e.g - knife[:security_group_ids] = ['sgroup1']")
956
- exit 1
957
- end
958
-
959
- if config[:classic_link_vpc_id].nil? ^ config[:classic_link_vpc_security_group_ids].nil?
960
- ui.error("--classic-link-vpc-id and --classic-link-vpc-security-group-ids must be used together")
961
- exit 1
962
- end
963
-
964
- if vpc_mode? and config[:classic_link_vpc_id]
965
- ui.error("You can only use ClassicLink if you are not using a VPC")
966
- exit 1
967
- end
968
-
969
- if locate_config_value(:ebs_encrypted)
970
- error_message = ""
971
- errors = []
972
- # validation for flavor and ebs_encrypted
973
- if !locate_config_value(:flavor)
974
- ui.error("--ebs-encrypted option requires valid flavor to be specified.")
975
- exit 1
976
- elsif (locate_config_value(:ebs_encrypted) and ! %w(m3.medium m3.large m3.xlarge m3.2xlarge m4.large m4.xlarge
977
- m4.2xlarge m4.4xlarge m4.10xlarge m4.16xlarge t2.nano t2.micro t2.small
978
- t2.medium t2.large t2.xlarge t2.2xlarge d2.xlarge d2.2xlarge d2.4xlarge
979
- d2.8xlarge c4.large c4.xlarge c4.2xlarge c4.4xlarge c4.8xlarge c3.large
980
- c3.xlarge c3.2xlarge c3.4xlarge c3.8xlarge cr1.8xlarge r3.large r3.xlarge
981
- r3.2xlarge r3.4xlarge r3.8xlarge r4.large r4.xlarge r4.2xlarge r4.4xlarge
982
- r4.8xlarge r4.16xlarge x1.16xlarge x1.32xlarge i2.xlarge i2.2xlarge i2.4xlarge
983
- i2.8xlarge i3.large i3.xlarge i3.2xlarge i3.4xlarge i3.8xlarge i3.16xlarge
984
- f1.2xlarge f1.16xlarge g2.2xlarge g2.8xlarge p2.xlarge p2.8xlarge p2.16xlarge).include?(locate_config_value(:flavor)))
985
- ui.error("--ebs-encrypted option is not supported for #{locate_config_value(:flavor)} flavor.")
986
- exit 1
987
- end
988
-
989
- # validation for ebs_size and ebs_volume_type and ebs_encrypted
990
- if !locate_config_value(:ebs_size)
991
- errors << "--ebs-encrypted option requires valid --ebs-size to be specified."
992
- elsif locate_config_value(:ebs_volume_type) == "gp2" and ! locate_config_value(:ebs_size).to_i.between?(1, 16384)
993
- errors << "--ebs-size should be in between 1-16384 for 'gp2' ebs volume type."
994
- elsif locate_config_value(:ebs_volume_type) == "io1" and ! locate_config_value(:ebs_size).to_i.between?(4, 16384)
995
- errors << "--ebs-size should be in between 4-16384 for 'io1' ebs volume type."
996
- elsif locate_config_value(:ebs_volume_type) == "standard" and ! locate_config_value(:ebs_size).to_i.between?(1, 1024)
997
- errors << "--ebs-size should be in between 1-1024 for 'standard' ebs volume type."
998
- end
999
-
1000
- if errors.each{|e| error_message = "#{error_message} #{e}"}.any?
1001
- ui.error(error_message)
1002
- exit 1
1003
- end
1004
- end
1005
-
1006
- if locate_config_value(:spot_price) && locate_config_value(:disable_api_termination)
1007
- ui.error("spot-price and disable-api-termination options cannot be passed together as 'Termination Protection' cannot be enabled for spot instances.")
1008
- exit 1
1009
- end
1010
-
1011
- if locate_config_value(:spot_price).nil? && locate_config_value(:spot_wait_mode).downcase != 'prompt'
1012
- ui.error('spot-wait-mode option requires that a spot-price option is set.')
1013
- exit 1
1014
- end
1015
-
1016
- volume_tags = locate_config_value(:volume_tags)
1017
- if !volume_tags.nil? and volume_tags.length != volume_tags.to_s.count('=')
1018
- ui.error("Volume Tags should be entered in a key = value pair")
1019
- exit 1
1020
- end
1021
-
1022
- if (locate_config_value(:winrm_password).to_s.length > 14 )
1023
- ui.warn("The password provided is longer than 14 characters. Computers with Windows prior to Windows 2000 will not be able to use this account. Do you want to continue this operation? (Y/N):")
1024
- password_promt = STDIN.gets.chomp.upcase
1025
- if (password_promt == "N")
1026
- raise "Exiting as operation with password greater than 14 characters not accepted"
1027
- elsif (password_promt == "Y")
1028
- @allow_long_password = "/yes"
1029
- else
1030
- raise "The input provided is incorrect."
1031
- end
1032
- end
1033
-
1034
- if locate_config_value(:tag_node_in_chef)
1035
- ui.warn("[DEPRECATED] --tag-node-in-chef option is deprecated. Use --chef-tag option instead.")
1036
- end
1037
- end
1038
-
1039
- def tags
1040
- tags = locate_config_value(:aws_tag)
1041
- if !tags.nil? and tags.length != tags.to_s.count('=')
1042
- ui.error("AWS Tags should be entered in a key = value pair")
1043
- exit 1
1044
- end
1045
- tags
1046
- end
1047
-
1048
- def eip_scope
1049
- if vpc_mode?
1050
- "vpc"
1051
- else
1052
- "standard"
1053
- end
1054
- end
1055
-
1056
- def ssl_config_user_data
1057
- user_related_commands = ""
1058
- winrm_user = locate_config_value(:winrm_user).split("\\")
1059
- if (winrm_user[0] == ".") || (winrm_user[0] == "") ||(winrm_user.length == 1)
1060
- user_related_commands = <<-EOH
1061
- net user /add #{locate_config_value(:winrm_user).delete('.\\')} #{windows_password} #{@allow_long_password};
1062
- net localgroup Administrators /add #{locate_config_value(:winrm_user).delete('.\\')};
1063
- EOH
1064
- end
1065
- <<-EOH
1066
- #{user_related_commands}
1067
- If (-Not (Get-Service WinRM | Where-Object {$_.status -eq "Running"})) {
1068
- winrm quickconfig -q
1069
- }
1070
- If (winrm e winrm/config/listener | Select-String -Pattern " Transport = HTTP\\b" -Quiet) {
1071
- winrm delete winrm/config/listener?Address=*+Transport=HTTP
1072
- }
1073
- $vm_name = invoke-restmethod -uri http://169.254.169.254/latest/meta-data/public-ipv4
1074
- If (-Not $vm_name) {
1075
- $vm_name = invoke-restmethod -uri http://169.254.169.254/latest/meta-data/local-ipv4
1076
- }
1077
-
1078
- $name = new-object -com "X509Enrollment.CX500DistinguishedName.1"
1079
- $name.Encode("CN=$vm_name", 0)
1080
- $key = new-object -com "X509Enrollment.CX509PrivateKey.1"
1081
- $key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
1082
- $key.KeySpec = 1
1083
- $key.Length = 2048
1084
- $key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
1085
- $key.MachineContext = 1
1086
- $key.Create()
1087
- $serverauthoid = new-object -com "X509Enrollment.CObjectId.1"
1088
- $serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1")
1089
- $ekuoids = new-object -com "X509Enrollment.CObjectIds.1"
1090
- $ekuoids.add($serverauthoid)
1091
- $ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"
1092
- $ekuext.InitializeEncode($ekuoids)
1093
- $cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1"
1094
- $cert.InitializeFromPrivateKey(2, $key, "")
1095
- $cert.Subject = $name
1096
- $cert.Issuer = $cert.Subject
1097
- $cert.NotBefore = get-date
1098
- $cert.NotAfter = $cert.NotBefore.AddYears(10)
1099
- $cert.X509Extensions.Add($ekuext)
1100
- $cert.Encode()
1101
- $enrollment = new-object -com "X509Enrollment.CX509Enrollment.1"
1102
- $enrollment.InitializeFromRequest($cert)
1103
- $certdata = $enrollment.CreateRequest(0)
1104
- $enrollment.InstallResponse(2, $certdata, 0, "")
1105
-
1106
- $thumbprint = (Get-ChildItem -Path cert:\\localmachine\\my | Where-Object {$_.Subject -match "$vm_name"}).Thumbprint;
1107
- $create_listener_cmd = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS '@{Hostname=`"$vm_name`";CertificateThumbprint=`"$thumbprint`"}'"
1108
- iex $create_listener_cmd
1109
- netsh advfirewall firewall add rule name="WinRM HTTPS" protocol=TCP dir=in Localport=5986 remoteport=any action=allow localip=any remoteip=any profile=any enable=yes
1110
- EOH
1111
- end
1112
-
1113
- def ssl_config_data_already_exist?
1114
- File.read(locate_config_value(:aws_user_data)).gsub(/\\\\/,"\\").include? ssl_config_user_data.strip
1115
- end
1116
-
1117
- def process_user_data(script_lines)
1118
- if !ssl_config_data_already_exist?
1119
- ps_start_tag = "<powershell>\n"
1120
- ps_end_tag = "</powershell>\n"
1121
- ps_start_tag_index = script_lines.index(ps_start_tag) || script_lines.index(ps_start_tag.strip)
1122
- ps_end_tag_index = script_lines.index(ps_end_tag) || script_lines.index(ps_end_tag.strip)
1123
- case
1124
- when ( ps_start_tag_index && !ps_end_tag_index ) || ( !ps_start_tag_index && ps_end_tag_index )
1125
- ui.error("Provided user_data file is invalid.")
1126
- exit 1
1127
- when ps_start_tag_index && ps_end_tag_index
1128
- script_lines[ps_end_tag_index] = ssl_config_user_data + ps_end_tag
1129
- when !ps_start_tag_index && !ps_end_tag_index
1130
- script_lines.insert(-1,"\n\n" + ps_start_tag + ssl_config_user_data + ps_end_tag)
1131
- end
1132
- end
1133
- script_lines
1134
- end
1135
-
1136
- def create_server_def
1137
- server_def = {
1138
- :image_id => locate_config_value(:image),
1139
- :groups => config[:security_groups],
1140
- :flavor_id => locate_config_value(:flavor),
1141
- :key_name => locate_config_value(:ssh_key_name),
1142
- :availability_zone => locate_config_value(:availability_zone),
1143
- :price => locate_config_value(:spot_price),
1144
- :request_type => locate_config_value(:spot_request_type)
1145
- }
1146
-
1147
- if primary_eni = locate_config_value(:primary_eni)
1148
- server_def[:network_interfaces] = [
1149
- {
1150
- :NetworkInterfaceId => primary_eni,
1151
- :DeviceIndex => "0"
1152
- }
1153
- ]
1154
- else
1155
- server_def[:security_group_ids] = locate_config_value(:security_group_ids)
1156
- server_def[:subnet_id] = locate_config_value(:subnet_id) if vpc_mode?
1157
- end
1158
-
1159
- server_def[:private_ip_address] = locate_config_value(:private_ip_address) if vpc_mode?
1160
- server_def[:placement_group] = locate_config_value(:placement_group)
1161
- server_def[:iam_instance_profile_name] = locate_config_value(:iam_instance_profile)
1162
- server_def[:tenancy] = "dedicated" if vpc_mode? and locate_config_value(:dedicated_instance)
1163
- server_def[:associate_public_ip] = locate_config_value(:associate_public_ip) if vpc_mode? and config[:associate_public_ip]
1164
-
1165
- if locate_config_value(:winrm_transport) == 'ssl'
1166
- if locate_config_value(:aws_user_data)
1167
- begin
1168
- user_data = File.readlines(locate_config_value(:aws_user_data))
1169
- if config[:create_ssl_listener]
1170
- user_data = process_user_data(user_data)
1171
- end
1172
- user_data = user_data.join
1173
- server_def.merge!(:user_data => user_data)
1174
- rescue
1175
- ui.warn("Cannot read #{locate_config_value(:aws_user_data)}: #{$!.inspect}. Ignoring option.")
1176
- end
1177
- else
1178
- if config[:create_ssl_listener]
1179
- server_def.merge!(:user_data => "<powershell>\n" + ssl_config_user_data + "</powershell>\n")
1180
- end
1181
- end
1182
- else
1183
- if locate_config_value(:aws_user_data)
1184
- begin
1185
- server_def.merge!(:user_data => File.read(locate_config_value(:aws_user_data)))
1186
- rescue
1187
- ui.warn("Cannot read #{locate_config_value(:aws_user_data)}: #{$!.inspect}. Ignoring option.")
1188
- end
1189
- end
1190
- end
1191
-
1192
- if config[:ebs_optimized]
1193
- server_def[:ebs_optimized] = "true"
1194
- else
1195
- server_def[:ebs_optimized] = "false"
1196
- end
1197
-
1198
- if ami.root_device_type == "ebs"
1199
- if locate_config_value(:ebs_encrypted)
1200
- ami_map = ami.block_device_mapping[1]
1201
- else
1202
- ami_map = ami.block_device_mapping.first
1203
- end
1204
-
1205
- ebs_size = begin
1206
- if config[:ebs_size]
1207
- Integer(config[:ebs_size]).to_s
1208
- else
1209
- ami_map["volumeSize"].to_s
1210
- end
1211
- rescue ArgumentError
1212
- puts "--ebs-size must be an integer"
1213
- msg opt_parser
1214
- exit 1
1215
- end
1216
- delete_term = if config[:ebs_no_delete_on_term]
1217
- "false"
1218
- else
1219
- ami_map["deleteOnTermination"]
1220
- end
1221
- iops_rate = begin
1222
- if config[:ebs_provisioned_iops]
1223
- Integer(config[:ebs_provisioned_iops]).to_s
1224
- else
1225
- ami_map["iops"].to_s
1226
- end
1227
- rescue ArgumentError
1228
- puts "--provisioned-iops must be an integer"
1229
- msg opt_parser
1230
- exit 1
1231
- end
1232
-
1233
- server_def[:block_device_mapping] =
1234
- [{
1235
- 'DeviceName' => ami_map["deviceName"],
1236
- 'Ebs.VolumeSize' => ebs_size,
1237
- 'Ebs.DeleteOnTermination' => delete_term,
1238
- 'Ebs.VolumeType' => config[:ebs_volume_type],
1239
- }]
1240
- server_def[:block_device_mapping].first['Ebs.Iops'] = iops_rate unless iops_rate.empty?
1241
- server_def[:block_device_mapping].first['Ebs.Encrypted'] = true if locate_config_value(:ebs_encrypted)
1242
- end
1243
-
1244
- (config[:ephemeral] || []).each_with_index do |device_name, i|
1245
- server_def[:block_device_mapping] = (server_def[:block_device_mapping] || []) << {'VirtualName' => "ephemeral#{i}", 'DeviceName' => device_name}
1246
- end
1247
-
1248
- ## cannot pass disable_api_termination option to the API when using spot instances ##
1249
- server_def[:disable_api_termination] = locate_config_value(:disable_api_termination) if locate_config_value(:spot_price).nil?
1250
-
1251
- server_def[:instance_initiated_shutdown_behavior] = locate_config_value(:instance_initiated_shutdown_behavior)
1252
- server_def[:chef_tag] = locate_config_value(:chef_tag)
1253
- server_def
1254
- end
1255
-
1256
- def wait_for_sshd(hostname)
1257
- ssh_gateway = get_ssh_gateway_for(hostname)
1258
- ssh_gateway ? wait_for_tunnelled_sshd(ssh_gateway, hostname) : wait_for_direct_sshd(hostname, config[:ssh_port])
1259
- end
1260
-
1261
- def get_ssh_gateway_for(hostname)
1262
- if config[:ssh_gateway]
1263
- # The ssh_gateway specified in the knife config (if any) takes
1264
- # precedence over anything in the SSH configuration
1265
- Chef::Log.debug("Using ssh gateway #{config[:ssh_gateway]} from knife config")
1266
- config[:ssh_gateway]
1267
- else
1268
- # Next, check if the SSH configuration has a ProxyCommand
1269
- # directive for this host. If there is one, parse out the
1270
- # host from the proxy command
1271
- ssh_proxy = Net::SSH::Config.for(hostname)[:proxy]
1272
- if ssh_proxy.respond_to?(:command_line_template)
1273
- # ssh gateway_hostname nc %h %p
1274
- proxy_pattern = /ssh\s+(\S+)\s+nc/
1275
- matchdata = proxy_pattern.match(ssh_proxy.command_line_template)
1276
- if matchdata.nil?
1277
- Chef::Log.debug("Unable to determine ssh gateway for '#{hostname}' from ssh config template: #{ssh_proxy.command_line_template}")
1278
- nil
1279
- else
1280
- # Return hostname extracted from command line template
1281
- Chef::Log.debug("Using ssh gateway #{matchdata[1]} from ssh config")
1282
- matchdata[1]
1283
- end
1284
- else
1285
- # Return nil if we cannot find an ssh_gateway
1286
- Chef::Log.debug("No ssh gateway found, making a direct connection")
1287
- nil
1288
- end
1289
- end
1290
- end
1291
-
1292
- def wait_for_tunnelled_sshd(ssh_gateway, hostname)
1293
- initial = true
1294
- print(".") until tunnel_test_ssh(ssh_gateway, hostname) {
1295
- if initial
1296
- initial = false
1297
- sleep (vpc_mode? ? 40 : 10)
1298
- else
1299
- sleep 10
1300
- end
1301
- puts("done")
1302
- }
1303
- end
1304
-
1305
- def tunnel_test_ssh(ssh_gateway, hostname, &block)
1306
- status = false
1307
- gateway = configure_ssh_gateway(ssh_gateway)
1308
- gateway.open(hostname, config[:ssh_port]) do |local_tunnel_port|
1309
- status = tcp_test_ssh('localhost', local_tunnel_port, &block)
1310
- end
1311
- status
1312
- rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
1313
- sleep 2
1314
- false
1315
- rescue Errno::EPERM, Errno::ETIMEDOUT
1316
- false
1317
- end
1318
-
1319
- def configure_ssh_gateway(ssh_gateway)
1320
- gw_host, gw_user = ssh_gateway.split('@').reverse
1321
- gw_host, gw_port = gw_host.split(':')
1322
- gateway_options = { :port => gw_port || 22 }
1323
-
1324
- # Load the SSH config for the SSH gateway host.
1325
- # Set the gateway user if it was not part of the
1326
- # SSH gateway string, and use any configured
1327
- # SSH keys.
1328
- ssh_gateway_config = Net::SSH::Config.for(gw_host)
1329
- gw_user ||= ssh_gateway_config[:user]
1330
-
1331
- # Always use the gateway keys from the SSH Config
1332
- gateway_keys = ssh_gateway_config[:keys]
1333
-
1334
- # Use the keys specificed on the command line if available (overrides SSH Config)
1335
- if config[:ssh_gateway_identity]
1336
- gateway_keys = Array(locate_config_value(:ssh_gateway_identity))
1337
- end
1338
-
1339
- unless gateway_keys.nil?
1340
- gateway_options[:keys] = gateway_keys
1341
- end
1342
-
1343
- Net::SSH::Gateway.new(gw_host, gw_user, gateway_options)
1344
- end
1345
-
1346
- def wait_for_direct_sshd(hostname, ssh_port)
1347
- initial = true
1348
- print(".") until tcp_test_ssh(hostname, ssh_port) {
1349
- if initial
1350
- initial = false
1351
- sleep (vpc_mode? ? 40 : 10)
1352
- else
1353
- sleep 10
1354
- end
1355
- puts("done")
1356
- }
1357
- end
1358
-
1359
- def subnet_public_ip_on_launch?
1360
- connection.subnets.get(server.subnet_id).map_public_ip_on_launch
1361
- end
1362
-
1363
- def ssh_connect_host
1364
- unless @ssh_connect_host
1365
- if config[:server_connect_attribute]
1366
- connect_attribute = config[:server_connect_attribute]
1367
- server.send(config[:server_connect_attribute])
1368
- elsif vpc_mode? && !(subnet_public_ip_on_launch? || config[:associate_public_ip] || config[:associate_eip])
1369
- connect_attribute = "private_ip_address"
1370
- server.private_ip_address
1371
- else
1372
- connect_attribute = server.dns_name ? "dns_name" : "public_ip_address"
1373
- server.send(connect_attribute)
1374
- end
1375
- @ssh_connect_host = server.send(connect_attribute)
1376
- end
1377
-
1378
- puts "\nSSH Target Address: #{@ssh_connect_host}(#{connect_attribute})"
1379
- @ssh_connect_host
1380
- end
1381
-
1382
- def create_tags(hashed_tags)
1383
- hashed_tags.each_pair do |key,val|
1384
- connection.tags.create :key => key, :value => val, :resource_id => @server.id
1385
- end
1386
- end
1387
-
1388
- def associate_eip(elastic_ip)
1389
- connection.associate_address(server.id, elastic_ip.public_ip, nil, elastic_ip.allocation_id)
1390
- @server.wait_for(locate_config_value(:aws_connection_timeout)) { public_ip_address == elastic_ip.public_ip }
1391
- end
1392
-
1393
- def validate_nics!
1394
- valid_nic_ids = connection.network_interfaces.all(
1395
- vpc_mode? ? { 'vpc-id' => vpc_id } : {}
1396
- ).map(&:network_interface_id)
1397
- invalid_nic_ids =
1398
- locate_config_value(:network_interfaces) - valid_nic_ids
1399
- return true if invalid_nic_ids.empty?
1400
- ui.error 'The following network interfaces are invalid: ' \
1401
- "#{invalid_nic_ids.join(', ')}"
1402
- exit 1
1403
- end
1404
-
1405
- def vpc_id
1406
- @vpc_id ||= begin
1407
- connection.subnets.get(locate_config_value(:subnet_id)).vpc_id
1408
- end
1409
- end
1410
-
1411
- def wait_for_nic_attachment
1412
- attached_nics_count = 0
1413
- until attached_nics_count ==
1414
- locate_config_value(:network_interfaces).count
1415
- attachment_nics =
1416
- locate_config_value(:network_interfaces).map do |nic_id|
1417
- connection.network_interfaces.get(nic_id).attachment['status']
1418
- end
1419
- attached_nics_count = attachment_nics.grep('attached').count
1420
- end
1421
- end
1422
-
1423
- def attach_nics
1424
- attachments = []
1425
- config[:network_interfaces].each_with_index do |nic_id, index|
1426
- attachments << connection.attach_network_interface(nic_id,
1427
- server.id,
1428
- index + 1).body
1429
- end
1430
- wait_for_nic_attachment
1431
- # rubocop:disable Style/RedundantReturn
1432
- return attachments
1433
- # rubocop:enable Style/RedundantReturn
1434
- end
1435
-
1436
- def enable_classic_link(vpc_id, security_group_ids)
1437
- connection.attach_classic_link_vpc(server.id, vpc_id, security_group_ids)
1438
- end
1439
-
1440
- def ssh_override_winrm
1441
- # unchanged ssh_user and changed winrm_user, override ssh_user
1442
- if locate_config_value(:ssh_user).eql?(options[:ssh_user][:default]) &&
1443
- !locate_config_value(:winrm_user).eql?(options[:winrm_user][:default])
1444
- config[:ssh_user] = locate_config_value(:winrm_user)
1445
- end
1446
- # unchanged ssh_port and changed winrm_port, override ssh_port
1447
- if locate_config_value(:ssh_port).eql?(options[:ssh_port][:default]) &&
1448
- !locate_config_value(:winrm_port).eql?(options[:winrm_port][:default])
1449
- config[:ssh_port] = locate_config_value(:winrm_port)
1450
- end
1451
- # unset ssh_password and set winrm_password, override ssh_password
1452
- if locate_config_value(:ssh_password).nil? &&
1453
- !locate_config_value(:winrm_password).nil?
1454
- config[:ssh_password] = locate_config_value(:winrm_password)
1455
- end
1456
- # unset identity_file and set kerberos_keytab_file, override identity_file
1457
- if locate_config_value(:identity_file).nil? &&
1458
- !locate_config_value(:kerberos_keytab_file).nil?
1459
- config[:identity_file] = locate_config_value(:kerberos_keytab_file)
1460
- end
1461
- end
1462
-
1463
- def tcp_test_winrm(ip_addr, port)
1464
- tcp_socket = TCPSocket.new(ip_addr, port)
1465
- yield
1466
- true
1467
- rescue SocketError
1468
- sleep 2
1469
- false
1470
- rescue Errno::ETIMEDOUT
1471
- false
1472
- rescue Errno::EPERM
1473
- false
1474
- rescue Errno::ECONNREFUSED
1475
- sleep 2
1476
- false
1477
- rescue Errno::EHOSTUNREACH
1478
- sleep 2
1479
- false
1480
- rescue Errno::ENETUNREACH
1481
- sleep 2
1482
- false
1483
- ensure
1484
- tcp_socket && tcp_socket.close
1485
- end
1486
-
1487
- def tcp_test_ssh(hostname, ssh_port)
1488
- tcp_socket = TCPSocket.new(hostname, ssh_port)
1489
- readable = IO.select([tcp_socket], nil, nil, 5)
1490
- if readable
1491
- ssh_banner = tcp_socket.gets
1492
- if ssh_banner.nil? or ssh_banner.empty?
1493
- false
1494
- else
1495
- Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{ssh_banner}")
1496
- yield
1497
- true
1498
- end
1499
- else
1500
- false
1501
- end
1502
- rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ENOTCONN, IOError
1503
- Chef::Log.debug("ssh failed to connect: #{hostname}")
1504
- sleep 2
1505
- false
1506
- rescue Errno::EPERM, Errno::ETIMEDOUT
1507
- Chef::Log.debug("ssh timed out: #{hostname}")
1508
- false
1509
- # This happens on some mobile phone networks
1510
- rescue Errno::ECONNRESET
1511
- Chef::Log.debug("ssh reset its connection: #{hostname}")
1512
- sleep 2
1513
- false
1514
- ensure
1515
- tcp_socket && tcp_socket.close
1516
- end
1517
-
1518
- def decrypt_admin_password(encoded_password, key)
1519
- require 'base64'
1520
- require 'openssl'
1521
- private_key = OpenSSL::PKey::RSA.new(key)
1522
- encrypted_password = Base64.decode64(encoded_password)
1523
- password = private_key.private_decrypt(encrypted_password)
1524
- password
1525
- end
1526
-
1527
- def check_windows_password_available(server_id)
1528
- sleep 10
1529
- response = connection.get_password_data(server_id)
1530
- if not response.body["passwordData"]
1531
- return false
1532
- end
1533
- response.body["passwordData"]
1534
- end
1535
-
1536
- def windows_password
1537
- if not locate_config_value(:winrm_password)
1538
- if locate_config_value(:identity_file)
1539
- print "\n#{ui.color("Waiting for Windows Admin password to be available", :magenta)}"
1540
- print(".") until check_windows_password_available(@server.id) {
1541
- puts("done")
1542
- }
1543
- response = connection.get_password_data(@server.id)
1544
- data = File.read(locate_config_value(:identity_file))
1545
- config[:winrm_password] = decrypt_admin_password(response.body["passwordData"], data)
1546
- else
1547
- ui.error("Cannot find SSH Identity file, required to fetch dynamically generated password")
1548
- exit 1
1549
- end
1550
- else
1551
- locate_config_value(:winrm_password)
1552
- end
1553
- end
1554
-
1555
- def load_winrm_deps
1556
- require 'winrm'
1557
- require 'chef/knife/winrm'
1558
- require 'chef/knife/bootstrap_windows_winrm'
1559
- require 'chef/knife/bootstrap_windows_ssh'
1560
- require 'chef/knife/core/windows_bootstrap_context'
1561
- end
1562
-
1563
- #Returns the name of node after evaluation of server id if %s is present.
1564
- #Eg: "Test-%s" will return "Test-i-12345" in case the instance id is i-12345
1565
- def evaluate_node_name(node_name)
1566
- return node_name%server.id
1567
- end
1568
-
1569
- def create_volume_tags(hashed_volume_tags)
1570
- hashed_volume_tags.each_pair do |key,val|
1571
- connection.tags.create :key => key, :value => val, :resource_id => @server.block_device_mapping.first['volumeId']
1572
- end
1573
- end
1574
-
1575
- end
1576
- end
1577
- end
1
+ #
2
+ # Author:: Adam Jacob (<adam@chef.io>)
3
+ # Author:: Seth Chisamore (<schisamo@chef.io>)
4
+ # Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'chef/knife/ec2_base'
21
+ require 'chef/knife/s3_source'
22
+ require 'chef/knife/winrm_base'
23
+ require 'chef/knife/bootstrap_windows_base'
24
+
25
+ class Chef
26
+ class Knife
27
+ class Ec2ServerCreate < Knife
28
+
29
+ include Knife::Ec2Base
30
+ include Knife::WinrmBase
31
+ include Knife::BootstrapWindowsBase
32
+ deps do
33
+ require 'tempfile'
34
+ require 'fog/aws'
35
+ require 'uri'
36
+ require 'readline'
37
+ require 'chef/json_compat'
38
+ require 'chef/knife/bootstrap'
39
+ Chef::Knife::Bootstrap.load_deps
40
+ end
41
+
42
+ banner "knife ec2 server create (options)"
43
+
44
+ attr_accessor :initial_sleep_delay
45
+ attr_reader :server
46
+
47
+ option :flavor,
48
+ :short => "-f FLAVOR",
49
+ :long => "--flavor FLAVOR",
50
+ :description => "The flavor of server (m1.small, m1.medium, etc)",
51
+ :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f }
52
+
53
+ option :image,
54
+ :short => "-I IMAGE",
55
+ :long => "--image IMAGE",
56
+ :description => "The AMI for the server",
57
+ :proc => Proc.new { |i| Chef::Config[:knife][:image] = i }
58
+
59
+ option :iam_instance_profile,
60
+ :long => "--iam-profile NAME",
61
+ :description => "The IAM instance profile to apply to this instance."
62
+
63
+ option :security_groups,
64
+ :short => "-G X,Y,Z",
65
+ :long => "--groups X,Y,Z",
66
+ :description => "The security groups for this server; not allowed when using VPC",
67
+ :proc => Proc.new { |groups| groups.split(',') }
68
+
69
+ option :security_group_ids,
70
+ :long => "--security-group-ids 'X,Y,Z'",
71
+ :description => "The security group ids for this server; required when using VPC. Provide values in format --security-group-ids 'X,Y,Z'. [DEPRECATED] This option will be removed in future release. Use the new --security-group-id option. ",
72
+ :proc => Proc.new { |security_group_ids|
73
+ ui.warn('[DEPRECATED] This option will be removed in future release. Use the new --security-group-id option multiple times when specifying multiple groups for e.g. -g sg-e985168d -g sg-e7f06383 -g sg-ec1b7e88.')
74
+ if security_group_ids.gsub(' ', '').split(',').size > 1
75
+ Chef::Config[:knife][:security_group_ids] = security_group_ids.gsub(' ', '').split(',')
76
+ else
77
+ Chef::Config[:knife][:security_group_ids] ||= []
78
+ Chef::Config[:knife][:security_group_ids].push(security_group_ids)
79
+ Chef::Config[:knife][:security_group_ids]
80
+ end
81
+ }
82
+
83
+ option :security_group_id,
84
+ :short => "-g SECURITY_GROUP_ID",
85
+ :long => "--security-group-id ID",
86
+ :description => "The security group id for this server; required when using VPC. Use the --security-group-id option multiple times when specifying multiple groups for e.g. -g sg-e985168d -g sg-e7f06383 -g sg-ec1b7e88.",
87
+ :proc => Proc.new { |security_group_id|
88
+ Chef::Config[:knife][:security_group_ids] ||= []
89
+ Chef::Config[:knife][:security_group_ids].push(security_group_id)
90
+ Chef::Config[:knife][:security_group_ids]
91
+ }
92
+
93
+ option :associate_eip,
94
+ :long => "--associate-eip IP_ADDRESS",
95
+ :description => "Associate existing elastic IP address with instance after launch"
96
+
97
+ option :dedicated_instance,
98
+ :long => "--dedicated_instance",
99
+ :description => "Launch as a Dedicated instance (VPC ONLY)"
100
+
101
+ option :placement_group,
102
+ :long => "--placement-group PLACEMENT_GROUP",
103
+ :description => "The placement group to place a cluster compute instance",
104
+ :proc => Proc.new { |pg| Chef::Config[:knife][:placement_group] = pg }
105
+
106
+ option :primary_eni,
107
+ :long => "--primary-eni ENI_ID",
108
+ :description => "Specify a pre-existing eni to use when building the instance."
109
+
110
+ option :tags,
111
+ :short => "-T T=V[,T=V,...]",
112
+ :long => "--tags Tag=Value[,Tag=Value...]",
113
+ :description => "The tags for this server. [DEPRECATED] Use --aws-tag instead.",
114
+ :proc => Proc.new { |tags|
115
+ Chef::Log.warn("[DEPRECATED] --tags option is deprecated. Use --aws-tag option instead.")
116
+ tags.split(',')
117
+ }
118
+
119
+ option :availability_zone,
120
+ :short => "-Z ZONE",
121
+ :long => "--availability-zone ZONE",
122
+ :description => "The Availability Zone",
123
+ :proc => Proc.new { |key| Chef::Config[:knife][:availability_zone] = key }
124
+
125
+ option :chef_node_name,
126
+ :short => "-N NAME",
127
+ :long => "--node-name NAME",
128
+ :description => "The Chef node name for your new node",
129
+ :proc => Proc.new { |key| Chef::Config[:knife][:chef_node_name] = key }
130
+
131
+ option :ssh_key_name,
132
+ :short => "-S KEY",
133
+ :long => "--ssh-key KEY",
134
+ :description => "The AWS SSH key id",
135
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_key_name] = key }
136
+
137
+ option :ssh_user,
138
+ :short => "-x USERNAME",
139
+ :long => "--ssh-user USERNAME",
140
+ :description => "The ssh username",
141
+ :default => "root"
142
+
143
+ option :ssh_password,
144
+ :short => "-P PASSWORD",
145
+ :long => "--ssh-password PASSWORD",
146
+ :description => "The ssh password"
147
+
148
+ option :ssh_port,
149
+ :short => "-p PORT",
150
+ :long => "--ssh-port PORT",
151
+ :description => "The ssh port",
152
+ :default => "22",
153
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
154
+
155
+ option :ssh_gateway,
156
+ :short => "-w GATEWAY",
157
+ :long => "--ssh-gateway GATEWAY",
158
+ :description => "The ssh gateway server. Any proxies configured in your ssh config are automatically used by default.",
159
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
160
+
161
+ option :ssh_gateway_identity,
162
+ :long => "--ssh-gateway-identity IDENTITY_FILE",
163
+ :description => "The private key for ssh gateway server",
164
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway_identity] = key }
165
+
166
+ option :identity_file,
167
+ :short => "-i IDENTITY_FILE",
168
+ :long => "--identity-file IDENTITY_FILE",
169
+ :description => "The SSH identity file used for authentication"
170
+
171
+ option :prerelease,
172
+ :long => "--prerelease",
173
+ :description => "Install the pre-release chef gems"
174
+
175
+ option :bootstrap_version,
176
+ :long => "--bootstrap-version VERSION",
177
+ :description => "The version of Chef to install",
178
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
179
+
180
+ option :bootstrap_proxy,
181
+ :long => "--bootstrap-proxy PROXY_URL",
182
+ :description => "The proxy server for the node being bootstrapped",
183
+ :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
184
+
185
+ option :distro,
186
+ :short => "-d DISTRO",
187
+ :long => "--distro DISTRO",
188
+ :description => "Bootstrap a distro using a template. [DEPRECATED] Use --bootstrap-template option instead.",
189
+ :proc => Proc.new { |v|
190
+ Chef::Log.warn("[DEPRECATED] -d / --distro option is deprecated. Use --bootstrap-template option instead.")
191
+ v
192
+ }
193
+
194
+ option :template_file,
195
+ :long => "--template-file TEMPLATE",
196
+ :description => "Full path to location of template to use. [DEPRECATED] Use -t / --bootstrap-template option instead.",
197
+ :proc => Proc.new { |v|
198
+ Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use -t / --bootstrap-template option instead.")
199
+ v
200
+ }
201
+
202
+ option :bootstrap_template,
203
+ :long => "--bootstrap-template TEMPLATE",
204
+ :description => "Bootstrap Chef using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates."
205
+
206
+ option :ebs_size,
207
+ :long => "--ebs-size SIZE",
208
+ :description => "The size of the EBS volume in GB, for EBS-backed instances"
209
+
210
+ option :ebs_optimized,
211
+ :long => "--ebs-optimized",
212
+ :description => "Enabled optimized EBS I/O"
213
+
214
+ option :ebs_no_delete_on_term,
215
+ :long => "--ebs-no-delete-on-term",
216
+ :description => "Do not delete EBS volume on instance termination"
217
+
218
+ option :run_list,
219
+ :short => "-r RUN_LIST",
220
+ :long => "--run-list RUN_LIST",
221
+ :description => "Comma separated list of roles/recipes to apply",
222
+ :proc => lambda { |o| o.split(/[\s,]+/) },
223
+ :default => []
224
+
225
+ option :secret,
226
+ :long => "--secret ",
227
+ :description => "The secret key to use to encrypt data bag item values",
228
+ :proc => lambda { |s| Chef::Config[:knife][:secret] = s }
229
+
230
+ option :secret_file,
231
+ :long => "--secret-file SECRET_FILE",
232
+ :description => "A file containing the secret key to use to encrypt data bag item values",
233
+ :proc => lambda { |sf| Chef::Config[:knife][:secret_file] = sf }
234
+
235
+ option :s3_secret,
236
+ :long => '--s3-secret S3_SECRET_URL',
237
+ :description => 'S3 URL (e.g. s3://bucket/file) for the encrypted_data_bag_secret_file',
238
+ :proc => lambda { |url| Chef::Config[:knife][:s3_secret] = url }
239
+
240
+ option :subnet_id,
241
+ :long => "--subnet SUBNET-ID",
242
+ :description => "create node in this Virtual Private Cloud Subnet ID (implies VPC mode)",
243
+ :proc => Proc.new { |key| Chef::Config[:knife][:subnet_id] = key }
244
+
245
+ option :private_ip_address,
246
+ :long => "--private-ip-address IP-ADDRESS",
247
+ :description => "allows to specify the private IP address of the instance in VPC mode",
248
+ :proc => Proc.new { |ip| Chef::Config[:knife][:private_ip_address] = ip }
249
+
250
+ option :host_key_verify,
251
+ :long => "--[no-]host-key-verify",
252
+ :description => "Verify host key, enabled by default.",
253
+ :boolean => true,
254
+ :default => true
255
+
256
+ option :bootstrap_protocol,
257
+ :long => "--bootstrap-protocol protocol",
258
+ :description => "protocol to bootstrap windows servers. options: winrm/ssh",
259
+ :proc => Proc.new { |key| Chef::Config[:knife][:bootstrap_protocol] = key },
260
+ :default => nil
261
+
262
+ option :fqdn,
263
+ :long => "--fqdn FQDN",
264
+ :description => "Pre-defined FQDN. This is used for Kerberos Authentication purpose only",
265
+ :proc => Proc.new { |key| Chef::Config[:knife][:fqdn] = key },
266
+ :default => nil
267
+
268
+ option :aws_user_data,
269
+ :long => "--user-data USER_DATA_FILE",
270
+ :short => "-u USER_DATA_FILE",
271
+ :description => "The EC2 User Data file to provision the instance with",
272
+ :proc => Proc.new { |m| Chef::Config[:knife][:aws_user_data] = m },
273
+ :default => nil
274
+
275
+ option :hint,
276
+ :long => "--hint HINT_NAME[=HINT_FILE]",
277
+ :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
278
+ :proc => Proc.new { |h|
279
+ Chef::Config[:knife][:hints] ||= {}
280
+ name, path = h.split("=")
281
+ Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
282
+ }
283
+
284
+ option :ephemeral,
285
+ :long => "--ephemeral EPHEMERAL_DEVICES",
286
+ :description => "Comma separated list of device locations (eg - /dev/sdb) to map ephemeral devices",
287
+ :proc => lambda { |o| o.split(/[\s,]+/) },
288
+ :default => []
289
+
290
+ option :server_connect_attribute,
291
+ :long => "--server-connect-attribute ATTRIBUTE",
292
+ :short => "-a ATTRIBUTE",
293
+ :description => "The EC2 server attribute to use for the SSH connection if necessary, e.g. public_ip_address or private_ip_address.",
294
+ :default => nil
295
+
296
+ option :associate_public_ip,
297
+ :long => "--associate-public-ip",
298
+ :description => "Associate public ip to VPC instance.",
299
+ :boolean => true,
300
+ :default => false
301
+
302
+ option :ebs_volume_type,
303
+ :long => "--ebs-volume-type TYPE",
304
+ :description => "Possible values are standard (magnetic) | io1 | gp2 | sc1 | st1. Default is gp2",
305
+ :proc => Proc.new { |key| Chef::Config[:knife][:ebs_volume_type] = key },
306
+ :default => "gp2"
307
+
308
+ option :ebs_provisioned_iops,
309
+ :long => "--provisioned-iops IOPS",
310
+ :description => "IOPS rate, only used when ebs volume type is 'io1'",
311
+ :proc => Proc.new { |key| Chef::Config[:knife][:provisioned_iops] = key },
312
+ :default => nil
313
+
314
+ option :auth_timeout,
315
+ :long => "--windows-auth-timeout MINUTES",
316
+ :description => "The maximum time in minutes to wait to for authentication over the transport to the node to succeed. The default value is 25 minutes.",
317
+ :default => 25
318
+
319
+ option :validation_key_url,
320
+ :long => "--validation-key-url URL",
321
+ :description => "Path to the validation key",
322
+ :proc => proc { |m| Chef::Config[:validation_key_url] = m }
323
+
324
+ option :ebs_encrypted,
325
+ :long => "--ebs-encrypted",
326
+ :description => "Enables EBS volume encryption",
327
+ :boolean => true,
328
+ :default => false
329
+
330
+ option :spot_price,
331
+ :long => "--spot-price PRICE",
332
+ :description => "The maximum hourly USD price for the instance",
333
+ :default => nil
334
+
335
+ option :spot_request_type,
336
+ :long => "--spot-request-type TYPE",
337
+ :description => "The Spot Instance request type. Possible values are 'one-time' and 'persistent', default value is 'one-time'",
338
+ :default => "one-time"
339
+
340
+ option :spot_wait_mode,
341
+ :long => "--spot-wait-mode MODE",
342
+ :description =>
343
+ "Whether we should wait for spot request fulfillment. Could be 'wait', 'exit', or " \
344
+ "'prompt' (default). For any of the above mentioned choices, ('wait') - if the " \
345
+ "instance does not get allocated before the command itself times-out or ('exit') the " \
346
+ "user needs to manually bootstrap the instance in the future after it gets allocated.",
347
+ :default => "prompt"
348
+
349
+ option :aws_connection_timeout,
350
+ :long => "--aws-connection-timeout MINUTES",
351
+ :description => "The maximum time in minutes to wait to for aws connection. Default is 10 min",
352
+ :proc => proc {|t| t = t.to_i * 60; Chef::Config[:aws_connection_timeout] = t},
353
+ :default => 600
354
+
355
+ option :node_ssl_verify_mode,
356
+ :long => "--node-ssl-verify-mode [peer|none]",
357
+ :description => "Whether or not to verify the SSL cert for all HTTPS requests.",
358
+ :proc => Proc.new { |v|
359
+ valid_values = ["none", "peer"]
360
+ unless valid_values.include?(v)
361
+ raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
362
+ end
363
+ }
364
+
365
+ option :node_verify_api_cert,
366
+ :long => "--[no-]node-verify-api-cert",
367
+ :description => "Verify the SSL cert for HTTPS requests to the Chef server API.",
368
+ :boolean => true
369
+
370
+ option :bootstrap_no_proxy,
371
+ :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]",
372
+ :description => "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode",
373
+ :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np }
374
+
375
+ option :bootstrap_url,
376
+ :long => "--bootstrap-url URL",
377
+ :description => "URL to a custom installation script",
378
+ :proc => Proc.new { |u| Chef::Config[:knife][:bootstrap_url] = u }
379
+
380
+ option :bootstrap_install_command,
381
+ :long => "--bootstrap-install-command COMMANDS",
382
+ :description => "Custom command to install chef-client",
383
+ :proc => Proc.new { |ic| Chef::Config[:knife][:bootstrap_install_command] = ic }
384
+
385
+ option :bootstrap_wget_options,
386
+ :long => "--bootstrap-wget-options OPTIONS",
387
+ :description => "Add options to wget when installing chef-client",
388
+ :proc => Proc.new { |wo| Chef::Config[:knife][:bootstrap_wget_options] = wo }
389
+
390
+ option :bootstrap_curl_options,
391
+ :long => "--bootstrap-curl-options OPTIONS",
392
+ :description => "Add options to curl when install chef-client",
393
+ :proc => Proc.new { |co| Chef::Config[:knife][:bootstrap_curl_options] = co }
394
+
395
+ option :bootstrap_vault_file,
396
+ :long => '--bootstrap-vault-file VAULT_FILE',
397
+ :description => 'A JSON file with a list of vault(s) and item(s) to be updated'
398
+
399
+ option :bootstrap_vault_json,
400
+ :long => '--bootstrap-vault-json VAULT_JSON',
401
+ :description => 'A JSON string with the vault(s) and item(s) to be updated'
402
+
403
+ option :bootstrap_vault_item,
404
+ :long => '--bootstrap-vault-item VAULT_ITEM',
405
+ :description => 'A single vault and item to update as "vault:item"',
406
+ :proc => Proc.new { |i|
407
+ (vault, item) = i.split(/:/)
408
+ Chef::Config[:knife][:bootstrap_vault_item] ||= {}
409
+ Chef::Config[:knife][:bootstrap_vault_item][vault] ||= []
410
+ Chef::Config[:knife][:bootstrap_vault_item][vault].push(item)
411
+ Chef::Config[:knife][:bootstrap_vault_item]
412
+ }
413
+
414
+ option :use_sudo_password,
415
+ :long => "--use-sudo-password",
416
+ :description => "Execute the bootstrap via sudo with password",
417
+ :boolean => false
418
+
419
+ option :forward_agent,
420
+ :short => "-A",
421
+ :long => "--forward-agent",
422
+ :description => "Enable SSH agent forwarding",
423
+ :boolean => true
424
+
425
+ option :create_ssl_listener,
426
+ :long => "--[no-]create-ssl-listener",
427
+ :description => "Create ssl listener, enabled by default.",
428
+ :boolean => true,
429
+ :default => true
430
+
431
+ option :network_interfaces,
432
+ :short => '-n',
433
+ :long => '--attach-network-interface ENI1,ENI2',
434
+ :description => 'Attach additional network interfaces during bootstrap',
435
+ :proc => proc { |nics| nics.split(',') }
436
+
437
+ option :classic_link_vpc_id,
438
+ :long => "--classic-link-vpc-id VPC_ID",
439
+ :description => "Enable ClassicLink connection with a VPC"
440
+
441
+ option :classic_link_vpc_security_group_ids,
442
+ :long => "--classic-link-vpc-security-groups-ids X,Y,Z",
443
+ :description => "Comma-separated list of security group ids for ClassicLink",
444
+ :proc => Proc.new { |groups| groups.split(',') }
445
+
446
+ option :disable_api_termination,
447
+ :long => "--disable-api-termination",
448
+ :description => "Disable termination of the instance using the Amazon EC2 console, CLI and API.",
449
+ :boolean => true,
450
+ :default => false
451
+
452
+ option :volume_tags,
453
+ :long => "--volume-tags Tag=Value[,Tag=Value...]",
454
+ :description => "Tag the Root volume",
455
+ :proc => Proc.new { |volume_tags| volume_tags.split(',') }
456
+
457
+ option :tag_node_in_chef,
458
+ :long => "--tag-node-in-chef",
459
+ :description => "Flag for tagging node in ec2 and chef both. [DEPRECATED] Use --chef-tag instead.",
460
+ :proc => Proc.new { |v|
461
+ Chef::Log.warn("[DEPRECATED] --tag-node-in-chef option is deprecated. Use --chef-tag option instead.")
462
+ v
463
+ },
464
+ :boolean => true,
465
+ :default => false
466
+
467
+ option :instance_initiated_shutdown_behavior,
468
+ :long => "--instance-initiated-shutdown-behavior SHUTDOWN_BEHAVIOR",
469
+ :description => "Indicates whether an instance stops or terminates when you initiate shutdown from the instance. Possible values are 'stop' and 'terminate', default is 'stop'."
470
+
471
+ option :chef_tag,
472
+ :long => "--chef-tag CHEF_TAG",
473
+ :description => "Use to tag the node in chef server; Provide --chef-tag option multiple times when specifying multiple tags e.g. --chef-tag tag1 --chef-tag tag2.",
474
+ :proc => Proc.new { |chef_tag|
475
+ Chef::Config[:knife][:chef_tag] ||= []
476
+ Chef::Config[:knife][:chef_tag].push(chef_tag)
477
+ Chef::Config[:knife][:chef_tag]
478
+ }
479
+
480
+ option :aws_tag,
481
+ :long => "--aws-tag AWS_TAG",
482
+ :description => "AWS tag for this server; Use the --aws-tag option multiple times when specifying multiple tags e.g. --aws-tag key1=value1 --aws-tag key2=value2.",
483
+ :proc => Proc.new { |aws_tag|
484
+ Chef::Config[:knife][:aws_tag] ||= []
485
+ Chef::Config[:knife][:aws_tag].push(aws_tag)
486
+ Chef::Config[:knife][:aws_tag]
487
+ }
488
+
489
+ def run
490
+ $stdout.sync = true
491
+ validate!
492
+
493
+ requested_elastic_ip = config[:associate_eip] if config[:associate_eip]
494
+
495
+ # For VPC EIP assignment we need the allocation ID so fetch full EIP details
496
+ elastic_ip = connection.addresses.detect{|addr| addr if addr.public_ip == requested_elastic_ip}
497
+
498
+ if locate_config_value(:spot_price)
499
+ server_def = create_server_def
500
+ server_def[:groups] = server_def[:security_group_ids] if vpc_mode?
501
+ spot_request = connection.spot_requests.create(server_def)
502
+ msg_pair("Spot Request ID", spot_request.id)
503
+ msg_pair("Spot Request Type", spot_request.request_type)
504
+ msg_pair("Spot Price", spot_request.price)
505
+
506
+ case config[:spot_wait_mode]
507
+ when 'prompt', '', nil
508
+ wait_msg = "Do you want to wait for Spot Instance Request fulfillment? (Y/N) \n"
509
+ wait_msg += "Y - Wait for Spot Instance request fulfillment\n"
510
+ wait_msg += "N - Do not wait for Spot Instance request fulfillment. "
511
+ wait_msg += ui.color("[WARN :: Request would be alive on AWS ec2 side but execution of Chef Bootstrap on the target instance will get skipped.]\n", :red, :bold)
512
+ wait_msg += ui.color("\n[WARN :: For any of the above mentioned choices, (Y) - if the instance does not get allocated before the command itself times-out or (N) - user decides to exit, then in both cases user needs to manually bootstrap the instance in the future after it gets allocated.]\n\n", :cyan, :bold)
513
+ confirm(wait_msg)
514
+ when 'wait'
515
+ # wait for the node and run Chef bootstrap
516
+ when 'exit'
517
+ ui.color("The 'exit' option was specified for --spot-wait-mode, exiting.", :cyan)
518
+ exit
519
+ else
520
+ raise "Invalid value for --spot-wait-mode: '#{config[:spot_wait_mode]}', " \
521
+ "valid values: wait, exit, prompt"
522
+ end
523
+
524
+ print ui.color("Waiting for Spot Request fulfillment: ", :cyan)
525
+ spot_request.wait_for do
526
+ @spinner ||= %w{| / - \\}
527
+ print "\b" + @spinner.rotate!.first
528
+ ready?
529
+ end
530
+ puts("\n")
531
+ @server = connection.servers.get(spot_request.instance_id)
532
+ else
533
+ begin
534
+ @server = connection.servers.create(create_server_def)
535
+ rescue => error
536
+ error.message.sub("download completed, but downloaded file not found", "Verify that you have public internet access.")
537
+ ui.error error.message
538
+ Chef::Log.debug("#{error.backtrace.join("\n")}")
539
+ exit
540
+ end
541
+ end
542
+
543
+ hashed_tags={}
544
+ tags.map{ |t| key,val=t.split('='); hashed_tags[key]=val} unless tags.nil?
545
+
546
+ # Always set the Name tag
547
+ unless hashed_tags.keys.include? "Name"
548
+ if locate_config_value(:chef_node_name)
549
+ hashed_tags["Name"] = evaluate_node_name(locate_config_value(:chef_node_name))
550
+ else
551
+ hashed_tags["Name"] = server.id
552
+ end
553
+ end
554
+
555
+ printed_aws_tags = hashed_tags.map{ |tag, val| "#{tag}: #{val}" }.join(", ")
556
+
557
+ hashed_volume_tags={}
558
+ volume_tags = locate_config_value(:volume_tags)
559
+ volume_tags.map{ |t| key,val=t.split('='); hashed_volume_tags[key]=val} unless volume_tags.nil?
560
+ printed_volume_tags = hashed_volume_tags.map{ |tag, val| "#{tag}: #{val}" }.join(", ")
561
+
562
+ msg_pair("Instance ID", @server.id)
563
+ msg_pair("Flavor", @server.flavor_id)
564
+ msg_pair("Image", @server.image_id)
565
+ msg_pair("Region", connection.instance_variable_get(:@region))
566
+ msg_pair("Availability Zone", @server.availability_zone)
567
+
568
+ # If we don't specify a security group or security group id, Fog will
569
+ # pick the appropriate default one. In case of a VPC we don't know the
570
+ # default security group id at this point unless we look it up, hence
571
+ # 'default' is printed if no id was specified.
572
+ printed_security_groups = "default"
573
+ printed_security_groups = @server.groups.join(", ") if @server.groups
574
+ msg_pair("Security Groups", printed_security_groups) unless vpc_mode? or (@server.groups.nil? and @server.security_group_ids)
575
+
576
+ printed_security_group_ids = "default"
577
+ printed_security_group_ids = @server.security_group_ids.join(", ") if @server.security_group_ids
578
+ msg_pair("Security Group Ids", printed_security_group_ids) if vpc_mode? or @server.security_group_ids
579
+
580
+ msg_pair("IAM Profile", locate_config_value(:iam_instance_profile))
581
+
582
+ msg_pair("AWS Tags", printed_aws_tags)
583
+ msg_pair("Volume Tags", printed_volume_tags)
584
+ msg_pair("SSH Key", @server.key_name)
585
+
586
+ print "\n#{ui.color("Waiting for EC2 to create the instance", :magenta)}"
587
+
588
+ # wait for instance to come up before acting against it
589
+ @server.wait_for(locate_config_value(:aws_connection_timeout)) { print "."; ready? }
590
+
591
+ puts("\n")
592
+
593
+ # occasionally 'ready?' isn't, so retry a couple times if needed.
594
+ tries = 6
595
+ begin
596
+ create_tags(hashed_tags) unless hashed_tags.empty?
597
+ create_volume_tags(hashed_volume_tags) unless hashed_volume_tags.empty?
598
+ associate_eip(elastic_ip) if config[:associate_eip]
599
+ enable_classic_link(config[:classic_link_vpc_id], config[:classic_link_vpc_security_group_ids]) if config[:classic_link_vpc_id]
600
+ rescue Fog::Compute::AWS::NotFound, Fog::Errors::Error
601
+ raise if (tries -= 1) <= 0
602
+ ui.warn("server not ready, retrying tag application (retries left: #{tries})")
603
+ sleep 5
604
+ retry
605
+ end
606
+
607
+ attach_nics if config[:network_interfaces]
608
+
609
+ if vpc_mode?
610
+ msg_pair("Subnet ID", @server.subnet_id)
611
+ msg_pair("Tenancy", @server.tenancy)
612
+ if config[:associate_public_ip]
613
+ msg_pair("Public DNS Name", @server.dns_name)
614
+ end
615
+ if elastic_ip
616
+ msg_pair("Public IP Address", @server.public_ip_address)
617
+ end
618
+ else
619
+ msg_pair("Public DNS Name", @server.dns_name)
620
+ msg_pair("Public IP Address", @server.public_ip_address)
621
+ msg_pair("Private DNS Name", @server.private_dns_name)
622
+ end
623
+ msg_pair("Private IP Address", @server.private_ip_address)
624
+
625
+ if Chef::Config[:knife][:validation_key_url]
626
+ download_validation_key(validation_key_path)
627
+ Chef::Config[:validation_key] = validation_key_path
628
+ end
629
+
630
+ #Check if Server is Windows or Linux
631
+ if is_image_windows?
632
+ protocol = locate_config_value(:bootstrap_protocol)
633
+ protocol ||= 'winrm'
634
+ if protocol == 'winrm'
635
+ load_winrm_deps
636
+ print "\n#{ui.color("Waiting for winrm access to become available", :magenta)}"
637
+ print(".") until tcp_test_winrm(ssh_connect_host, locate_config_value(:winrm_port)) {
638
+ sleep 10
639
+ puts("done")
640
+ }
641
+ else
642
+ print "\n#{ui.color("Waiting for sshd access to become available", :magenta)}"
643
+ #If FreeSSHd, winsshd etc are available
644
+ print(".") until tcp_test_ssh(ssh_connect_host, config[:ssh_port]) {
645
+ sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10)
646
+ puts("done")
647
+ }
648
+ ssh_override_winrm
649
+ end
650
+ bootstrap_for_windows_node(@server, ssh_connect_host).run
651
+ else
652
+ print "\n#{ui.color("Waiting for sshd access to become available", :magenta)}"
653
+ wait_for_sshd(ssh_connect_host)
654
+ ssh_override_winrm
655
+ bootstrap_for_linux_node(@server, ssh_connect_host).run
656
+ end
657
+
658
+ puts "\n"
659
+ msg_pair("Instance ID", @server.id)
660
+ msg_pair("Flavor", @server.flavor_id)
661
+ msg_pair("Placement Group", @server.placement_group) unless @server.placement_group.nil?
662
+ msg_pair("Image", @server.image_id)
663
+ msg_pair("Region", connection.instance_variable_get(:@region))
664
+ msg_pair("Availability Zone", @server.availability_zone)
665
+ msg_pair("Security Groups", printed_security_groups) unless vpc_mode? or (@server.groups.nil? and @server.security_group_ids)
666
+ msg_pair("Security Group Ids", printed_security_group_ids) if vpc_mode? or @server.security_group_ids
667
+ msg_pair("IAM Profile", locate_config_value(:iam_instance_profile)) if locate_config_value(:iam_instance_profile)
668
+ msg_pair("Primary ENI", locate_config_value(:primary_eni)) if locate_config_value(:primary_eni)
669
+ msg_pair("AWS Tags", printed_aws_tags)
670
+ msg_pair("Chef Tags", locate_config_value(:chef_tag)) if locate_config_value(:chef_tag)
671
+ msg_pair("SSH Key", @server.key_name)
672
+ msg_pair("Root Device Type", @server.root_device_type)
673
+ msg_pair("Root Volume Tags", printed_volume_tags)
674
+ if @server.root_device_type == "ebs"
675
+ device_map = @server.block_device_mapping.first
676
+ msg_pair("Root Volume ID", device_map['volumeId'])
677
+ msg_pair("Root Device Name", device_map['deviceName'])
678
+ msg_pair("Root Device Delete on Terminate", device_map['deleteOnTermination'])
679
+ msg_pair("Standard or Provisioned IOPS", device_map['volumeType'])
680
+ msg_pair("IOPS rate", device_map['iops'])
681
+
682
+ print "\n#{ui.color("Block devices", :magenta)}\n"
683
+ print "#{ui.color("===========================", :magenta)}\n"
684
+ @server.block_device_mapping.each do |device_map|
685
+ msg_pair("Device Name", device_map['deviceName'])
686
+ msg_pair("Volume ID", device_map['volumeId'])
687
+ msg_pair("Delete on Terminate", device_map['deleteOnTermination'].to_s)
688
+ msg_pair("Standard or Provisioned IOPS", device_map['volumeType'])
689
+ msg_pair("IOPS rate", device_map['iops'])
690
+ print "\n"
691
+ end
692
+ print "#{ui.color("===========================", :magenta)}\n"
693
+
694
+ if config[:ebs_size]
695
+ if ami.block_device_mapping.first['volumeSize'].to_i < config[:ebs_size].to_i
696
+ volume_too_large_warning = "#{config[:ebs_size]}GB " +
697
+ "EBS volume size is larger than size set in AMI of " +
698
+ "#{ami.block_device_mapping.first['volumeSize']}GB.\n" +
699
+ "Use file system tools to make use of the increased volume size."
700
+ msg_pair("Warning", volume_too_large_warning, :yellow)
701
+ end
702
+ end
703
+ end
704
+ if config[:ebs_optimized]
705
+ msg_pair("EBS is Optimized", @server.ebs_optimized.to_s)
706
+ end
707
+ if vpc_mode?
708
+ msg_pair("Subnet ID", @server.subnet_id)
709
+ msg_pair("Tenancy", @server.tenancy)
710
+ if config[:associate_public_ip]
711
+ msg_pair("Public DNS Name", @server.dns_name)
712
+ end
713
+ else
714
+ msg_pair("Public DNS Name", @server.dns_name)
715
+ msg_pair("Public IP Address", @server.public_ip_address)
716
+ msg_pair("Private DNS Name", @server.private_dns_name)
717
+ end
718
+ msg_pair("Private IP Address", @server.private_ip_address)
719
+ msg_pair("Environment", config[:environment] || '_default')
720
+ msg_pair("Run List", (config[:run_list] || []).join(', '))
721
+ if config[:first_boot_attributes] || config[:first_boot_attributes_from_file]
722
+ msg_pair("JSON Attributes",config[:first_boot_attributes] || config[:first_boot_attributes_from_file])
723
+ end
724
+ end
725
+
726
+ def default_bootstrap_template
727
+ is_image_windows? ? 'windows-chef-client-msi' : 'chef-full'
728
+ end
729
+
730
+ def validation_key_path
731
+ @validation_key_path ||= begin
732
+ if URI(Chef::Config[:knife][:validation_key_url]).scheme == 'file'
733
+ URI(Chef::Config[:knife][:validation_key_url]).path
734
+ else
735
+ validation_key_tmpfile.path
736
+ end
737
+ end
738
+ end
739
+
740
+ def validation_key_tmpfile
741
+ @validation_key_tmpfile ||= Tempfile.new('validation_key')
742
+ end
743
+
744
+ def download_validation_key(tempfile)
745
+ Chef::Log.debug 'Downloading validation key ' \
746
+ "<#{Chef::Config[:knife][:validation_key_url]}> to file " \
747
+ "<#{tempfile}>"
748
+
749
+ case URI(Chef::Config[:knife][:validation_key_url]).scheme
750
+ when 's3'
751
+ File.open(tempfile, 'w') { |f| f.write(s3_validation_key) }
752
+ end
753
+ end
754
+
755
+ def s3_validation_key
756
+ @s3_validation_key ||= begin
757
+ Chef::Knife::S3Source.fetch(Chef::Config[:knife][:validation_key_url])
758
+ end
759
+ end
760
+
761
+ def s3_secret
762
+ @s3_secret ||= begin
763
+ return false unless locate_config_value(:s3_secret)
764
+ Chef::Knife::S3Source.fetch(locate_config_value(:s3_secret))
765
+ end
766
+ end
767
+
768
+ def bootstrap_common_params(bootstrap)
769
+ bootstrap.config[:run_list] = config[:run_list]
770
+ bootstrap.config[:policy_group] = locate_config_value(:policy_group)
771
+ bootstrap.config[:policy_name] = locate_config_value(:policy_name)
772
+ bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
773
+ bootstrap.config[:distro] = locate_config_value(:distro) || default_bootstrap_template
774
+ # setting bootstrap_template value to template_file for backward compatibility
775
+ bootstrap.config[:template_file] = locate_config_value(:template_file) || locate_config_value(:bootstrap_template)
776
+ bootstrap.config[:environment] = locate_config_value(:environment)
777
+ bootstrap.config[:prerelease] = config[:prerelease]
778
+ bootstrap.config[:first_boot_attributes] = locate_config_value(:first_boot_attributes)
779
+ bootstrap.config[:first_boot_attributes_from_file] = locate_config_value(:first_boot_attributes_from_file)
780
+ bootstrap.config[:encrypted_data_bag_secret] = s3_secret || locate_config_value(:secret)
781
+ bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:secret_file)
782
+ # retrieving the secret from S3 is unique to knife-ec2, so we need to set "command line secret" to the value fetched from S3
783
+ # When linux vm is spawned, the chef's secret option proc function sets the value "command line secret" and this value is used by
784
+ # chef's code to check if secret option is passed through command line or not
785
+ Chef::Knife::DataBagSecretOptions.set_cl_secret(s3_secret) if locate_config_value(:s3_secret)
786
+ bootstrap.config[:secret] = s3_secret || locate_config_value(:secret)
787
+ bootstrap.config[:secret_file] = locate_config_value(:secret_file)
788
+ bootstrap.config[:node_ssl_verify_mode] = locate_config_value(:node_ssl_verify_mode)
789
+ bootstrap.config[:node_verify_api_cert] = locate_config_value(:node_verify_api_cert)
790
+ bootstrap.config[:bootstrap_no_proxy] = locate_config_value(:bootstrap_no_proxy)
791
+ bootstrap.config[:bootstrap_url] = locate_config_value(:bootstrap_url)
792
+ bootstrap.config[:bootstrap_install_command] = locate_config_value(:bootstrap_install_command)
793
+ bootstrap.config[:bootstrap_wget_options] = locate_config_value(:bootstrap_wget_options)
794
+ bootstrap.config[:bootstrap_curl_options] = locate_config_value(:bootstrap_curl_options)
795
+ bootstrap.config[:bootstrap_vault_file] = locate_config_value(:bootstrap_vault_file)
796
+ bootstrap.config[:bootstrap_vault_json] = locate_config_value(:bootstrap_vault_json)
797
+ bootstrap.config[:bootstrap_vault_item] = locate_config_value(:bootstrap_vault_item)
798
+ bootstrap.config[:use_sudo_password] = locate_config_value(:use_sudo_password)
799
+ bootstrap.config[:yes] = locate_config_value(:yes)
800
+ # If --chef-tag is provided then it will be set in chef as single value e.g. --chef-tag "myTag"
801
+ # Otherwise if --tag-node-in-chef is provided then it will tag the chef in key=value pair of --tags option
802
+ # e.g. --tags "key=value"
803
+ if locate_config_value(:chef_tag)
804
+ bootstrap.config[:tags] = locate_config_value(:chef_tag)
805
+ elsif locate_config_value(:tag_node_in_chef)
806
+ bootstrap.config[:tags] = config[:tags]
807
+ end
808
+ # Modify global configuration state to ensure hint gets set by
809
+ # knife-bootstrap
810
+ Chef::Config[:knife][:hints] ||= {}
811
+ Chef::Config[:knife][:hints]["ec2"] ||= {}
812
+ bootstrap
813
+ end
814
+
815
+ def fetch_server_fqdn(ip_addr)
816
+ require 'resolv'
817
+ Resolv.getname(ip_addr)
818
+ end
819
+
820
+ def bootstrap_for_windows_node(server, fqdn)
821
+ if locate_config_value(:bootstrap_protocol) == 'winrm' || locate_config_value(:bootstrap_protocol) == nil
822
+ if locate_config_value(:kerberos_realm)
823
+ #Fetch AD/WINS based fqdn if any for Kerberos-based Auth
824
+ fqdn = locate_config_value(:fqdn) || fetch_server_fqdn(server.private_ip_address)
825
+ end
826
+ bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
827
+ bootstrap.config[:winrm_user] = locate_config_value(:winrm_user)
828
+ bootstrap.config[:winrm_password] = windows_password
829
+ bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport)
830
+ bootstrap.config[:kerberos_keytab_file] = locate_config_value(:kerberos_keytab_file)
831
+ bootstrap.config[:kerberos_realm] = locate_config_value(:kerberos_realm)
832
+ bootstrap.config[:kerberos_service] = locate_config_value(:kerberos_service)
833
+ bootstrap.config[:ca_trust_file] = locate_config_value(:ca_trust_file)
834
+ bootstrap.config[:winrm_port] = locate_config_value(:winrm_port)
835
+ bootstrap.config[:auth_timeout] = locate_config_value(:auth_timeout)
836
+ bootstrap.config[:winrm_ssl_verify_mode] = locate_config_value(:winrm_ssl_verify_mode)
837
+ elsif locate_config_value(:bootstrap_protocol) == 'ssh'
838
+ bootstrap = Chef::Knife::BootstrapWindowsSsh.new
839
+ bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
840
+ bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
841
+ bootstrap.config[:ssh_port] = locate_config_value(:ssh_port)
842
+ bootstrap.config[:identity_file] = locate_config_value(:identity_file)
843
+ bootstrap.config[:no_host_key_verify] = locate_config_value(:no_host_key_verify)
844
+ bootstrap.config[:forward_agent] = locate_config_value(:forward_agent)
845
+ else
846
+ ui.error("Unsupported Bootstrapping Protocol. Supported : winrm, ssh")
847
+ exit 1
848
+ end
849
+ bootstrap.name_args = [fqdn]
850
+ bootstrap.config[:msi_url] = locate_config_value(:msi_url)
851
+ bootstrap.config[:install_as_service] = locate_config_value(:install_as_service)
852
+ bootstrap.config[:session_timeout] = locate_config_value(:session_timeout)
853
+
854
+ if locate_config_value(:chef_node_name)
855
+ bootstrap.config[:chef_node_name] = evaluate_node_name(locate_config_value(:chef_node_name))
856
+ else
857
+ bootstrap.config[:chef_node_name] = server.id
858
+ end
859
+ bootstrap_common_params(bootstrap)
860
+ end
861
+
862
+ def bootstrap_for_linux_node(server,ssh_host)
863
+ bootstrap = Chef::Knife::Bootstrap.new
864
+ bootstrap.name_args = [ssh_host]
865
+ bootstrap.config[:ssh_user] = config[:ssh_user]
866
+ bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
867
+ bootstrap.config[:ssh_port] = config[:ssh_port]
868
+ bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
869
+ bootstrap.config[:identity_file] = config[:identity_file]
870
+
871
+ if locate_config_value(:chef_node_name)
872
+ bootstrap.config[:chef_node_name] = evaluate_node_name(locate_config_value(:chef_node_name))
873
+ else
874
+ bootstrap.config[:chef_node_name] = server.id
875
+ end
876
+ bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
877
+ # may be needed for vpc_mode
878
+ bootstrap.config[:host_key_verify] = config[:host_key_verify]
879
+ bootstrap_common_params(bootstrap)
880
+ end
881
+
882
+ def vpc_mode?
883
+ # Amazon Virtual Private Cloud requires a subnet_id. If
884
+ # present, do a few things differently
885
+ !!locate_config_value(:subnet_id)
886
+ end
887
+
888
+ def ami
889
+ @ami ||= connection.images.get(locate_config_value(:image))
890
+ end
891
+
892
+ def validate!
893
+ if Chef::Config[:knife].keys.include? :aws_ssh_key_id
894
+ Chef::Config[:knife][:ssh_key_name] = Chef::Config[:knife][:aws_ssh_key_id] if !Chef::Config[:knife][:ssh_key_name]
895
+ Chef::Config[:knife].delete(:aws_ssh_key_id)
896
+ ui.warn("Use of aws_ssh_key_id option in knife.rb config is deprecated, use ssh_key_name option instead.")
897
+ end
898
+
899
+ super([:image, :ssh_key_name, :aws_access_key_id, :aws_secret_access_key])
900
+
901
+ validate_nics! if locate_config_value(:network_interfaces)
902
+
903
+ if ami.nil?
904
+ ui.error("You have not provided a valid image (AMI) value.")
905
+ exit 1
906
+ end
907
+
908
+ if vpc_mode? and !!config[:security_groups]
909
+ ui.error("You are using a VPC, security groups specified with '-G' are not allowed, specify one or more security group ids with '-g' instead.")
910
+ exit 1
911
+ end
912
+
913
+ if !vpc_mode? and !!config[:private_ip_address]
914
+ ui.error("You can only specify a private IP address if you are using VPC.")
915
+ exit 1
916
+ end
917
+
918
+ if config[:dedicated_instance] and !vpc_mode?
919
+ ui.error("You can only specify a Dedicated Instance if you are using VPC.")
920
+ exit 1
921
+ end
922
+
923
+ if !vpc_mode? and config[:associate_public_ip]
924
+ ui.error("--associate-public-ip option only applies to VPC instances, and you have not specified a subnet id.")
925
+ exit 1
926
+ end
927
+
928
+ if config[:associate_eip]
929
+ eips = connection.addresses.collect{|addr| addr if addr.domain == eip_scope}.compact
930
+
931
+ unless eips.detect{|addr| addr.public_ip == config[:associate_eip] && addr.server_id == nil}
932
+ ui.error("Elastic IP requested is not available.")
933
+ exit 1
934
+ end
935
+ end
936
+
937
+ if config[:ebs_provisioned_iops] and config[:ebs_volume_type] != 'io1'
938
+ ui.error("--provisioned-iops option is only supported for volume type of 'io1'")
939
+ exit 1
940
+ end
941
+
942
+ if config[:ebs_volume_type] == 'io1' and config[:ebs_provisioned_iops].nil?
943
+ ui.error("--provisioned-iops option is required when using volume type of 'io1'")
944
+ exit 1
945
+ end
946
+
947
+ if config[:ebs_volume_type] and ! %w(gp2 io1 standard).include?(config[:ebs_volume_type])
948
+ ui.error("--ebs-volume-type must be 'standard' or 'io1' or 'gp2'")
949
+ msg opt_parser
950
+ exit 1
951
+ end
952
+
953
+ if config[:security_groups] && config[:security_groups].class == String
954
+ ui.error("Invalid value type for knife[:security_groups] in knife configuration file (i.e knife.rb). Type should be array. e.g - knife[:security_groups] = ['sgroup1']")
955
+ exit 1
956
+ end
957
+
958
+ # Validation for security_group_ids passed through knife.rb. It will raise error if values are not provided in Array.
959
+ if locate_config_value(:security_group_ids) && locate_config_value(:security_group_ids).class == String
960
+ ui.error("Invalid value type for knife[:security_group_ids] in knife configuration file (i.e knife.rb). Type should be array. e.g - knife[:security_group_ids] = ['sgroup1']")
961
+ exit 1
962
+ end
963
+
964
+ if config[:classic_link_vpc_id].nil? ^ config[:classic_link_vpc_security_group_ids].nil?
965
+ ui.error("--classic-link-vpc-id and --classic-link-vpc-security-group-ids must be used together")
966
+ exit 1
967
+ end
968
+
969
+ if vpc_mode? and config[:classic_link_vpc_id]
970
+ ui.error("You can only use ClassicLink if you are not using a VPC")
971
+ exit 1
972
+ end
973
+
974
+ if locate_config_value(:ebs_encrypted)
975
+ error_message = ""
976
+ errors = []
977
+ # validation for flavor and ebs_encrypted
978
+ if !locate_config_value(:flavor)
979
+ ui.error("--ebs-encrypted option requires valid flavor to be specified.")
980
+ exit 1
981
+ elsif (locate_config_value(:ebs_encrypted) and ! %w(m3.medium m3.large m3.xlarge m3.2xlarge m4.large m4.xlarge
982
+ m4.2xlarge m4.4xlarge m4.10xlarge m4.16xlarge t2.nano t2.micro t2.small
983
+ t2.medium t2.large t2.xlarge t2.2xlarge d2.xlarge d2.2xlarge d2.4xlarge
984
+ d2.8xlarge c4.large c4.xlarge c4.2xlarge c4.4xlarge c4.8xlarge c3.large
985
+ c3.xlarge c3.2xlarge c3.4xlarge c3.8xlarge cr1.8xlarge r3.large r3.xlarge
986
+ r3.2xlarge r3.4xlarge r3.8xlarge r4.large r4.xlarge r4.2xlarge r4.4xlarge
987
+ r4.8xlarge r4.16xlarge x1.16xlarge x1.32xlarge i2.xlarge i2.2xlarge i2.4xlarge
988
+ i2.8xlarge i3.large i3.xlarge i3.2xlarge i3.4xlarge i3.8xlarge i3.16xlarge
989
+ f1.2xlarge f1.16xlarge g2.2xlarge g2.8xlarge p2.xlarge p2.8xlarge p2.16xlarge).include?(locate_config_value(:flavor)))
990
+ ui.error("--ebs-encrypted option is not supported for #{locate_config_value(:flavor)} flavor.")
991
+ exit 1
992
+ end
993
+
994
+ # validation for ebs_size and ebs_volume_type and ebs_encrypted
995
+ if !locate_config_value(:ebs_size)
996
+ errors << "--ebs-encrypted option requires valid --ebs-size to be specified."
997
+ elsif locate_config_value(:ebs_volume_type) == "gp2" and ! locate_config_value(:ebs_size).to_i.between?(1, 16384)
998
+ errors << "--ebs-size should be in between 1-16384 for 'gp2' ebs volume type."
999
+ elsif locate_config_value(:ebs_volume_type) == "io1" and ! locate_config_value(:ebs_size).to_i.between?(4, 16384)
1000
+ errors << "--ebs-size should be in between 4-16384 for 'io1' ebs volume type."
1001
+ elsif locate_config_value(:ebs_volume_type) == "standard" and ! locate_config_value(:ebs_size).to_i.between?(1, 1024)
1002
+ errors << "--ebs-size should be in between 1-1024 for 'standard' ebs volume type."
1003
+ end
1004
+
1005
+ if errors.each{|e| error_message = "#{error_message} #{e}"}.any?
1006
+ ui.error(error_message)
1007
+ exit 1
1008
+ end
1009
+ end
1010
+
1011
+ if locate_config_value(:spot_price) && locate_config_value(:disable_api_termination)
1012
+ ui.error("spot-price and disable-api-termination options cannot be passed together as 'Termination Protection' cannot be enabled for spot instances.")
1013
+ exit 1
1014
+ end
1015
+
1016
+ if locate_config_value(:spot_price).nil? && locate_config_value(:spot_wait_mode).downcase != 'prompt'
1017
+ ui.error('spot-wait-mode option requires that a spot-price option is set.')
1018
+ exit 1
1019
+ end
1020
+
1021
+ volume_tags = locate_config_value(:volume_tags)
1022
+ if !volume_tags.nil? and volume_tags.length != volume_tags.to_s.count('=')
1023
+ ui.error("Volume Tags should be entered in a key = value pair")
1024
+ exit 1
1025
+ end
1026
+
1027
+ if (locate_config_value(:winrm_password).to_s.length > 14 )
1028
+ ui.warn("The password provided is longer than 14 characters. Computers with Windows prior to Windows 2000 will not be able to use this account. Do you want to continue this operation? (Y/N):")
1029
+ password_promt = STDIN.gets.chomp.upcase
1030
+ if (password_promt == "N")
1031
+ raise "Exiting as operation with password greater than 14 characters not accepted"
1032
+ elsif (password_promt == "Y")
1033
+ @allow_long_password = "/yes"
1034
+ else
1035
+ raise "The input provided is incorrect."
1036
+ end
1037
+ end
1038
+
1039
+ if locate_config_value(:tag_node_in_chef)
1040
+ ui.warn("[DEPRECATED] --tag-node-in-chef option is deprecated. Use --chef-tag option instead.")
1041
+ end
1042
+
1043
+ if locate_config_value(:tags)
1044
+ ui.warn("[DEPRECATED] --tags option is deprecated. Use --aws-tag option instead.")
1045
+ end
1046
+ end
1047
+
1048
+ def tags
1049
+ tags = locate_config_value(:tags) || locate_config_value(:aws_tag)
1050
+ if !tags.nil? and tags.length != tags.to_s.count('=')
1051
+ ui.error("AWS Tags should be entered in a key = value pair")
1052
+ exit 1
1053
+ end
1054
+ tags
1055
+ end
1056
+
1057
+ def eip_scope
1058
+ if vpc_mode?
1059
+ "vpc"
1060
+ else
1061
+ "standard"
1062
+ end
1063
+ end
1064
+
1065
+ def ssl_config_user_data
1066
+ user_related_commands = ""
1067
+ winrm_user = locate_config_value(:winrm_user).split("\\")
1068
+ if (winrm_user[0] == ".") || (winrm_user[0] == "") ||(winrm_user.length == 1)
1069
+ user_related_commands = <<-EOH
1070
+ net user /add #{locate_config_value(:winrm_user).delete('.\\')} #{windows_password} #{@allow_long_password};
1071
+ net localgroup Administrators /add #{locate_config_value(:winrm_user).delete('.\\')};
1072
+ EOH
1073
+ end
1074
+ <<-EOH
1075
+ #{user_related_commands}
1076
+ If (-Not (Get-Service WinRM | Where-Object {$_.status -eq "Running"})) {
1077
+ winrm quickconfig -q
1078
+ }
1079
+ If (winrm e winrm/config/listener | Select-String -Pattern " Transport = HTTP\\b" -Quiet) {
1080
+ winrm delete winrm/config/listener?Address=*+Transport=HTTP
1081
+ }
1082
+ $vm_name = invoke-restmethod -uri http://169.254.169.254/latest/meta-data/public-ipv4
1083
+ If (-Not $vm_name) {
1084
+ $vm_name = invoke-restmethod -uri http://169.254.169.254/latest/meta-data/local-ipv4
1085
+ }
1086
+
1087
+ $name = new-object -com "X509Enrollment.CX500DistinguishedName.1"
1088
+ $name.Encode("CN=$vm_name", 0)
1089
+ $key = new-object -com "X509Enrollment.CX509PrivateKey.1"
1090
+ $key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
1091
+ $key.KeySpec = 1
1092
+ $key.Length = 2048
1093
+ $key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
1094
+ $key.MachineContext = 1
1095
+ $key.Create()
1096
+ $serverauthoid = new-object -com "X509Enrollment.CObjectId.1"
1097
+ $serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1")
1098
+ $ekuoids = new-object -com "X509Enrollment.CObjectIds.1"
1099
+ $ekuoids.add($serverauthoid)
1100
+ $ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"
1101
+ $ekuext.InitializeEncode($ekuoids)
1102
+ $cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1"
1103
+ $cert.InitializeFromPrivateKey(2, $key, "")
1104
+ $cert.Subject = $name
1105
+ $cert.Issuer = $cert.Subject
1106
+ $cert.NotBefore = get-date
1107
+ $cert.NotAfter = $cert.NotBefore.AddYears(10)
1108
+ $cert.X509Extensions.Add($ekuext)
1109
+ $cert.Encode()
1110
+ $enrollment = new-object -com "X509Enrollment.CX509Enrollment.1"
1111
+ $enrollment.InitializeFromRequest($cert)
1112
+ $certdata = $enrollment.CreateRequest(0)
1113
+ $enrollment.InstallResponse(2, $certdata, 0, "")
1114
+
1115
+ $thumbprint = (Get-ChildItem -Path cert:\\localmachine\\my | Where-Object {$_.Subject -match "$vm_name"}).Thumbprint;
1116
+ $create_listener_cmd = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS '@{Hostname=`"$vm_name`";CertificateThumbprint=`"$thumbprint`"}'"
1117
+ iex $create_listener_cmd
1118
+ netsh advfirewall firewall add rule name="WinRM HTTPS" protocol=TCP dir=in Localport=5986 remoteport=any action=allow localip=any remoteip=any profile=any enable=yes
1119
+ EOH
1120
+ end
1121
+
1122
+ def ssl_config_data_already_exist?
1123
+ File.read(locate_config_value(:aws_user_data)).gsub(/\\\\/,"\\").include? ssl_config_user_data.strip
1124
+ end
1125
+
1126
+ def process_user_data(script_lines)
1127
+ if !ssl_config_data_already_exist?
1128
+ ps_start_tag = "<powershell>\n"
1129
+ ps_end_tag = "</powershell>\n"
1130
+ ps_start_tag_index = script_lines.index(ps_start_tag) || script_lines.index(ps_start_tag.strip)
1131
+ ps_end_tag_index = script_lines.index(ps_end_tag) || script_lines.index(ps_end_tag.strip)
1132
+ case
1133
+ when ( ps_start_tag_index && !ps_end_tag_index ) || ( !ps_start_tag_index && ps_end_tag_index )
1134
+ ui.error("Provided user_data file is invalid.")
1135
+ exit 1
1136
+ when ps_start_tag_index && ps_end_tag_index
1137
+ script_lines[ps_end_tag_index] = ssl_config_user_data + ps_end_tag
1138
+ when !ps_start_tag_index && !ps_end_tag_index
1139
+ script_lines.insert(-1,"\n\n" + ps_start_tag + ssl_config_user_data + ps_end_tag)
1140
+ end
1141
+ end
1142
+ script_lines
1143
+ end
1144
+
1145
+ def create_server_def
1146
+ server_def = {
1147
+ :image_id => locate_config_value(:image),
1148
+ :groups => config[:security_groups],
1149
+ :flavor_id => locate_config_value(:flavor),
1150
+ :key_name => locate_config_value(:ssh_key_name),
1151
+ :availability_zone => locate_config_value(:availability_zone),
1152
+ :price => locate_config_value(:spot_price),
1153
+ :request_type => locate_config_value(:spot_request_type)
1154
+ }
1155
+
1156
+ if primary_eni = locate_config_value(:primary_eni)
1157
+ server_def[:network_interfaces] = [
1158
+ {
1159
+ :NetworkInterfaceId => primary_eni,
1160
+ :DeviceIndex => "0"
1161
+ }
1162
+ ]
1163
+ else
1164
+ server_def[:security_group_ids] = locate_config_value(:security_group_ids)
1165
+ server_def[:subnet_id] = locate_config_value(:subnet_id) if vpc_mode?
1166
+ end
1167
+
1168
+ server_def[:private_ip_address] = locate_config_value(:private_ip_address) if vpc_mode?
1169
+ server_def[:placement_group] = locate_config_value(:placement_group)
1170
+ server_def[:iam_instance_profile_name] = locate_config_value(:iam_instance_profile)
1171
+ server_def[:tenancy] = "dedicated" if vpc_mode? and locate_config_value(:dedicated_instance)
1172
+ server_def[:associate_public_ip] = locate_config_value(:associate_public_ip) if vpc_mode? and config[:associate_public_ip]
1173
+
1174
+ if locate_config_value(:winrm_transport) == 'ssl'
1175
+ if locate_config_value(:aws_user_data)
1176
+ begin
1177
+ user_data = File.readlines(locate_config_value(:aws_user_data))
1178
+ if config[:create_ssl_listener]
1179
+ user_data = process_user_data(user_data)
1180
+ end
1181
+ user_data = user_data.join
1182
+ server_def.merge!(:user_data => user_data)
1183
+ rescue
1184
+ ui.warn("Cannot read #{locate_config_value(:aws_user_data)}: #{$!.inspect}. Ignoring option.")
1185
+ end
1186
+ else
1187
+ if config[:create_ssl_listener]
1188
+ server_def.merge!(:user_data => "<powershell>\n" + ssl_config_user_data + "</powershell>\n")
1189
+ end
1190
+ end
1191
+ else
1192
+ if locate_config_value(:aws_user_data)
1193
+ begin
1194
+ server_def.merge!(:user_data => File.read(locate_config_value(:aws_user_data)))
1195
+ rescue
1196
+ ui.warn("Cannot read #{locate_config_value(:aws_user_data)}: #{$!.inspect}. Ignoring option.")
1197
+ end
1198
+ end
1199
+ end
1200
+
1201
+ if config[:ebs_optimized]
1202
+ server_def[:ebs_optimized] = "true"
1203
+ else
1204
+ server_def[:ebs_optimized] = "false"
1205
+ end
1206
+
1207
+ if ami.root_device_type == "ebs"
1208
+ if locate_config_value(:ebs_encrypted)
1209
+ ami_map = ami.block_device_mapping[1]
1210
+ else
1211
+ ami_map = ami.block_device_mapping.first
1212
+ end
1213
+
1214
+ ebs_size = begin
1215
+ if config[:ebs_size]
1216
+ Integer(config[:ebs_size]).to_s
1217
+ else
1218
+ ami_map["volumeSize"].to_s
1219
+ end
1220
+ rescue ArgumentError
1221
+ puts "--ebs-size must be an integer"
1222
+ msg opt_parser
1223
+ exit 1
1224
+ end
1225
+ delete_term = if config[:ebs_no_delete_on_term]
1226
+ "false"
1227
+ else
1228
+ ami_map["deleteOnTermination"]
1229
+ end
1230
+ iops_rate = begin
1231
+ if config[:ebs_provisioned_iops]
1232
+ Integer(config[:ebs_provisioned_iops]).to_s
1233
+ else
1234
+ ami_map["iops"].to_s
1235
+ end
1236
+ rescue ArgumentError
1237
+ puts "--provisioned-iops must be an integer"
1238
+ msg opt_parser
1239
+ exit 1
1240
+ end
1241
+
1242
+ server_def[:block_device_mapping] =
1243
+ [{
1244
+ 'DeviceName' => ami_map["deviceName"],
1245
+ 'Ebs.VolumeSize' => ebs_size,
1246
+ 'Ebs.DeleteOnTermination' => delete_term,
1247
+ 'Ebs.VolumeType' => config[:ebs_volume_type],
1248
+ }]
1249
+ server_def[:block_device_mapping].first['Ebs.Iops'] = iops_rate unless iops_rate.empty?
1250
+ server_def[:block_device_mapping].first['Ebs.Encrypted'] = true if locate_config_value(:ebs_encrypted)
1251
+ end
1252
+
1253
+ (config[:ephemeral] || []).each_with_index do |device_name, i|
1254
+ server_def[:block_device_mapping] = (server_def[:block_device_mapping] || []) << {'VirtualName' => "ephemeral#{i}", 'DeviceName' => device_name}
1255
+ end
1256
+
1257
+ ## cannot pass disable_api_termination option to the API when using spot instances ##
1258
+ server_def[:disable_api_termination] = locate_config_value(:disable_api_termination) if locate_config_value(:spot_price).nil?
1259
+
1260
+ server_def[:instance_initiated_shutdown_behavior] = locate_config_value(:instance_initiated_shutdown_behavior)
1261
+ server_def[:chef_tag] = locate_config_value(:chef_tag)
1262
+ server_def
1263
+ end
1264
+
1265
+ def wait_for_sshd(hostname)
1266
+ ssh_gateway = get_ssh_gateway_for(hostname)
1267
+ ssh_gateway ? wait_for_tunnelled_sshd(ssh_gateway, hostname) : wait_for_direct_sshd(hostname, config[:ssh_port])
1268
+ end
1269
+
1270
+ def get_ssh_gateway_for(hostname)
1271
+ if config[:ssh_gateway]
1272
+ # The ssh_gateway specified in the knife config (if any) takes
1273
+ # precedence over anything in the SSH configuration
1274
+ Chef::Log.debug("Using ssh gateway #{config[:ssh_gateway]} from knife config")
1275
+ config[:ssh_gateway]
1276
+ else
1277
+ # Next, check if the SSH configuration has a ProxyCommand
1278
+ # directive for this host. If there is one, parse out the
1279
+ # host from the proxy command
1280
+ ssh_proxy = Net::SSH::Config.for(hostname)[:proxy]
1281
+ if ssh_proxy.respond_to?(:command_line_template)
1282
+ # ssh gateway_hostname nc %h %p
1283
+ proxy_pattern = /ssh\s+(\S+)\s+nc/
1284
+ matchdata = proxy_pattern.match(ssh_proxy.command_line_template)
1285
+ if matchdata.nil?
1286
+ Chef::Log.debug("Unable to determine ssh gateway for '#{hostname}' from ssh config template: #{ssh_proxy.command_line_template}")
1287
+ nil
1288
+ else
1289
+ # Return hostname extracted from command line template
1290
+ Chef::Log.debug("Using ssh gateway #{matchdata[1]} from ssh config")
1291
+ matchdata[1]
1292
+ end
1293
+ else
1294
+ # Return nil if we cannot find an ssh_gateway
1295
+ Chef::Log.debug("No ssh gateway found, making a direct connection")
1296
+ nil
1297
+ end
1298
+ end
1299
+ end
1300
+
1301
+ def wait_for_tunnelled_sshd(ssh_gateway, hostname)
1302
+ initial = true
1303
+ print(".") until tunnel_test_ssh(ssh_gateway, hostname) {
1304
+ if initial
1305
+ initial = false
1306
+ sleep (vpc_mode? ? 40 : 10)
1307
+ else
1308
+ sleep 10
1309
+ end
1310
+ puts("done")
1311
+ }
1312
+ end
1313
+
1314
+ def tunnel_test_ssh(ssh_gateway, hostname, &block)
1315
+ status = false
1316
+ gateway = configure_ssh_gateway(ssh_gateway)
1317
+ gateway.open(hostname, config[:ssh_port]) do |local_tunnel_port|
1318
+ status = tcp_test_ssh('localhost', local_tunnel_port, &block)
1319
+ end
1320
+ status
1321
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
1322
+ sleep 2
1323
+ false
1324
+ rescue Errno::EPERM, Errno::ETIMEDOUT
1325
+ false
1326
+ end
1327
+
1328
+ def configure_ssh_gateway(ssh_gateway)
1329
+ gw_host, gw_user = ssh_gateway.split('@').reverse
1330
+ gw_host, gw_port = gw_host.split(':')
1331
+ gateway_options = { :port => gw_port || 22 }
1332
+
1333
+ # Load the SSH config for the SSH gateway host.
1334
+ # Set the gateway user if it was not part of the
1335
+ # SSH gateway string, and use any configured
1336
+ # SSH keys.
1337
+ ssh_gateway_config = Net::SSH::Config.for(gw_host)
1338
+ gw_user ||= ssh_gateway_config[:user]
1339
+
1340
+ # Always use the gateway keys from the SSH Config
1341
+ gateway_keys = ssh_gateway_config[:keys]
1342
+
1343
+ # Use the keys specificed on the command line if available (overrides SSH Config)
1344
+ if config[:ssh_gateway_identity]
1345
+ gateway_keys = Array(locate_config_value(:ssh_gateway_identity))
1346
+ end
1347
+
1348
+ unless gateway_keys.nil?
1349
+ gateway_options[:keys] = gateway_keys
1350
+ end
1351
+
1352
+ Net::SSH::Gateway.new(gw_host, gw_user, gateway_options)
1353
+ end
1354
+
1355
+ def wait_for_direct_sshd(hostname, ssh_port)
1356
+ initial = true
1357
+ print(".") until tcp_test_ssh(hostname, ssh_port) {
1358
+ if initial
1359
+ initial = false
1360
+ sleep (vpc_mode? ? 40 : 10)
1361
+ else
1362
+ sleep 10
1363
+ end
1364
+ puts("done")
1365
+ }
1366
+ end
1367
+
1368
+ def subnet_public_ip_on_launch?
1369
+ connection.subnets.get(server.subnet_id).map_public_ip_on_launch
1370
+ end
1371
+
1372
+ def ssh_connect_host
1373
+ unless @ssh_connect_host
1374
+ if config[:server_connect_attribute]
1375
+ connect_attribute = config[:server_connect_attribute]
1376
+ server.send(config[:server_connect_attribute])
1377
+ elsif vpc_mode? && !(subnet_public_ip_on_launch? || config[:associate_public_ip] || config[:associate_eip])
1378
+ connect_attribute = "private_ip_address"
1379
+ server.private_ip_address
1380
+ else
1381
+ connect_attribute = server.dns_name ? "dns_name" : "public_ip_address"
1382
+ server.send(connect_attribute)
1383
+ end
1384
+ @ssh_connect_host = server.send(connect_attribute)
1385
+ end
1386
+
1387
+ puts "\nSSH Target Address: #{@ssh_connect_host}(#{connect_attribute})"
1388
+ @ssh_connect_host
1389
+ end
1390
+
1391
+ def create_tags(hashed_tags)
1392
+ hashed_tags.each_pair do |key,val|
1393
+ connection.tags.create :key => key, :value => val, :resource_id => @server.id
1394
+ end
1395
+ end
1396
+
1397
+ def associate_eip(elastic_ip)
1398
+ connection.associate_address(server.id, elastic_ip.public_ip, nil, elastic_ip.allocation_id)
1399
+ @server.wait_for(locate_config_value(:aws_connection_timeout)) { public_ip_address == elastic_ip.public_ip }
1400
+ end
1401
+
1402
+ def validate_nics!
1403
+ valid_nic_ids = connection.network_interfaces.all(
1404
+ vpc_mode? ? { 'vpc-id' => vpc_id } : {}
1405
+ ).map(&:network_interface_id)
1406
+ invalid_nic_ids =
1407
+ locate_config_value(:network_interfaces) - valid_nic_ids
1408
+ return true if invalid_nic_ids.empty?
1409
+ ui.error 'The following network interfaces are invalid: ' \
1410
+ "#{invalid_nic_ids.join(', ')}"
1411
+ exit 1
1412
+ end
1413
+
1414
+ def vpc_id
1415
+ @vpc_id ||= begin
1416
+ connection.subnets.get(locate_config_value(:subnet_id)).vpc_id
1417
+ end
1418
+ end
1419
+
1420
+ def wait_for_nic_attachment
1421
+ attached_nics_count = 0
1422
+ until attached_nics_count ==
1423
+ locate_config_value(:network_interfaces).count
1424
+ attachment_nics =
1425
+ locate_config_value(:network_interfaces).map do |nic_id|
1426
+ connection.network_interfaces.get(nic_id).attachment['status']
1427
+ end
1428
+ attached_nics_count = attachment_nics.grep('attached').count
1429
+ end
1430
+ end
1431
+
1432
+ def attach_nics
1433
+ attachments = []
1434
+ config[:network_interfaces].each_with_index do |nic_id, index|
1435
+ attachments << connection.attach_network_interface(nic_id,
1436
+ server.id,
1437
+ index + 1).body
1438
+ end
1439
+ wait_for_nic_attachment
1440
+ # rubocop:disable Style/RedundantReturn
1441
+ return attachments
1442
+ # rubocop:enable Style/RedundantReturn
1443
+ end
1444
+
1445
+ def enable_classic_link(vpc_id, security_group_ids)
1446
+ connection.attach_classic_link_vpc(server.id, vpc_id, security_group_ids)
1447
+ end
1448
+
1449
+ def ssh_override_winrm
1450
+ # unchanged ssh_user and changed winrm_user, override ssh_user
1451
+ if locate_config_value(:ssh_user).eql?(options[:ssh_user][:default]) &&
1452
+ !locate_config_value(:winrm_user).eql?(options[:winrm_user][:default])
1453
+ config[:ssh_user] = locate_config_value(:winrm_user)
1454
+ end
1455
+ # unchanged ssh_port and changed winrm_port, override ssh_port
1456
+ if locate_config_value(:ssh_port).eql?(options[:ssh_port][:default]) &&
1457
+ !locate_config_value(:winrm_port).eql?(options[:winrm_port][:default])
1458
+ config[:ssh_port] = locate_config_value(:winrm_port)
1459
+ end
1460
+ # unset ssh_password and set winrm_password, override ssh_password
1461
+ if locate_config_value(:ssh_password).nil? &&
1462
+ !locate_config_value(:winrm_password).nil?
1463
+ config[:ssh_password] = locate_config_value(:winrm_password)
1464
+ end
1465
+ # unset identity_file and set kerberos_keytab_file, override identity_file
1466
+ if locate_config_value(:identity_file).nil? &&
1467
+ !locate_config_value(:kerberos_keytab_file).nil?
1468
+ config[:identity_file] = locate_config_value(:kerberos_keytab_file)
1469
+ end
1470
+ end
1471
+
1472
+ def tcp_test_winrm(ip_addr, port)
1473
+ tcp_socket = TCPSocket.new(ip_addr, port)
1474
+ yield
1475
+ true
1476
+ rescue SocketError
1477
+ sleep 2
1478
+ false
1479
+ rescue Errno::ETIMEDOUT
1480
+ false
1481
+ rescue Errno::EPERM
1482
+ false
1483
+ rescue Errno::ECONNREFUSED
1484
+ sleep 2
1485
+ false
1486
+ rescue Errno::EHOSTUNREACH
1487
+ sleep 2
1488
+ false
1489
+ rescue Errno::ENETUNREACH
1490
+ sleep 2
1491
+ false
1492
+ ensure
1493
+ tcp_socket && tcp_socket.close
1494
+ end
1495
+
1496
+ def tcp_test_ssh(hostname, ssh_port)
1497
+ tcp_socket = TCPSocket.new(hostname, ssh_port)
1498
+ readable = IO.select([tcp_socket], nil, nil, 5)
1499
+ if readable
1500
+ ssh_banner = tcp_socket.gets
1501
+ if ssh_banner.nil? or ssh_banner.empty?
1502
+ false
1503
+ else
1504
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{ssh_banner}")
1505
+ yield
1506
+ true
1507
+ end
1508
+ else
1509
+ false
1510
+ end
1511
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ENOTCONN, IOError
1512
+ Chef::Log.debug("ssh failed to connect: #{hostname}")
1513
+ sleep 2
1514
+ false
1515
+ rescue Errno::EPERM, Errno::ETIMEDOUT
1516
+ Chef::Log.debug("ssh timed out: #{hostname}")
1517
+ false
1518
+ # This happens on some mobile phone networks
1519
+ rescue Errno::ECONNRESET
1520
+ Chef::Log.debug("ssh reset its connection: #{hostname}")
1521
+ sleep 2
1522
+ false
1523
+ ensure
1524
+ tcp_socket && tcp_socket.close
1525
+ end
1526
+
1527
+ def decrypt_admin_password(encoded_password, key)
1528
+ require 'base64'
1529
+ require 'openssl'
1530
+ private_key = OpenSSL::PKey::RSA.new(key)
1531
+ encrypted_password = Base64.decode64(encoded_password)
1532
+ password = private_key.private_decrypt(encrypted_password)
1533
+ password
1534
+ end
1535
+
1536
+ def check_windows_password_available(server_id)
1537
+ sleep 10
1538
+ response = connection.get_password_data(server_id)
1539
+ if not response.body["passwordData"]
1540
+ return false
1541
+ end
1542
+ response.body["passwordData"]
1543
+ end
1544
+
1545
+ def windows_password
1546
+ if not locate_config_value(:winrm_password)
1547
+ if locate_config_value(:identity_file)
1548
+ if @server
1549
+ print "\n#{ui.color("Waiting for Windows Admin password to be available: ", :magenta)}"
1550
+ print(".") until check_windows_password_available(@server.id) { puts("done") }
1551
+ response = connection.get_password_data(@server.id)
1552
+ data = File.read(locate_config_value(:identity_file))
1553
+ config[:winrm_password] = decrypt_admin_password(response.body["passwordData"], data)
1554
+ else
1555
+ print "\n#{ui.color("Fetchig instance details: \n", :magenta)}"
1556
+ end
1557
+ else
1558
+ ui.error("Cannot find SSH Identity file, required to fetch dynamically generated password")
1559
+ exit 1
1560
+ end
1561
+ else
1562
+ locate_config_value(:winrm_password)
1563
+ end
1564
+ end
1565
+
1566
+ def load_winrm_deps
1567
+ require 'winrm'
1568
+ require 'chef/knife/winrm'
1569
+ require 'chef/knife/bootstrap_windows_winrm'
1570
+ require 'chef/knife/bootstrap_windows_ssh'
1571
+ require 'chef/knife/core/windows_bootstrap_context'
1572
+ end
1573
+
1574
+ #Returns the name of node after evaluation of server id if %s is present.
1575
+ #Eg: "Test-%s" will return "Test-i-12345" in case the instance id is i-12345
1576
+ def evaluate_node_name(node_name)
1577
+ return node_name%server.id
1578
+ end
1579
+
1580
+ def create_volume_tags(hashed_volume_tags)
1581
+ hashed_volume_tags.each_pair do |key,val|
1582
+ connection.tags.create :key => key, :value => val, :resource_id => @server.block_device_mapping.first['volumeId']
1583
+ end
1584
+ end
1585
+
1586
+ end
1587
+ end
1588
+ end