knife-ec2 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|