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.
@@ -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@opscode.com', 'schisamo@opscode.com']
10
- s.homepage = 'https://github.com/opscode/knife-ec2'
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', '~> 1.29.0'
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'
@@ -1,6 +1,6 @@
1
1
  #
2
- # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
- # Copyright:: Copyright (c) 2011 Opscode, Inc.
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 aws cmdline tools",
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
- unless Chef::Config[:knife][:aws_credential_file].nil?
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(Chef::Config[:knife][:aws_credential_file]))
129
- profile = Chef::Config[:knife][:aws_profile] || 'default'
130
- entries = aws_creds.values.first.has_key?("AWSAccessKeyId") ? aws_creds.values.first : aws_creds[profile]
131
-
132
- Chef::Config[:knife][:aws_access_key_id] = entries['AWSAccessKeyId'] || entries['aws_access_key_id']
133
- Chef::Config[:knife][:aws_secret_access_key] = entries['AWSSecretKey'] || entries['aws_secret_access_key']
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@opscode.com>)
3
- # Copyright:: Copyright (c) 2012 Opscode, 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
+ #
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@opscode.com>)
3
- # Author:: Seth Chisamore (<schisamo@opscode.com>)
4
- # Copyright:: Copyright (c) 2010-2011 Opscode, Inc.
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. Use this attr for creating VPC instances along with --associate-eip",
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
- spot_request = connection.spot_requests.create(create_server_def)
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
- wait_msg = "Do you want to wait for Spot Instance Request fulfillment? (Y/N) \n"
413
- wait_msg += "Y - Wait for Spot Instance request fulfillment\n"
414
- wait_msg += "N - Do not wait for Spot Instance request fulfillment. "
415
- 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)
416
- 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 future after it gets allocated.]\n\n", :cyan, :bold)
417
- confirm(wait_msg)
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
- msg_pair("JSON Attributes",config[:json_attributes]) unless !config[:json_attributes] || config[:json_attributes].empty?
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(:json_attributes) || {}
646
- bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:encrypted_data_bag_secret)
647
- bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:encrypted_data_bag_secret_file)
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(config[:security_groups] && config[:security_groups].class == String)
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(config[:security_group_ids] && config[:security_group_ids].class == String)
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 c4.large c4.xlarge
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(:aws_user_data)
873
- begin
874
- server_def.merge!(:user_data => File.read(locate_config_value(:aws_user_data)))
875
- rescue
876
- ui.warn("Cannot read #{locate_config_value(:aws_user_data)}: #{$!.inspect}. Ignoring option.")
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 ||= if config[:server_connect_attribute]
1044
- server.send(config[:server_connect_attribute])
1045
- else
1046
- if vpc_mode? && !config[:associate_public_ip]
1047
- server.private_ip_address
1048
- else
1049
- server.dns_name || server.public_ip_address
1050
- end
1051
- end
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