knife-ec2 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +41 -41
- data/.travis.yml +7 -1
- data/CHANGELOG.md +40 -4
- data/CONTRIBUTING.md +216 -71
- data/CONTRIBUTIONS.md +3 -6
- data/DOC_CHANGES.md +25 -50
- data/Gemfile +4 -1
- data/LICENSE +201 -201
- data/README.md +138 -83
- data/RELEASE_NOTES.md +20 -36
- data/Rakefile +56 -56
- data/knife-ec2.gemspec +4 -3
- data/lib/chef/knife/ec2_base.rb +45 -12
- data/lib/chef/knife/ec2_flavor_list.rb +53 -53
- data/lib/chef/knife/ec2_server_create.rb +251 -45
- data/lib/chef/knife/ec2_server_delete.rb +140 -140
- data/lib/chef/knife/ec2_server_list.rb +52 -83
- data/lib/chef/knife/s3_source.rb +49 -49
- data/lib/knife-ec2/version.rb +6 -6
- data/spec/spec_helper.rb +18 -18
- data/spec/unit/ec2_server_create_spec.rb +930 -19
- data/spec/unit/ec2_server_delete_spec.rb +141 -141
- data/spec/unit/ec2_server_list_spec.rb +131 -0
- data/spec/unit/s3_source_deps_spec.rb +24 -24
- data/spec/unit/s3_source_spec.rb +75 -75
- metadata +25 -15
data/knife-ec2.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = 'knife-ec2'
|
7
7
|
s.version = Knife::Ec2::VERSION
|
8
8
|
s.authors = ['Adam Jacob', 'Seth Chisamore']
|
9
|
-
s.email = ['adam@
|
10
|
-
s.homepage = 'https://github.com/
|
9
|
+
s.email = ['adam@chef.io', 'schisamo@chef.io']
|
10
|
+
s.homepage = 'https://github.com/chef/knife-ec2'
|
11
11
|
s.summary = "EC2 Support for Chef's Knife Command"
|
12
12
|
s.description = s.summary
|
13
13
|
s.license = 'Apache-2.0'
|
@@ -16,7 +16,8 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
18
18
|
|
19
|
-
s.add_dependency 'fog',
|
19
|
+
s.add_dependency 'fog-aws', '~> 0.7'
|
20
|
+
s.add_dependency 'mime-types'
|
20
21
|
s.add_dependency 'knife-windows', '~> 1.0'
|
21
22
|
|
22
23
|
s.add_development_dependency 'chef', '~> 12.0', '>= 12.2.1'
|
data/lib/chef/knife/ec2_base.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
|
-
# Author:: Seth Chisamore (<schisamo@
|
3
|
-
# Copyright:: Copyright (c) 2011
|
2
|
+
# Author:: Seth Chisamore (<schisamo@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2011-2015 Chef Software, Inc.
|
4
4
|
# License:: Apache License, Version 2.0
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -29,19 +29,24 @@ class Chef
|
|
29
29
|
includer.class_eval do
|
30
30
|
|
31
31
|
deps do
|
32
|
-
require 'fog'
|
32
|
+
require 'fog/aws'
|
33
33
|
require 'readline'
|
34
34
|
require 'chef/json_compat'
|
35
35
|
end
|
36
36
|
|
37
37
|
option :aws_credential_file,
|
38
38
|
:long => "--aws-credential-file FILE",
|
39
|
-
:description => "File containing AWS credentials as used by
|
39
|
+
:description => "File containing AWS credentials as used by AWS command line tools",
|
40
40
|
:proc => Proc.new { |key| Chef::Config[:knife][:aws_credential_file] = key }
|
41
41
|
|
42
|
+
option :aws_config_file,
|
43
|
+
:long => "--aws-config-file FILE",
|
44
|
+
:description => "File containing AWS configurations as used by aws cmdline tools",
|
45
|
+
:proc => Proc.new {|key| Chef::Config[:knife][:aws_config_file] = key}
|
46
|
+
|
42
47
|
option :aws_profile,
|
43
48
|
:long => "--aws-profile PROFILE",
|
44
|
-
:description => "AWS profile, from credential file, to use",
|
49
|
+
:description => "AWS profile, from AWS credential file and AWS config file, to use",
|
45
50
|
:default => 'default',
|
46
51
|
:proc => Proc.new { |key| Chef::Config[:knife][:aws_profile] = key }
|
47
52
|
|
@@ -81,6 +86,7 @@ class Chef
|
|
81
86
|
:provider => 'AWS',
|
82
87
|
:region => locate_config_value(:region)
|
83
88
|
}
|
89
|
+
|
84
90
|
if locate_config_value(:use_iam_profile)
|
85
91
|
connection_settings[:use_iam_profile] = true
|
86
92
|
else
|
@@ -112,8 +118,25 @@ class Chef
|
|
112
118
|
def validate!(keys=[:aws_access_key_id, :aws_secret_access_key])
|
113
119
|
errors = []
|
114
120
|
|
121
|
+
if locate_config_value(:aws_config_file)
|
122
|
+
aws_config = ini_parse(File.read(locate_config_value(:aws_config_file)))
|
123
|
+
profile = if locate_config_value(:aws_profile) == 'default'
|
124
|
+
'default'
|
125
|
+
else
|
126
|
+
"profile #{locate_config_value(:aws_profile)}"
|
127
|
+
end
|
128
|
+
|
129
|
+
unless aws_config.values.empty?
|
130
|
+
if aws_config[profile]
|
131
|
+
Chef::Config[:knife][:region] = aws_config[profile]['region']
|
132
|
+
else
|
133
|
+
raise ArgumentError, "The provided --aws-profile '#{profile}' is invalid."
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
115
138
|
unless locate_config_value(:use_iam_profile)
|
116
|
-
|
139
|
+
if locate_config_value(:aws_credential_file)
|
117
140
|
unless (Chef::Config[:knife].keys & [:aws_access_key_id, :aws_secret_access_key]).empty?
|
118
141
|
errors << "Either provide a credentials file or the access key and secret keys but not both."
|
119
142
|
end
|
@@ -125,12 +148,22 @@ class Chef
|
|
125
148
|
# aws_access_key_id = somethingsomethingdarkside
|
126
149
|
# aws_secret_access_key = somethingsomethingdarkside
|
127
150
|
|
128
|
-
aws_creds = ini_parse(File.read(
|
129
|
-
profile =
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
151
|
+
aws_creds = ini_parse(File.read(locate_config_value(:aws_credential_file)))
|
152
|
+
profile = locate_config_value(:aws_profile)
|
153
|
+
|
154
|
+
entries = if aws_creds.values.first.has_key?("AWSAccessKeyId")
|
155
|
+
aws_creds.values.first
|
156
|
+
else
|
157
|
+
aws_creds[profile]
|
158
|
+
end
|
159
|
+
|
160
|
+
if entries
|
161
|
+
Chef::Config[:knife][:aws_access_key_id] = entries['AWSAccessKeyId'] || entries['aws_access_key_id']
|
162
|
+
Chef::Config[:knife][:aws_secret_access_key] = entries['AWSSecretKey'] || entries['aws_secret_access_key']
|
163
|
+
Chef::Config[:knife][:aws_session_token] = entries['AWSSessionToken'] || entries['aws_session_token']
|
164
|
+
else
|
165
|
+
raise ArgumentError, "The provided --aws-profile '#{profile}' is invalid."
|
166
|
+
end
|
134
167
|
end
|
135
168
|
|
136
169
|
keys.each do |k|
|
@@ -1,53 +1,53 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Seth Chisamore (<schisamo@
|
3
|
-
# Copyright:: Copyright (c) 2012
|
4
|
-
# License:: Apache License, Version 2.0
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#
|
18
|
-
|
19
|
-
require 'chef/knife/ec2_base'
|
20
|
-
|
21
|
-
class Chef
|
22
|
-
class Knife
|
23
|
-
class Ec2FlavorList < Knife
|
24
|
-
|
25
|
-
include Knife::Ec2Base
|
26
|
-
|
27
|
-
banner "knife ec2 flavor list (options)"
|
28
|
-
|
29
|
-
def run
|
30
|
-
|
31
|
-
validate!
|
32
|
-
|
33
|
-
flavor_list = [
|
34
|
-
ui.color('ID', :bold),
|
35
|
-
ui.color('Name', :bold),
|
36
|
-
ui.color('Architecture', :bold),
|
37
|
-
ui.color('RAM', :bold),
|
38
|
-
ui.color('Disk', :bold),
|
39
|
-
ui.color('Cores', :bold)
|
40
|
-
]
|
41
|
-
connection.flavors.sort_by(&:id).each do |flavor|
|
42
|
-
flavor_list << flavor.id.to_s
|
43
|
-
flavor_list << flavor.name
|
44
|
-
flavor_list << "#{flavor.bits.to_s}-bit"
|
45
|
-
flavor_list << "#{flavor.ram.to_s}"
|
46
|
-
flavor_list << "#{flavor.disk.to_s} GB"
|
47
|
-
flavor_list << flavor.cores.to_s
|
48
|
-
end
|
49
|
-
puts ui.list(flavor_list, :columns_across, 6)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
1
|
+
#
|
2
|
+
# Author:: Seth Chisamore (<schisamo@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2012-2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife/ec2_base'
|
20
|
+
|
21
|
+
class Chef
|
22
|
+
class Knife
|
23
|
+
class Ec2FlavorList < Knife
|
24
|
+
|
25
|
+
include Knife::Ec2Base
|
26
|
+
|
27
|
+
banner "knife ec2 flavor list (options)"
|
28
|
+
|
29
|
+
def run
|
30
|
+
|
31
|
+
validate!
|
32
|
+
|
33
|
+
flavor_list = [
|
34
|
+
ui.color('ID', :bold),
|
35
|
+
ui.color('Name', :bold),
|
36
|
+
ui.color('Architecture', :bold),
|
37
|
+
ui.color('RAM', :bold),
|
38
|
+
ui.color('Disk', :bold),
|
39
|
+
ui.color('Cores', :bold)
|
40
|
+
]
|
41
|
+
connection.flavors.sort_by(&:id).each do |flavor|
|
42
|
+
flavor_list << flavor.id.to_s
|
43
|
+
flavor_list << flavor.name
|
44
|
+
flavor_list << "#{flavor.bits.to_s}-bit"
|
45
|
+
flavor_list << "#{flavor.ram.to_s}"
|
46
|
+
flavor_list << "#{flavor.disk.to_s} GB"
|
47
|
+
flavor_list << flavor.cores.to_s
|
48
|
+
end
|
49
|
+
puts ui.list(flavor_list, :columns_across, 6)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#
|
2
|
-
# Author:: Adam Jacob (<adam@
|
3
|
-
# Author:: Seth Chisamore (<schisamo@
|
4
|
-
# Copyright:: Copyright (c) 2010-
|
2
|
+
# Author:: Adam Jacob (<adam@chef.io>)
|
3
|
+
# Author:: Seth Chisamore (<schisamo@chef.io>)
|
4
|
+
# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
|
5
5
|
# License:: Apache License, Version 2.0
|
6
6
|
#
|
7
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -31,7 +31,7 @@ class Chef
|
|
31
31
|
include Knife::BootstrapWindowsBase
|
32
32
|
deps do
|
33
33
|
require 'tempfile'
|
34
|
-
require 'fog'
|
34
|
+
require 'fog/aws'
|
35
35
|
require 'uri'
|
36
36
|
require 'readline'
|
37
37
|
require 'chef/json_compat'
|
@@ -67,9 +67,9 @@ class Chef
|
|
67
67
|
:proc => Proc.new { |groups| groups.split(',') }
|
68
68
|
|
69
69
|
option :security_group_ids,
|
70
|
-
:short => "-g X,Y,Z",
|
71
|
-
:long => "--security-group-ids X,Y,Z",
|
72
|
-
:description => "The security group ids for this server; required when using VPC",
|
70
|
+
:short => "-g 'X,Y,Z'",
|
71
|
+
:long => "--security-group-ids 'X,Y,Z'",
|
72
|
+
:description => "The security group ids for this server; required when using VPC,Please provide values in format --security-group-ids 'X,Y,Z'",
|
73
73
|
:proc => Proc.new { |security_group_ids| security_group_ids.split(',') }
|
74
74
|
|
75
75
|
option :associate_eip,
|
@@ -194,7 +194,8 @@ class Chef
|
|
194
194
|
:short => "-r RUN_LIST",
|
195
195
|
:long => "--run-list RUN_LIST",
|
196
196
|
:description => "Comma separated list of roles/recipes to apply",
|
197
|
-
:proc => lambda { |o| o.split(/[\s,]+/) }
|
197
|
+
:proc => lambda { |o| o.split(/[\s,]+/) },
|
198
|
+
:default => []
|
198
199
|
|
199
200
|
option :secret,
|
200
201
|
:long => "--secret ",
|
@@ -211,12 +212,6 @@ class Chef
|
|
211
212
|
:description => 'S3 URL (e.g. s3://bucket/file) for the encrypted_data_bag_secret_file',
|
212
213
|
:proc => lambda { |url| Chef::Config[:knife][:s3_secret] = url }
|
213
214
|
|
214
|
-
option :json_attributes,
|
215
|
-
:short => "-j JSON",
|
216
|
-
:long => "--json-attributes JSON",
|
217
|
-
:description => "A JSON string to be added to the first run of chef-client",
|
218
|
-
:proc => lambda { |o| JSON.parse(o) }
|
219
|
-
|
220
215
|
option :subnet_id,
|
221
216
|
:long => "--subnet SUBNET-ID",
|
222
217
|
:description => "create node in this Virtual Private Cloud Subnet ID (implies VPC mode)",
|
@@ -270,7 +265,7 @@ class Chef
|
|
270
265
|
option :server_connect_attribute,
|
271
266
|
:long => "--server-connect-attribute ATTRIBUTE",
|
272
267
|
:short => "-a ATTRIBUTE",
|
273
|
-
:description => "The EC2 server attribute to use for SSH connection
|
268
|
+
:description => "The EC2 server attribute to use for the SSH connection if necessary, e.g. public_ip_address or private_ip_address.",
|
274
269
|
:default => nil
|
275
270
|
|
276
271
|
option :associate_public_ip,
|
@@ -317,6 +312,15 @@ class Chef
|
|
317
312
|
:description => "The Spot Instance request type. Possible values are 'one-time' and 'persistent', default value is 'one-time'",
|
318
313
|
:default => "one-time"
|
319
314
|
|
315
|
+
option :spot_wait_mode,
|
316
|
+
:long => "--spot-wait-mode MODE",
|
317
|
+
:description =>
|
318
|
+
"Whether we should wait for spot request fulfillment. Could be 'wait', 'exit', or " \
|
319
|
+
"'prompt' (default). For any of the above mentioned choices, ('wait') - if the " \
|
320
|
+
"instance does not get allocated before the command itself times-out or ('exit') the " \
|
321
|
+
"user needs to manually bootstrap the instance in the future after it gets allocated.",
|
322
|
+
:default => "prompt"
|
323
|
+
|
320
324
|
option :aws_connection_timeout,
|
321
325
|
:long => "--aws-connection-timeout MINUTES",
|
322
326
|
:description => "The maximum time in minutes to wait to for aws connection. Default is 10 min",
|
@@ -393,6 +397,33 @@ class Chef
|
|
393
397
|
:description => "Enable SSH agent forwarding",
|
394
398
|
:boolean => true
|
395
399
|
|
400
|
+
option :create_ssl_listener,
|
401
|
+
:long => "--[no-]create-ssl-listener",
|
402
|
+
:description => "Create ssl listener, enabled by default.",
|
403
|
+
:boolean => true,
|
404
|
+
:default => true
|
405
|
+
|
406
|
+
option :network_interfaces,
|
407
|
+
:short => '-n',
|
408
|
+
:long => '--attach-network-interface ENI1,ENI2',
|
409
|
+
:description => 'Attach additional network interfaces during bootstrap',
|
410
|
+
:proc => proc { |nics| nics.split(',') }
|
411
|
+
|
412
|
+
option :classic_link_vpc_id,
|
413
|
+
:long => "--classic-link-vpc-id VPC_ID",
|
414
|
+
:description => "Enable ClassicLink connection with a VPC"
|
415
|
+
|
416
|
+
option :classic_link_vpc_security_group_ids,
|
417
|
+
:long => "--classic-link-vpc-security-groups-ids X,Y,Z",
|
418
|
+
:description => "Comma-separated list of security group ids for ClassicLink",
|
419
|
+
:proc => Proc.new { |groups| groups.split(',') }
|
420
|
+
|
421
|
+
option :disable_api_termination,
|
422
|
+
:long => "--disable-api-termination",
|
423
|
+
:description => "Disable termination of the instance using the Amazon EC2 console, CLI and API.",
|
424
|
+
:boolean => true,
|
425
|
+
:default => false
|
426
|
+
|
396
427
|
def run
|
397
428
|
$stdout.sync = true
|
398
429
|
|
@@ -404,17 +435,30 @@ class Chef
|
|
404
435
|
elastic_ip = connection.addresses.detect{|addr| addr if addr.public_ip == requested_elastic_ip}
|
405
436
|
|
406
437
|
if locate_config_value(:spot_price)
|
407
|
-
|
438
|
+
server_def = create_server_def
|
439
|
+
server_def[:groups] = config[:security_group_ids] if vpc_mode?
|
440
|
+
spot_request = connection.spot_requests.create(server_def)
|
408
441
|
msg_pair("Spot Request ID", spot_request.id)
|
409
442
|
msg_pair("Spot Request Type", spot_request.request_type)
|
410
443
|
msg_pair("Spot Price", spot_request.price)
|
411
444
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
445
|
+
case config[:spot_wait_mode]
|
446
|
+
when 'prompt', '', nil
|
447
|
+
wait_msg = "Do you want to wait for Spot Instance Request fulfillment? (Y/N) \n"
|
448
|
+
wait_msg += "Y - Wait for Spot Instance request fulfillment\n"
|
449
|
+
wait_msg += "N - Do not wait for Spot Instance request fulfillment. "
|
450
|
+
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)
|
451
|
+
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)
|
452
|
+
confirm(wait_msg)
|
453
|
+
when 'wait'
|
454
|
+
# wait for the node and run Chef bootstrap
|
455
|
+
when 'exit'
|
456
|
+
ui.color("The 'exit' option was specified for --spot-wait-mode, exiting.", :cyan)
|
457
|
+
exit
|
458
|
+
else
|
459
|
+
raise "Invalid value for --spot-wait-mode: '#{config[:spot_wait_mode]}', " \
|
460
|
+
"valid values: wait, exit, prompt"
|
461
|
+
end
|
418
462
|
|
419
463
|
print ui.color("Waiting for Spot Request fulfillment: ", :cyan)
|
420
464
|
spot_request.wait_for do
|
@@ -473,6 +517,7 @@ class Chef
|
|
473
517
|
begin
|
474
518
|
create_tags(hashed_tags) unless hashed_tags.empty?
|
475
519
|
associate_eip(elastic_ip) if config[:associate_eip]
|
520
|
+
enable_classic_link(config[:classic_link_vpc_id], config[:classic_link_vpc_security_group_ids]) if config[:classic_link_vpc_id]
|
476
521
|
rescue Fog::Compute::AWS::NotFound, Fog::Errors::Error
|
477
522
|
raise if (tries -= 1) <= 0
|
478
523
|
ui.warn("server not ready, retrying tag application (retries left: #{tries})")
|
@@ -480,6 +525,8 @@ class Chef
|
|
480
525
|
retry
|
481
526
|
end
|
482
527
|
|
528
|
+
attach_nics if config[:network_interfaces]
|
529
|
+
|
483
530
|
if vpc_mode?
|
484
531
|
msg_pair("Subnet ID", @server.subnet_id)
|
485
532
|
msg_pair("Tenancy", @server.tenancy)
|
@@ -589,7 +636,9 @@ class Chef
|
|
589
636
|
msg_pair("Private IP Address", @server.private_ip_address)
|
590
637
|
msg_pair("Environment", config[:environment] || '_default')
|
591
638
|
msg_pair("Run List", (config[:run_list] || []).join(', '))
|
592
|
-
|
639
|
+
if config[:first_boot_attributes] || config[:first_boot_attributes_from_file]
|
640
|
+
msg_pair("JSON Attributes",config[:first_boot_attributes] || config[:first_boot_attributes_from_file])
|
641
|
+
end
|
593
642
|
end
|
594
643
|
|
595
644
|
def default_bootstrap_template
|
@@ -642,9 +691,14 @@ class Chef
|
|
642
691
|
bootstrap.config[:template_file] = locate_config_value(:template_file) || locate_config_value(:bootstrap_template)
|
643
692
|
bootstrap.config[:environment] = locate_config_value(:environment)
|
644
693
|
bootstrap.config[:prerelease] = config[:prerelease]
|
645
|
-
bootstrap.config[:first_boot_attributes] = locate_config_value(:
|
646
|
-
bootstrap.config[:
|
647
|
-
bootstrap.config[:
|
694
|
+
bootstrap.config[:first_boot_attributes] = locate_config_value(:first_boot_attributes)
|
695
|
+
bootstrap.config[:first_boot_attributes_from_file] = locate_config_value(:first_boot_attributes_from_file)
|
696
|
+
bootstrap.config[:encrypted_data_bag_secret] = s3_secret || locate_config_value(:secret)
|
697
|
+
bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:secret_file)
|
698
|
+
# retrieving the secret from S3 is unique to knife-ec2, so we need to set "command line secret" to the value fetched from S3
|
699
|
+
# When linux vm is spawned, the chef's secret option proc function sets the value "command line secret" and this value is used by
|
700
|
+
# chef's code to check if secret option is passed through command line or not
|
701
|
+
Chef::Knife::DataBagSecretOptions.set_cl_secret(s3_secret) if locate_config_value(:s3_secret)
|
648
702
|
bootstrap.config[:secret] = s3_secret || locate_config_value(:secret)
|
649
703
|
bootstrap.config[:secret_file] = locate_config_value(:secret_file)
|
650
704
|
bootstrap.config[:node_ssl_verify_mode] = locate_config_value(:node_ssl_verify_mode)
|
@@ -741,6 +795,8 @@ class Chef
|
|
741
795
|
|
742
796
|
super([:image, :ssh_key_name, :aws_access_key_id, :aws_secret_access_key])
|
743
797
|
|
798
|
+
validate_nics! if locate_config_value(:network_interfaces)
|
799
|
+
|
744
800
|
if ami.nil?
|
745
801
|
ui.error("You have not provided a valid image (AMI) value.")
|
746
802
|
exit 1
|
@@ -791,15 +847,26 @@ class Chef
|
|
791
847
|
exit 1
|
792
848
|
end
|
793
849
|
|
794
|
-
if
|
850
|
+
if config[:security_groups] && config[:security_groups].class == String
|
795
851
|
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']")
|
796
852
|
exit 1
|
797
853
|
end
|
798
854
|
|
799
|
-
if
|
855
|
+
if config[:security_group_ids] && config[:security_group_ids].class == String
|
800
856
|
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']")
|
801
857
|
exit 1
|
802
858
|
end
|
859
|
+
|
860
|
+
if config[:classic_link_vpc_id].nil? ^ config[:classic_link_vpc_security_group_ids].nil?
|
861
|
+
ui.error("--classic-link-vpc-id and --classic-link-vpc-security-group-ids must be used together")
|
862
|
+
exit 1
|
863
|
+
end
|
864
|
+
|
865
|
+
if vpc_mode? and config[:classic_link_vpc_id]
|
866
|
+
ui.error("You can only use ClassicLink if you are not using a VPC")
|
867
|
+
exit 1
|
868
|
+
end
|
869
|
+
|
803
870
|
if locate_config_value(:ebs_encrypted)
|
804
871
|
error_message = ""
|
805
872
|
errors = []
|
@@ -807,10 +874,12 @@ class Chef
|
|
807
874
|
if !locate_config_value(:flavor)
|
808
875
|
ui.error("--ebs-encrypted option requires valid flavor to be specified.")
|
809
876
|
exit 1
|
810
|
-
elsif (locate_config_value(:ebs_encrypted) and ! %w(m3.medium m3.large m3.xlarge m3.2xlarge
|
877
|
+
elsif (locate_config_value(:ebs_encrypted) and ! %w(m3.medium m3.large m3.xlarge m3.2xlarge m4.large m4.xlarge
|
878
|
+
m4.2xlarge m4.4xlarge m4.10xlarge t2.micro t2.small t2.medium t2.large
|
879
|
+
d2.xlarge d2.2xlarge d2.4xlarge d2.8xlarge c4.large c4.xlarge
|
811
880
|
c4.2xlarge c4.4xlarge c4.8xlarge c3.large c3.xlarge c3.2xlarge
|
812
881
|
c3.4xlarge c3.8xlarge cr1.8xlarge r3.large r3.xlarge r3.2xlarge
|
813
|
-
r3.4xlarge r3.8xlarge i2.xlarge i2.2xlarge i2.4xlarge i2.8xlarge g2.2xlarge).include?(locate_config_value(:flavor)))
|
882
|
+
r3.4xlarge r3.8xlarge i2.xlarge i2.2xlarge i2.4xlarge i2.8xlarge g2.2xlarge g2.8xlarge).include?(locate_config_value(:flavor)))
|
814
883
|
ui.error("--ebs-encrypted option is not supported for #{locate_config_value(:flavor)} flavor.")
|
815
884
|
exit 1
|
816
885
|
end
|
@@ -832,6 +901,16 @@ class Chef
|
|
832
901
|
end
|
833
902
|
end
|
834
903
|
|
904
|
+
if locate_config_value(:spot_price) && locate_config_value(:disable_api_termination)
|
905
|
+
ui.error("spot-price and disable-api-termination options cannot be passed together as 'Termination Protection' cannot be enabled for spot instances.")
|
906
|
+
exit 1
|
907
|
+
end
|
908
|
+
|
909
|
+
if locate_config_value(:spot_price).nil? && locate_config_value(:spot_wait_mode).downcase != 'prompt'
|
910
|
+
ui.error('spot-wait-mode option requires that a spot-price option is set.')
|
911
|
+
exit 1
|
912
|
+
end
|
913
|
+
|
835
914
|
end
|
836
915
|
|
837
916
|
def tags
|
@@ -851,6 +930,57 @@ class Chef
|
|
851
930
|
end
|
852
931
|
end
|
853
932
|
|
933
|
+
def ssl_config_user_data
|
934
|
+
user_related_commands = ""
|
935
|
+
winrm_user = locate_config_value(:winrm_user).split("\\")
|
936
|
+
if (winrm_user[0] == ".") || (winrm_user[0] == "") ||(winrm_user.length == 1)
|
937
|
+
user_related_commands = <<-EOH
|
938
|
+
net user /add #{locate_config_value(:winrm_user).delete('.\\')} #{windows_password};
|
939
|
+
net localgroup Administrators /add #{locate_config_value(:winrm_user).delete('.\\')};
|
940
|
+
EOH
|
941
|
+
end
|
942
|
+
<<-EOH
|
943
|
+
#{user_related_commands}
|
944
|
+
If (-Not (Get-Service WinRM | Where-Object {$_.status -eq "Running"})) {
|
945
|
+
winrm quickconfig -q
|
946
|
+
}
|
947
|
+
If (winrm e winrm/config/listener | Select-String -Pattern " Transport = HTTP\\b" -Quiet) {
|
948
|
+
winrm delete winrm/config/listener?Address=*+Transport=HTTP
|
949
|
+
}
|
950
|
+
$vm_name = invoke-restmethod -uri http://169.254.169.254/latest/meta-data/public-ipv4
|
951
|
+
New-SelfSignedCertificate -certstorelocation cert:\\localmachine\\my -dnsname $vm_name
|
952
|
+
$thumbprint = (Get-ChildItem -Path cert:\\localmachine\\my | Where-Object {$_.Subject -match "$vm_name"}).Thumbprint;
|
953
|
+
$create_listener_cmd = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS '@{Hostname=`"$vm_name`";CertificateThumbprint=`"$thumbprint`"}'"
|
954
|
+
iex $create_listener_cmd
|
955
|
+
|
956
|
+
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
|
957
|
+
|
958
|
+
EOH
|
959
|
+
end
|
960
|
+
|
961
|
+
def ssl_config_data_already_exist?
|
962
|
+
File.read(locate_config_value(:aws_user_data)).gsub(/\\\\/,"\\").include? ssl_config_user_data.strip
|
963
|
+
end
|
964
|
+
|
965
|
+
def process_user_data(script_lines)
|
966
|
+
if !ssl_config_data_already_exist?
|
967
|
+
ps_start_tag = "<powershell>\n"
|
968
|
+
ps_end_tag = "</powershell>\n"
|
969
|
+
ps_start_tag_index = script_lines.index(ps_start_tag) || script_lines.index(ps_start_tag.strip)
|
970
|
+
ps_end_tag_index = script_lines.index(ps_end_tag) || script_lines.index(ps_end_tag.strip)
|
971
|
+
case
|
972
|
+
when ( ps_start_tag_index && !ps_end_tag_index ) || ( !ps_start_tag_index && ps_end_tag_index )
|
973
|
+
ui.error("Provided user_data file is invalid.")
|
974
|
+
exit 1
|
975
|
+
when ps_start_tag_index && ps_end_tag_index
|
976
|
+
script_lines[ps_end_tag_index] = ssl_config_user_data + ps_end_tag
|
977
|
+
when !ps_start_tag_index && !ps_end_tag_index
|
978
|
+
script_lines.insert(-1,"\n\n" + ps_start_tag + ssl_config_user_data + ps_end_tag)
|
979
|
+
end
|
980
|
+
end
|
981
|
+
script_lines
|
982
|
+
end
|
983
|
+
|
854
984
|
def create_server_def
|
855
985
|
server_def = {
|
856
986
|
:image_id => locate_config_value(:image),
|
@@ -869,11 +999,30 @@ class Chef
|
|
869
999
|
server_def[:tenancy] = "dedicated" if vpc_mode? and locate_config_value(:dedicated_instance)
|
870
1000
|
server_def[:associate_public_ip] = locate_config_value(:associate_public_ip) if vpc_mode? and config[:associate_public_ip]
|
871
1001
|
|
872
|
-
if locate_config_value(:
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
1002
|
+
if locate_config_value(:winrm_transport) == 'ssl'
|
1003
|
+
if locate_config_value(:aws_user_data)
|
1004
|
+
begin
|
1005
|
+
user_data = File.readlines(locate_config_value(:aws_user_data))
|
1006
|
+
if config[:create_ssl_listener]
|
1007
|
+
user_data = process_user_data(user_data)
|
1008
|
+
end
|
1009
|
+
user_data = user_data.join
|
1010
|
+
server_def.merge!(:user_data => user_data)
|
1011
|
+
rescue
|
1012
|
+
ui.warn("Cannot read #{locate_config_value(:aws_user_data)}: #{$!.inspect}. Ignoring option.")
|
1013
|
+
end
|
1014
|
+
else
|
1015
|
+
if config[:create_ssl_listener]
|
1016
|
+
server_def.merge!(:user_data => "<powershell>\n" + ssl_config_user_data + "</powershell>\n")
|
1017
|
+
end
|
1018
|
+
end
|
1019
|
+
else
|
1020
|
+
if locate_config_value(:aws_user_data)
|
1021
|
+
begin
|
1022
|
+
server_def.merge!(:user_data => File.read(locate_config_value(:aws_user_data)))
|
1023
|
+
rescue
|
1024
|
+
ui.warn("Cannot read #{locate_config_value(:aws_user_data)}: #{$!.inspect}. Ignoring option.")
|
1025
|
+
end
|
877
1026
|
end
|
878
1027
|
end
|
879
1028
|
|
@@ -933,6 +1082,9 @@ class Chef
|
|
933
1082
|
server_def[:block_device_mapping] = (server_def[:block_device_mapping] || []) << {'VirtualName' => "ephemeral#{i}", 'DeviceName' => device_name}
|
934
1083
|
end
|
935
1084
|
|
1085
|
+
## cannot pass disable_api_termination option to the API when using spot instances ##
|
1086
|
+
server_def[:disable_api_termination] = locate_config_value(:disable_api_termination) if locate_config_value(:spot_price).nil?
|
1087
|
+
|
936
1088
|
server_def
|
937
1089
|
end
|
938
1090
|
|
@@ -1040,15 +1192,22 @@ class Chef
|
|
1040
1192
|
end
|
1041
1193
|
|
1042
1194
|
def ssh_connect_host
|
1043
|
-
@ssh_connect_host
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1195
|
+
unless @ssh_connect_host
|
1196
|
+
if config[:server_connect_attribute]
|
1197
|
+
connect_attribute = config[:server_connect_attribute]
|
1198
|
+
else
|
1199
|
+
if vpc_mode? && !(config[:associate_public_ip] || config[:associate_eip])
|
1200
|
+
connect_attribute = "private_ip_address"
|
1201
|
+
else
|
1202
|
+
connect_attribute = server.dns_name ? "dns_name" : "public_ip_address"
|
1203
|
+
end
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
@ssh_connect_host = server.send(connect_attribute)
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
puts "\nSSH Target Address: #{@ssh_connect_host}(#{connect_attribute})"
|
1210
|
+
@ssh_connect_host
|
1052
1211
|
end
|
1053
1212
|
|
1054
1213
|
def create_tags(hashed_tags)
|
@@ -1062,6 +1221,53 @@ class Chef
|
|
1062
1221
|
@server.wait_for(locate_config_value(:aws_connection_timeout)) { public_ip_address == elastic_ip.public_ip }
|
1063
1222
|
end
|
1064
1223
|
|
1224
|
+
def validate_nics!
|
1225
|
+
valid_nic_ids = connection.network_interfaces.all(
|
1226
|
+
vpc_mode? ? { 'vpc-id' => vpc_id } : {}
|
1227
|
+
).map(&:network_interface_id)
|
1228
|
+
invalid_nic_ids =
|
1229
|
+
locate_config_value(:network_interfaces) - valid_nic_ids
|
1230
|
+
return true if invalid_nic_ids.empty?
|
1231
|
+
ui.error 'The following network interfaces are invalid: ' \
|
1232
|
+
"#{invalid_nic_ids.join(', ')}"
|
1233
|
+
exit 1
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
def vpc_id
|
1237
|
+
@vpc_id ||= begin
|
1238
|
+
connection.subnets.get(locate_config_value(:subnet_id)).vpc_id
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
def wait_for_nic_attachment
|
1243
|
+
attached_nics_count = 0
|
1244
|
+
until attached_nics_count ==
|
1245
|
+
locate_config_value(:network_interfaces).count
|
1246
|
+
attachment_nics =
|
1247
|
+
locate_config_value(:network_interfaces).map do |nic_id|
|
1248
|
+
connection.network_interfaces.get(nic_id).attachment['status']
|
1249
|
+
end
|
1250
|
+
attached_nics_count = attachment_nics.grep('attached').count
|
1251
|
+
end
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
def attach_nics
|
1255
|
+
attachments = []
|
1256
|
+
config[:network_interfaces].each_with_index do |nic_id, index|
|
1257
|
+
attachments << connection.attach_network_interface(nic_id,
|
1258
|
+
server.id,
|
1259
|
+
index + 1).body
|
1260
|
+
end
|
1261
|
+
wait_for_nic_attachment
|
1262
|
+
# rubocop:disable Style/RedundantReturn
|
1263
|
+
return attachments
|
1264
|
+
# rubocop:enable Style/RedundantReturn
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
def enable_classic_link(vpc_id, security_group_ids)
|
1268
|
+
connection.attach_classic_link_vpc(server.id, vpc_id, security_group_ids)
|
1269
|
+
end
|
1270
|
+
|
1065
1271
|
def ssh_override_winrm
|
1066
1272
|
# unchanged ssh_user and changed winrm_user, override ssh_user
|
1067
1273
|
if locate_config_value(:ssh_user).eql?(options[:ssh_user][:default]) &&
|
@@ -1124,7 +1330,7 @@ class Chef
|
|
1124
1330
|
else
|
1125
1331
|
false
|
1126
1332
|
end
|
1127
|
-
rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
|
1333
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ENOTCONN, IOError
|
1128
1334
|
Chef::Log.debug("ssh failed to connect: #{hostname}")
|
1129
1335
|
sleep 2
|
1130
1336
|
false
|