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.
@@ -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