diff --git a/README.md b/README.md index f5fffe62..245b4fe3 100644 --- a/README.md +++ b/README.md @@ -255,15 +255,22 @@ The default is unset, or `nil`. #### `subnet_filter` -The EC2 [subnet][subnet_docs] to use, specified by tag. - +The EC2 [subnet][subnet_docs] to use, specified by tag(s). The default is unset, or `nil`. An example of usage: ```yaml +# By Tag subnet_filter: tag: 'Name' value: 'example-subnet-name' + +# Multiple Tags +subnet_filter: + - tag: 'Type' + value: 'application' + - tag: 'Environment' + value: 'dev' ``` #### `tags` @@ -272,6 +279,17 @@ The Hash of EC tag name/value pairs which will be applied to the instance. The default is `{ "created-by" => "test-kitchen" }`. +An example of usage: +```yaml +# Single tag +tags: + created-by: 'test-kitchen' + +# Multiple Tags +tags: + created-by: 'test-kitchen' + reason: 'Only used for testing can be deleted' +``` #### `user_data` diff --git a/lib/kitchen/driver/aws/instance_generator.rb b/lib/kitchen/driver/aws/instance_generator.rb index 2b6841a6..48e433e8 100644 --- a/lib/kitchen/driver/aws/instance_generator.rb +++ b/lib/kitchen/driver/aws/instance_generator.rb @@ -47,15 +47,19 @@ def ec2_instance_data # rubocop:disable Metrics/MethodLength, Metrics/AbcSize vpc_id = nil client = ::Aws::EC2::Client.new(region: config[:region]) if config[:subnet_id].nil? && config[:subnet_filter] - subnets = client.describe_subnets( - filters: [ + filters = [config[:subnet_filter]].flatten + + r = { filters: [] } + filters.each do |subnet_filter| + r[:filters] << { - name: "tag:#{config[:subnet_filter][:tag]}", - values: [config[:subnet_filter][:value]], - }, - ] - ).subnets - raise "The subnet tagged '#{config[:subnet_filter][:tag]}:#{config[:subnet_filter][:value]}' does not exist!" unless subnets.any? + name: "tag:#{subnet_filter[:tag]}", + values: [subnet_filter[:value]], + } + end + + subnets = client.describe_subnets(r).subnets + raise "Subnets with tags '#{filters}' not found during security group creation" if subnets.empty? # => Select the least-populated subnet if we have multiple matches subnet = subnets.max_by { |s| s[:available_ip_address_count] } diff --git a/lib/kitchen/driver/ec2.rb b/lib/kitchen/driver/ec2.rb index e2543da9..3e9db3f4 100644 --- a/lib/kitchen/driver/ec2.rb +++ b/lib/kitchen/driver/ec2.rb @@ -265,9 +265,8 @@ def create(state) create_ec2_json(state) if /chef/i.match?(instance.provisioner.name) debug("ec2:create '#{state[:hostname]}'") rescue Exception => e - # Clean up any auto-created security groups or keys on the way out. - delete_security_group(state) - delete_key(state) + # Clean up the instance and any auto-created security groups or keys on the way out. + destroy(state) raise "#{e.message} in the specified region #{config[:region]}. Please check this AMI is available in this region." end @@ -433,15 +432,21 @@ def submit_spots else # => Enable cascading through matching subnets client = ::Aws::EC2::Client.new(region: config[:region]) - subnets = client.describe_subnets( - filters: [ + + filters = [config[:subnet_filter]].flatten + + r = { filters: [] } + filters.each do |subnet_filter| + r[:filters] << { - name: "tag:#{config[:subnet_filter][:tag]}", - values: [config[:subnet_filter][:value]], - }, - ] - ).subnets - raise "A subnet matching '#{config[:subnet_filter][:tag]}:#{config[:subnet_filter][:value]}' does not exist!" unless subnets.any? + name: "tag:#{subnet_filter[:tag]}", + values: [subnet_filter[:value]], + } + end + + subnets = client.describe_subnets(r).subnets + + raise "Subnets with tags '#{filters}' not found!" if subnets.empty? configs = subnets.map do |subnet| new_config = config.clone @@ -751,8 +756,19 @@ def create_security_group(state) subnets.first.vpc_id elsif config[:subnet_filter] - subnets = ec2.client.describe_subnets(filters: [{ name: "tag:#{config[:subnet_filter][:tag]}", values: [config[:subnet_filter][:value]] }]).subnets - raise "Subnets with tag '#{config[:subnet_filter][:tag]}=#{config[:subnet_filter][:value]}' not found during security group creation" if subnets.empty? + filters = [config[:subnet_filter]].flatten + + r = { filters: [] } + filters.each do |subnet_filter| + r[:filters] << { + name: "tag:#{subnet_filter[:tag]}", + values: [subnet_filter[:value]], + } + end + + subnets = ec2.client.describe_subnets(r).subnets + + raise "Subnets with tags '#{filters}' not found during security group creation" if subnets.empty? subnets.first.vpc_id else diff --git a/spec/kitchen/driver/ec2_spec.rb b/spec/kitchen/driver/ec2_spec.rb index fe9ab6fc..1bf6b3af 100644 --- a/spec/kitchen/driver/ec2_spec.rb +++ b/spec/kitchen/driver/ec2_spec.rb @@ -546,6 +546,30 @@ include_examples "common create" end + context "with multiple subnet filters configured" do + before do + config.delete(:subnet_id) + config[:subnet_filter] = [{ + tag: "foo", + value: "bar", + }, + { + tag: "hello", + value: "world", + }] + expect(actual_client).to receive(:describe_subnets).with(filters: [{ name: "tag:foo", values: ["bar"] }, { name: "tag:hello", values: ["world"] }]).and_return(double(subnets: [double(vpc_id: "vpc-1")])) + expect(actual_client).to receive(:create_security_group).with(group_name: /kitchen-/, description: /Test Kitchen for/, vpc_id: "vpc-1").and_return(double(group_id: "sg-9876")) + expect(actual_client).to receive(:authorize_security_group_ingress).with(group_id: "sg-9876", ip_permissions: [ + { ip_protocol: "tcp", from_port: 22, to_port: 22, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] }, + { ip_protocol: "tcp", from_port: 3389, to_port: 3389, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] }, + { ip_protocol: "tcp", from_port: 5985, to_port: 5985, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] }, + { ip_protocol: "tcp", from_port: 5986, to_port: 5986, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] }, + ]) + end + + include_examples "common create" + end + context "with an ip address configured as a string" do before do config[:security_group_cidr_ip] = "1.2.3.4/32"