chef-provisioning-aws 1.2.1 → 1.3.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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -8
  3. data/lib/chef/provider/aws_cache_cluster.rb +75 -0
  4. data/lib/chef/provider/aws_cache_replication_group.rb +49 -0
  5. data/lib/chef/provider/aws_cache_subnet_group.rb +60 -0
  6. data/lib/chef/provider/aws_instance.rb +4 -1
  7. data/lib/chef/provider/aws_key_pair.rb +1 -1
  8. data/lib/chef/provider/aws_network_acl.rb +131 -0
  9. data/lib/chef/provider/aws_security_group.rb +1 -1
  10. data/lib/chef/provider/aws_subnet.rb +14 -0
  11. data/lib/chef/provider/aws_vpc.rb +1 -0
  12. data/lib/chef/provisioning/aws_driver.rb +4 -0
  13. data/lib/chef/provisioning/aws_driver/aws_provider.rb +25 -0
  14. data/lib/chef/provisioning/aws_driver/aws_resource.rb +7 -2
  15. data/lib/chef/provisioning/aws_driver/driver.rb +59 -24
  16. data/lib/chef/provisioning/aws_driver/version.rb +1 -1
  17. data/lib/chef/resource/aws_cache_cluster.rb +37 -0
  18. data/lib/chef/resource/aws_cache_replication_group.rb +37 -0
  19. data/lib/chef/resource/aws_cache_subnet_group.rb +28 -0
  20. data/lib/chef/resource/aws_network_acl.rb +61 -0
  21. data/lib/chef/resource/aws_subnet.rb +9 -0
  22. data/spec/aws_support.rb +4 -1
  23. data/spec/aws_support/matchers/match_an_aws_object.rb +58 -0
  24. data/spec/integration/aws_cache_subnet_group_spec.rb +32 -0
  25. data/spec/integration/aws_key_pair_spec.rb +2 -2
  26. data/spec/integration/aws_network_acl_spec.rb +107 -0
  27. data/spec/integration/aws_security_group_spec.rb +16 -0
  28. data/spec/integration/aws_subnet_spec.rb +8 -11
  29. data/spec/integration/aws_tagged_items_spec.rb +1 -1
  30. data/spec/integration/aws_vpc_spec.rb +16 -0
  31. metadata +27 -2
@@ -42,6 +42,7 @@ class Chef::Provider::AwsVpc < Chef::Provisioning::AWSDriver::AWSProvider
42
42
 
43
43
  converge_by "create new VPC #{new_resource.name} in #{region}" do
44
44
  vpc = new_resource.driver.ec2.vpcs.create(new_resource.cidr_block, options)
45
+ wait_for_state(vpc, [:available])
45
46
  vpc.tags['Name'] = new_resource.name
46
47
  vpc
47
48
  end
@@ -2,6 +2,9 @@ require 'chef/provisioning'
2
2
  require 'chef/provisioning/aws_driver/driver'
3
3
 
4
4
  require "chef/resource/aws_auto_scaling_group"
5
+ require "chef/resource/aws_cache_cluster"
6
+ require "chef/resource/aws_cache_replication_group"
7
+ require "chef/resource/aws_cache_subnet_group"
5
8
  require "chef/resource/aws_dhcp_options"
6
9
  require "chef/resource/aws_ebs_volume"
7
10
  require "chef/resource/aws_eip_address"
@@ -10,6 +13,7 @@ require "chef/resource/aws_instance"
10
13
  require "chef/resource/aws_internet_gateway"
11
14
  require "chef/resource/aws_launch_configuration"
12
15
  require "chef/resource/aws_load_balancer"
16
+ require "chef/resource/aws_network_acl"
13
17
  require "chef/resource/aws_network_interface"
14
18
  require "chef/resource/aws_route_table"
15
19
  require "chef/resource/aws_s3_bucket"
@@ -288,5 +288,30 @@ class AWSProvider < Chef::Provider::LWRPBase
288
288
  end
289
289
  end
290
290
 
291
+ # Wait until aws_object obtains one of expected_state
292
+ #
293
+ # @param aws_object Aws SDK Object to check state on
294
+ # @param expected_state [Symbol,Array<Symbol>] Final state(s) to look for
295
+ # @param acceptable_errors [Exception,Array<Exception>] Acceptable errors that are caught and squelched
296
+ # @param tries [Integer] Number of times to check state
297
+ # @param sleep [Integer] Time to wait between checking states
298
+ #
299
+ def wait_for_state(aws_object, expected_states, acceptable_errors = [], tries=60, sleep=5)
300
+ acceptable_errors = [acceptable_errors].flatten
301
+ expected_states = [expected_states].flatten
302
+ current_state = aws_object.state
303
+
304
+ Retryable.retryable(:tries => tries, :sleep => sleep, :on => StatusTimeoutError) do |retries, exception|
305
+ action_handler.report_progress "waited #{retries*sleep}/#{tries*sleep}s for #{aws_object.id} state to change to #{expected_states.inspect}..."
306
+ begin
307
+ current_state = aws_object.state
308
+ unless expected_states.include?(current_state)
309
+ raise StatusTimeoutError.new(aws_object, current_state, expected_states)
310
+ end
311
+ rescue *acceptable_errors
312
+ end
313
+ end
314
+ end
315
+
291
316
  end
292
317
  end
@@ -25,8 +25,13 @@ class AWSResource < Chef::Provisioning::AWSDriver::SuperLWRP
25
25
  super
26
26
  end
27
27
  end
28
- def action=(value)
29
- action(value)
28
+
29
+ # In Chef < 12.4 we need to define this method. In > 12.4 this method is
30
+ # already defined for us so we don't need to overwrite it.
31
+ unless defined?(:action=)
32
+ def action=(value)
33
+ action(value)
34
+ end
30
35
  end
31
36
 
32
37
  #
@@ -1,4 +1,5 @@
1
1
  require 'chef/mixin/shell_out'
2
+ require 'chef/mixin/deep_merge'
2
3
  require 'chef/provisioning/driver'
3
4
  require 'chef/provisioning/convergence_strategy/install_cached'
4
5
  require 'chef/provisioning/convergence_strategy/install_sh'
@@ -21,7 +22,7 @@ require 'chef/provisioning/aws_driver/credentials'
21
22
  require 'yaml'
22
23
  require 'aws-sdk-v1'
23
24
  require 'retryable'
24
-
25
+ require 'ubuntu_ami'
25
26
 
26
27
  # loads the entire aws-sdk
27
28
  AWS.eager_autoload!
@@ -33,6 +34,7 @@ module AWSDriver
33
34
  class Driver < Chef::Provisioning::Driver
34
35
 
35
36
  include Chef::Mixin::ShellOut
37
+ include Chef::Mixin::DeepMerge
36
38
 
37
39
  attr_reader :aws_config
38
40
 
@@ -69,6 +71,9 @@ module AWSDriver
69
71
  # Load balancer methods
70
72
  def allocate_load_balancer(action_handler, lb_spec, lb_options, machine_specs)
71
73
  lb_options = AWSResource.lookup_options(lb_options || {}, managed_entry_store: lb_spec.managed_entry_store, driver: self)
74
+ # We delete the attributes here because they are not valid in the create call
75
+ # and must be applied afterward
76
+ lb_attributes = lb_options.delete(:attributes)
72
77
 
73
78
  old_elb = nil
74
79
  actual_elb = load_balancer_for(lb_spec)
@@ -304,6 +309,21 @@ module AWSDriver
304
309
  }
305
310
  converge_tags(actual_elb, lb_options[:aws_tags], action_handler, read_tags_block, set_tags_block, delete_tags_block)
306
311
 
312
+ # Update load balancer attributes
313
+ if lb_attributes
314
+ current = elb.client.describe_load_balancer_attributes(load_balancer_name: actual_elb.name)[:load_balancer_attributes]
315
+ # Need to do a deep copy w/ Marshal load/dump to avoid overwriting current
316
+ desired = deep_merge!(lb_attributes, Marshal.load(Marshal.dump(current)))
317
+ if current != desired
318
+ perform_action.call(" updating attributes to #{desired.inspect}") do
319
+ elb.client.modify_load_balancer_attributes(
320
+ load_balancer_name: actual_elb.name,
321
+ load_balancer_attributes: desired.to_hash
322
+ )
323
+ end
324
+ end
325
+ end
326
+
307
327
  # Update instance list, but only if there are machines specified
308
328
  if machine_specs
309
329
  actual_instance_ids = actual_elb.instances.map { |i| i.instance_id }
@@ -522,6 +542,10 @@ EOD
522
542
  @elb ||= AWS::ELB.new(config: aws_config)
523
543
  end
524
544
 
545
+ def elasticache
546
+ @elasticache ||= AWS::ElastiCache::Client.new(config: aws_config)
547
+ end
548
+
525
549
  def iam
526
550
  @iam ||= AWS::IAM.new(config: aws_config)
527
551
  end
@@ -675,31 +699,42 @@ EOD
675
699
  end
676
700
  end
677
701
 
678
- def default_ami_for_region(region)
679
- Chef::Log.debug("Choosing default AMI for region '#{region}'")
702
+ def default_ami_arch
703
+ 'amd64'
704
+ end
680
705
 
681
- case region
682
- when 'ap-northeast-1'
683
- 'ami-6cbca76d'
684
- when 'ap-southeast-1'
685
- 'ami-04c6ec56'
686
- when 'ap-southeast-2'
687
- 'ami-c9eb9ff3'
688
- when 'eu-west-1'
689
- 'ami-5f9e1028'
690
- when 'eu-central-1'
691
- 'ami-56c2f14b'
692
- when 'sa-east-1'
693
- 'ami-81f14e9c'
694
- when 'us-east-1'
695
- 'ami-12793a7a'
696
- when 'us-west-1'
697
- 'ami-6ebca42b'
698
- when 'us-west-2'
699
- 'ami-b9471c89'
700
- else
701
- raise 'Unsupported region!'
706
+ def default_ami_release
707
+ 'vivid'
708
+ end
709
+
710
+ def default_ami_root_store
711
+ 'ebs'
712
+ end
713
+
714
+ def default_ami_virtualization_type
715
+ 'hvm'
716
+ end
717
+
718
+ def default_ami_for_criteria(region, arch, release, root_store, virtualization_type)
719
+ ami = Ubuntu.release(release).amis.find do |ami|
720
+ ami.arch == arch &&
721
+ ami.root_store == root_store &&
722
+ ami.region == region &&
723
+ ami.virtualization_type == virtualization_type
702
724
  end
725
+
726
+ ami.name || fail("Default AMI not found")
727
+ end
728
+
729
+ def default_ami_for_region(region, criteria = {})
730
+ Chef::Log.debug("Choosing default AMI for region '#{region}'")
731
+
732
+ arch = criteria['arch'] || default_ami_arch
733
+ release = criteria['release'] || default_ami_release
734
+ root_store = criteria['root_store'] || default_ami_root_store
735
+ virtualization_type = criteria['virtualization_type'] || default_ami_virtualization_type
736
+
737
+ default_ami_for_criteria(region, arch, release, root_store, virtualization_type)
703
738
  end
704
739
 
705
740
  def create_winrm_transport(machine_spec, machine_options, instance)
@@ -1,7 +1,7 @@
1
1
  class Chef
2
2
  module Provisioning
3
3
  module AWSDriver
4
- VERSION = '1.2.1'
4
+ VERSION = '1.3.0'
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,37 @@
1
+ require 'chef/provisioning/aws_driver/aws_resource'
2
+ require 'chef/resource/aws_security_group'
3
+
4
+ class Chef::Resource::AwsCacheCluster < Chef::Provisioning::AWSDriver::AWSResource
5
+ # Note: There isn't actually an SDK class for Elasticache.
6
+ aws_sdk_type AWS::ElastiCache
7
+
8
+ # See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/ElastiCache/Client/V20140930.html#create_cache_cluster-instance_method
9
+ # for information on possible values for each attribute. Values are passed
10
+ # straight through to AWS, with the exception of security_groups, which
11
+ # may contain a reference to a Chef aws_security_group resource.
12
+ attribute :cluster_name, kind_of: String, name_attribute: true
13
+ attribute :az_mode, kind_of: String
14
+ attribute :preferred_availability_zone, kind_of: String
15
+ attribute :preferred_availability_zones,
16
+ kind_of: [ String, Array ],
17
+ coerce: proc { |v| [v].flatten }
18
+ attribute :number_nodes, kind_of: Integer, default: 1
19
+ attribute :node_type, kind_of: String, required: true
20
+ attribute :engine, kind_of: String, required: true
21
+ attribute :engine_version, kind_of: String, required: true
22
+ attribute :subnet_group_name, kind_of: String
23
+ attribute :security_groups,
24
+ kind_of: [ String, Array, AwsSecurityGroup, AWS::EC2::SecurityGroup ],
25
+ required: true,
26
+ coerce: proc { |v| [v].flatten }
27
+
28
+ def aws_object
29
+ begin
30
+ driver.elasticache
31
+ .describe_cache_clusters(cache_cluster_id: cluster_name)
32
+ .data[:cache_clusters].first
33
+ rescue AWS::ElastiCache::Errors::CacheClusterNotFound
34
+ nil
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require 'chef/provisioning/aws_driver/aws_resource'
2
+ require 'chef/resource/aws_security_group'
3
+
4
+ class Chef::Resource::AwsCacheReplicationGroup < Chef::Provisioning::AWSDriver::AWSResource
5
+ # Note: There isn't actually an SDK class for Elasticache.
6
+ aws_sdk_type AWS::ElastiCache
7
+
8
+ # See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/ElastiCache/Client/V20140930.html#create_replication_group-instance_method
9
+ # for information on possible values for each attribute. Values are passed
10
+ # straight through to AWS, with the exception of security_groups, which
11
+ # may contain a reference to a Chef aws_security_group resource.
12
+ attribute :group_name, kind_of: String, name_attribute: true
13
+ attribute :description, kind_of: String, required: true
14
+ attribute :automatic_failover, kind_of: [TrueClass, FalseClass], default: false
15
+ attribute :number_cache_clusters, kind_of: Integer, default: 2
16
+ attribute :node_type, kind_of: String, required: true
17
+ attribute :engine, kind_of: String, required: true
18
+ attribute :engine_version, kind_of: String, required: true
19
+ attribute :subnet_group_name, kind_of: String
20
+ attribute :security_groups,
21
+ kind_of: [ String, Array, AwsSecurityGroup, AWS::EC2::SecurityGroup ],
22
+ required: true,
23
+ coerce: proc { |v| [v].flatten }
24
+ attribute :preferred_availability_zones,
25
+ kind_of: [ String, Array ],
26
+ coerce: proc { |v| [v].flatten }
27
+
28
+ def aws_object
29
+ begin
30
+ driver.elasticache
31
+ .describe_replication_groups(replication_group_id: group_name)
32
+ .data[:replication_groups].first
33
+ rescue AWS::ElastiCache::Errors::ReplicationGroupNotFoundFault
34
+ nil
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ require 'chef/provisioning/aws_driver/aws_resource'
2
+ require 'chef/resource/aws_subnet'
3
+
4
+ class Chef::Resource::AwsCacheSubnetGroup < Chef::Provisioning::AWSDriver::AWSResource
5
+ # Note: There isn't actually an SDK class for Elasticache.
6
+ aws_sdk_type AWS::ElastiCache, id: :group_name
7
+
8
+ # See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/ElastiCache/Client/V20140930.html#create_cache_subnet_group-instance_method
9
+ # for information on possible values for each attribute. Values are passed
10
+ # straight through to AWS, with the exception of subnets, which
11
+ # may contain a reference to a Chef aws_subnet resource.
12
+ attribute :group_name, kind_of: String, name_attribute: true
13
+ attribute :description, kind_of: String, required: true
14
+ attribute :subnets,
15
+ kind_of: [ String, Array, AwsSubnet, AWS::EC2::Subnet ],
16
+ required: true,
17
+ coerce: proc { |v| [v].flatten }
18
+
19
+ def aws_object
20
+ begin
21
+ driver.elasticache
22
+ .describe_cache_subnet_groups(cache_subnet_group_name: group_name)
23
+ .data[:cache_subnet_groups].first
24
+ rescue AWS::ElastiCache::Errors::CacheSubnetGroupNotFoundFault
25
+ nil
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,61 @@
1
+ require 'chef/provisioning/aws_driver/aws_resource_with_entry'
2
+ require 'chef/resource/aws_vpc'
3
+ require 'chef/resource/aws_subnet'
4
+
5
+ class Chef::Resource::AwsNetworkAcl < Chef::Provisioning::AWSDriver::AWSResourceWithEntry
6
+ aws_sdk_type AWS::EC2::NetworkACL
7
+
8
+ #
9
+ # The name of this network acl.
10
+ #
11
+ attribute :name, kind_of: String, name_attribute: true
12
+
13
+ #
14
+ # The VPC of this network acl.
15
+ #
16
+ # May be one of:
17
+ # - The name of an `aws_vpc` Chef resource.
18
+ # - An actual `aws_vpc` resource.
19
+ # - An AWS `VPC` object.
20
+ #
21
+ attribute :vpc, kind_of: [ String, AwsVpc, AWS::EC2::VPC ]
22
+
23
+ #
24
+ # Accepts rules in the format:
25
+ # [
26
+ # { rule_number: 100, action: <:deny|:allow>, protocol: -1, cidr_block: '0.0.0.0/0', port_range: 80..80 }
27
+ # ]
28
+ #
29
+ # `cidr_block` will be a source if it is an inbound rule, or a destination if it is an outbound rule
30
+ #
31
+ # If `inbound_rules` or `outbound_rules` is `nil`, respective current rules will not be changed.
32
+ # However, if either is set to `[]` all respective current rules will be removed.
33
+ #
34
+ attribute :inbound_rules,
35
+ kind_of: [ Array, Hash ],
36
+ coerce: proc { |v| [v].flatten }
37
+
38
+ attribute :outbound_rules,
39
+ kind_of: [ Array, Hash ],
40
+ coerce: proc { |v| [v].flatten }
41
+
42
+ attribute :network_acl_id,
43
+ kind_of: String,
44
+ aws_id_attribute: true,
45
+ lazy_default: proc {
46
+ name =~ /^acl-[a-f0-9]{8}$/ ? name : nil
47
+ }
48
+
49
+ def aws_object
50
+ driver, id = get_driver_and_id
51
+ result = driver.ec2.network_acls[id] if id
52
+ begin
53
+ # network_acls don't have an `exists?` method so have to query an attribute
54
+ result.vpc_id
55
+ result
56
+ rescue AWS::EC2::Errors::InvalidNetworkAclID::NotFound
57
+ nil
58
+ end
59
+ end
60
+
61
+ end
@@ -17,6 +17,7 @@ class Chef::Resource::AwsSubnet < Chef::Provisioning::AWSDriver::AWSResourceWith
17
17
  aws_sdk_type AWS::EC2::Subnet
18
18
 
19
19
  require 'chef/resource/aws_vpc'
20
+ require 'chef/resource/aws_network_acl'
20
21
  require 'chef/resource/aws_route_table'
21
22
 
22
23
  #
@@ -75,6 +76,14 @@ class Chef::Resource::AwsSubnet < Chef::Provisioning::AWSDriver::AWSResourceWith
75
76
  #
76
77
  attribute :route_table#, kind_of: [ String, AwsRouteTable, AWS::EC2::RouteTable ], equal_to: [ :default_to_main ]
77
78
 
79
+ #
80
+ # The Network ACL to associate with this subnet. Subnets may only
81
+ # be associated with a single Network ACL.
82
+ #
83
+ # TODO: See if it's possible to disassociate a Network ACL.
84
+ #
85
+ attribute :network_acl, kind_of: [ String, AwsNetworkAcl, AWS::EC2::NetworkACL ]
86
+
78
87
  attribute :subnet_id, kind_of: String, aws_id_attribute: true, lazy_default: proc {
79
88
  name =~ /^subnet-[a-f0-9]{8}$/ ? name : nil
80
89
  }
data/spec/aws_support.rb CHANGED
@@ -14,6 +14,7 @@ module AWSSupport
14
14
  require 'aws_support/matchers/update_an_aws_object'
15
15
  require 'aws_support/matchers/destroy_an_aws_object'
16
16
  require 'aws_support/matchers/have_aws_object_tags'
17
+ require 'aws_support/matchers/match_an_aws_object'
17
18
  require 'aws_support/delayed_stream'
18
19
  require 'chef/provisioning/aws_driver/resources'
19
20
  require 'aws_support/aws_resource_run_wrapper'
@@ -196,7 +197,9 @@ module AWSSupport
196
197
  define_method("destroy_an_#{resource_name}") do |name, expected_values={}|
197
198
  AWSSupport::Matchers::DestroyAnAWSObject.new(self, resource_class, name)
198
199
  end
199
-
200
+ define_method("match_an_#{resource_name}") do |name, expected_values={}|
201
+ AWSSupport::Matchers::MatchAnAWSObject.new(self, resource_class, name, expected_values)
202
+ end
200
203
  end
201
204
 
202
205
  def chef_config
@@ -0,0 +1,58 @@
1
+ require 'rspec/matchers'
2
+ require 'chef/provisioning'
3
+ require 'aws_support/deep_matcher'
4
+
5
+ module AWSSupport
6
+ module Matchers
7
+
8
+ # This matcher doesn't try to validate that an example was created/updated/destroyed
9
+ # it just checks that the object exists and posses the attributes you specify
10
+ # It also doesn't clean up any aws objects so only use if the resource is defined outside
11
+ # of an example block
12
+ class MatchAnAWSObject
13
+ include RSpec::Matchers::Composable
14
+ include AWSSupport::DeepMatcher
15
+
16
+ def initialize(example, resource_class, name, expected_values)
17
+ @example = example
18
+ @resource_class = resource_class
19
+ @name = name
20
+ @expected_values = expected_values
21
+ end
22
+
23
+ attr_reader :example
24
+ attr_reader :resource_class
25
+ attr_reader :name
26
+ attr_reader :expected_values
27
+ def resource_name
28
+ @resource_class.resource_name
29
+ end
30
+
31
+ def match_failure_messages(recipe)
32
+ differences = []
33
+
34
+ # Converge
35
+ begin
36
+ recipe.converge
37
+ rescue
38
+ differences += [ "error trying to converge #{resource_name}[#{name}]!\n#{($!.backtrace.map { |line| "- #{line}\n" } + [ recipe.output_for_failure_message ]).join("")}" ]
39
+ end
40
+
41
+ # Check for object existence and properties
42
+ resource = resource_class.new(name, nil)
43
+ resource.driver example.driver
44
+ resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store
45
+ aws_object = resource.aws_object
46
+
47
+ # Check existence
48
+ if aws_object.nil?
49
+ differences << "#{resource_name}[#{name}] AWS object did not exist!"
50
+ else
51
+ differences += match_values_failure_messages(expected_values, aws_object, "#{resource_name}[#{name}]")
52
+ end
53
+
54
+ differences
55
+ end
56
+ end
57
+ end
58
+ end