chef-provisioning-aws 1.2.1 → 1.3.0

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