diff --git a/aws/resource_aws_network_interface_sg_attachment.go b/aws/resource_aws_network_interface_sg_attachment.go index bc0c33b4b16..1ac6476975e 100644 --- a/aws/resource_aws_network_interface_sg_attachment.go +++ b/aws/resource_aws_network_interface_sg_attachment.go @@ -81,6 +81,13 @@ func resourceAwsNetworkInterfaceSGAttachmentRead(d *schema.ResourceData, meta in conn := meta.(*AWSClient).ec2conn iface, err := fetchNetworkInterface(conn, interfaceID) + + if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") { + log.Printf("[WARN] EC2 Network Interface (%s) not found, removing from state", interfaceID) + d.SetId("") + return nil + } + if err != nil { return err } @@ -108,15 +115,16 @@ func resourceAwsNetworkInterfaceSGAttachmentDelete(d *schema.ResourceData, meta conn := meta.(*AWSClient).ec2conn iface, err := fetchNetworkInterface(conn, interfaceID) - if err != nil { - return err + + if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") { + return nil } - if err := delSGFromENI(conn, sgID, iface); err != nil { + if err != nil { return err } - return nil + return delSGFromENI(conn, sgID, iface) } // fetchNetworkInterface is a utility function used by Read and Delete to fetch @@ -154,6 +162,11 @@ func delSGFromENI(conn *ec2.EC2, sgID string, iface *ec2.NetworkInterface) error } _, err := conn.ModifyNetworkInterfaceAttribute(params) + + if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") { + return nil + } + return err } diff --git a/aws/resource_aws_network_interface_sg_attachment_test.go b/aws/resource_aws_network_interface_sg_attachment_test.go index ab674da441c..afc7c79cd64 100644 --- a/aws/resource_aws_network_interface_sg_attachment_test.go +++ b/aws/resource_aws_network_interface_sg_attachment_test.go @@ -2,76 +2,269 @@ package aws import ( "fmt" - "strconv" "testing" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) -func TestAccAWSNetworkInterfaceSGAttachment(t *testing.T) { - cases := []struct { - Name string - ResourceAttr string - Config func(bool) string - }{ - { - Name: "instance primary interface", - ResourceAttr: "primary_network_interface_id", - Config: testAccAwsNetworkInterfaceSGAttachmentConfigViaInstance, +func TestAccAWSNetworkInterfaceSGAttachment_basic(t *testing.T) { + var networkInterface ec2.NetworkInterface + networkInterfaceResourceName := "aws_network_interface.test" + securityGroupResourceName := "aws_security_group.test" + resourceName := "aws_network_interface_sg_attachment.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkInterfaceSGAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsNetworkInterfaceSGAttachmentConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName, &networkInterface), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", networkInterfaceResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", securityGroupResourceName, "id"), + ), + }, + }, + }) +} + +func TestAccAWSNetworkInterfaceSGAttachment_disappears(t *testing.T) { + var networkInterface ec2.NetworkInterface + resourceName := "aws_network_interface_sg_attachment.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkInterfaceSGAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsNetworkInterfaceSGAttachmentConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName, &networkInterface), + testAccCheckAwsNetworkInterfaceSGAttachmentDisappears(resourceName, &networkInterface), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccAwsNetworkInterfaceSGAttachmentConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName, &networkInterface), + testAccCheckAWSENIDisappears(&networkInterface), + ), + ExpectNonEmptyPlan: true, + }, }, - { - Name: "externally supplied instance through data source", - ResourceAttr: "network_interface_id", - Config: testAccAwsNetworkInterfaceSGAttachmentConfigViaDataSource, + }) +} + +func TestAccAWSNetworkInterfaceSGAttachment_Instance(t *testing.T) { + var networkInterface ec2.NetworkInterface + instanceResourceName := "aws_instance.test" + securityGroupResourceName := "aws_security_group.test" + resourceName := "aws_network_interface_sg_attachment.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkInterfaceSGAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsNetworkInterfaceSGAttachmentConfigViaInstance(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName, &networkInterface), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", instanceResourceName, "primary_network_interface_id"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", securityGroupResourceName, "id"), + ), + }, }, - } - for _, tc := range cases { - t.Run(tc.Name, func(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: tc.Config(true), - Check: checkSecurityGroupAttached(tc.ResourceAttr, true), - }, - { - Config: tc.Config(false), - Check: checkSecurityGroupAttached(tc.ResourceAttr, false), - }, - }, - }) - }) - } + }) +} + +func TestAccAWSNetworkInterfaceSGAttachment_DataSource(t *testing.T) { + var networkInterface ec2.NetworkInterface + instanceDataSourceName := "data.aws_instance.test" + securityGroupResourceName := "aws_security_group.test" + resourceName := "aws_network_interface_sg_attachment.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkInterfaceSGAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsNetworkInterfaceSGAttachmentConfigViaDataSource(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName, &networkInterface), + resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", instanceDataSourceName, "network_interface_id"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_id", securityGroupResourceName, "id"), + ), + }, + }, + }) +} + +func TestAccAWSNetworkInterfaceSGAttachment_Multiple(t *testing.T) { + var networkInterface ec2.NetworkInterface + networkInterfaceResourceName := "aws_network_interface.test" + securityGroupResourceName1 := "aws_security_group.test.0" + securityGroupResourceName2 := "aws_security_group.test.1" + securityGroupResourceName3 := "aws_security_group.test.2" + securityGroupResourceName4 := "aws_security_group.test.3" + resourceName1 := "aws_network_interface_sg_attachment.test.0" + resourceName2 := "aws_network_interface_sg_attachment.test.1" + resourceName3 := "aws_network_interface_sg_attachment.test.2" + resourceName4 := "aws_network_interface_sg_attachment.test.3" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNetworkInterfaceSGAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsNetworkInterfaceSGAttachmentConfigMultiple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName1, &networkInterface), + resource.TestCheckResourceAttrPair(resourceName1, "network_interface_id", networkInterfaceResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName1, "security_group_id", securityGroupResourceName1, "id"), + testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName2, &networkInterface), + resource.TestCheckResourceAttrPair(resourceName2, "network_interface_id", networkInterfaceResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName2, "security_group_id", securityGroupResourceName2, "id"), + testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName3, &networkInterface), + resource.TestCheckResourceAttrPair(resourceName3, "network_interface_id", networkInterfaceResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName3, "security_group_id", securityGroupResourceName3, "id"), + testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName4, &networkInterface), + resource.TestCheckResourceAttrPair(resourceName4, "network_interface_id", networkInterfaceResourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName4, "security_group_id", securityGroupResourceName4, "id"), + ), + }, + }, + }) } -func checkSecurityGroupAttached(attr string, expected bool) resource.TestCheckFunc { +func testAccCheckAWSNetworkInterfaceSGAttachmentExists(resourceName string, networkInterface *ec2.NetworkInterface) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ec2conn + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No resource ID set: %s", resourceName) + } - interfaceID := s.Modules[0].Resources["aws_instance.instance"].Primary.Attributes[attr] - sgID := s.Modules[0].Resources["aws_security_group.sg"].Primary.ID + conn := testAccProvider.Meta().(*AWSClient).ec2conn + networkInterfaceID := rs.Primary.Attributes["network_interface_id"] + securityGroupID := rs.Primary.Attributes["security_group_id"] - iface, err := fetchNetworkInterface(conn, interfaceID) + iface, err := fetchNetworkInterface(conn, networkInterfaceID) if err != nil { - return err + return fmt.Errorf("ENI (%s) not found: %s", networkInterfaceID, err) } - actual := sgExistsInENI(sgID, iface) - if expected != actual { - return fmt.Errorf("expected existence of security group in ENI to be %t, got %t", expected, actual) + + if !sgExistsInENI(securityGroupID, iface) { + return fmt.Errorf("Security Group ID (%s) not attached to ENI (%s)", securityGroupID, networkInterfaceID) } + + *networkInterface = *iface + return nil } } -func testAccAwsNetworkInterfaceSGAttachmentConfigViaInstance(attachmentEnabled bool) string { +func testAccCheckAWSNetworkInterfaceSGAttachmentDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_network_interface_sg_attachment" { + continue + } + + networkInterfaceID := rs.Primary.Attributes["network_interface_id"] + securityGroupID := rs.Primary.Attributes["security_group_id"] + + networkInterface, err := fetchNetworkInterface(conn, networkInterfaceID) + + if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") { + continue + } + + if err != nil { + return fmt.Errorf("ENI (%s) not found: %s", networkInterfaceID, err) + } + + if sgExistsInENI(securityGroupID, networkInterface) { + return fmt.Errorf("Security Group ID (%s) still attached to ENI (%s)", securityGroupID, networkInterfaceID) + } + } + + return nil +} + +func testAccCheckAwsNetworkInterfaceSGAttachmentDisappears(resourceName string, networkInterface *ec2.NetworkInterface) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + securityGroupID := rs.Primary.Attributes["security_group_id"] + + return delSGFromENI(conn, securityGroupID, networkInterface) + } +} + +func testAccAwsNetworkInterfaceSGAttachmentConfig(rName string) string { return fmt.Sprintf(` -variable "sg_attachment_enabled" { - type = "string" - default = "%t" +resource "aws_vpc" "test" { + cidr_block = "172.16.0.0/16" + + tags { + Name = %q + } +} + +resource "aws_subnet" "test" { + cidr_block = "172.16.10.0/24" + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = %q + } +} + +resource "aws_security_group" "test" { + name = %q + vpc_id = "${aws_vpc.test.id}" +} + +resource "aws_network_interface" "test" { + subnet_id = "${aws_subnet.test.id}" + + tags { + Name = %q + } +} + +resource "aws_network_interface_sg_attachment" "test" { + network_interface_id = "${aws_network_interface.test.id}" + security_group_id = "${aws_security_group.test.id}" +} +`, rName, rName, rName, rName) } +func testAccAwsNetworkInterfaceSGAttachmentConfigViaInstance(rName string) string { + return fmt.Sprintf(` data "aws_ami" "ami" { most_recent = true @@ -83,36 +276,28 @@ data "aws_ami" "ami" { owners = ["amazon"] } -resource "aws_instance" "instance" { +resource "aws_instance" "test" { instance_type = "t2.micro" ami = "${data.aws_ami.ami.id}" tags = { - "type" = "terraform-test-instance" + Name = %q } } -resource "aws_security_group" "sg" { - tags = { - "type" = "terraform-test-security-group" - } +resource "aws_security_group" "test" { + name = %q } -resource "aws_network_interface_sg_attachment" "sg_attachment" { - count = "${var.sg_attachment_enabled == "true" ? 1 : 0}" - security_group_id = "${aws_security_group.sg.id}" - network_interface_id = "${aws_instance.instance.primary_network_interface_id}" +resource "aws_network_interface_sg_attachment" "test" { + network_interface_id = "${aws_instance.test.primary_network_interface_id}" + security_group_id = "${aws_security_group.test.id}" } -`, attachmentEnabled) +`, rName, rName) } -func testAccAwsNetworkInterfaceSGAttachmentConfigViaDataSource(attachmentEnabled bool) string { +func testAccAwsNetworkInterfaceSGAttachmentConfigViaDataSource(rName string) string { return fmt.Sprintf(` -variable "sg_attachment_enabled" { - type = "string" - default = "%t" -} - data "aws_ami" "ami" { most_recent = true @@ -124,105 +309,56 @@ data "aws_ami" "ami" { owners = ["amazon"] } -resource "aws_instance" "instance" { +resource "aws_instance" "test" { instance_type = "t2.micro" ami = "${data.aws_ami.ami.id}" tags = { - "type" = "terraform-test-instance" + Name = %q } } -data "aws_instance" "external_instance" { - instance_id = "${aws_instance.instance.id}" +data "aws_instance" "test" { + instance_id = "${aws_instance.test.id}" } -resource "aws_security_group" "sg" { - tags = { - "type" = "terraform-test-security-group" - } +resource "aws_security_group" "test" { + name = %q } -resource "aws_network_interface_sg_attachment" "sg_attachment" { - count = "${var.sg_attachment_enabled == "true" ? 1 : 0}" - security_group_id = "${aws_security_group.sg.id}" - network_interface_id = "${data.aws_instance.external_instance.network_interface_id}" -} -`, attachmentEnabled) -} - -func TestAccAWSNetworkInterfaceSGAttachmentRaceCheck(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: testAccAwsNetworkInterfaceSGAttachmentRaceCheckConfig(), - Check: checkSecurityGroupAttachmentRace(), - }, - }, - }) +resource "aws_network_interface_sg_attachment" "test" { + security_group_id = "${aws_security_group.test.id}" + network_interface_id = "${data.aws_instance.test.network_interface_id}" } - -// sgRaceCheckCount specifies the amount of security groups to create in the -// race check. This should be the maximum amount of security groups that can be -// attached to an interface at once, minus the default (we don't remove it in -// the config). -const sgRaceCheckCount = 4 - -func checkSecurityGroupAttachmentRace() resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ec2conn - - interfaceID := s.Modules[0].Resources["aws_network_interface.interface"].Primary.ID - for i := 0; i < sgRaceCheckCount; i++ { - sgID := s.Modules[0].Resources["aws_security_group.sg."+strconv.Itoa(i)].Primary.ID - iface, err := fetchNetworkInterface(conn, interfaceID) - if err != nil { - return err - } - if !sgExistsInENI(sgID, iface) { - return fmt.Errorf("security group ID %s was not attached to ENI ID %s", sgID, interfaceID) - } - } - return nil - } +`, rName, rName) } -func testAccAwsNetworkInterfaceSGAttachmentRaceCheckConfig() string { +func testAccAwsNetworkInterfaceSGAttachmentConfigMultiple(rName string) string { return fmt.Sprintf(` -variable "security_group_count" { - type = "string" - default = "%d" -} - data "aws_availability_zones" "available" {} -data "aws_subnet" "subnet" { +data "aws_subnet" "test" { availability_zone = "${data.aws_availability_zones.available.names[0]}" default_for_az = "true" } -resource "aws_network_interface" "interface" { - subnet_id = "${data.aws_subnet.subnet.id}" +resource "aws_network_interface" "test" { + subnet_id = "${data.aws_subnet.test.id}" tags = { - "type" = "terraform-test-network-interface" + Name = %q } } -resource "aws_security_group" "sg" { - count = "${var.security_group_count}" - - tags = { - "type" = "terraform-test-security-group" - } +resource "aws_security_group" "test" { + count = 4 + name = "%s-${count.index}" } -resource "aws_network_interface_sg_attachment" "sg_attachment" { - count = "${var.security_group_count}" - security_group_id = "${aws_security_group.sg.*.id[count.index]}" - network_interface_id = "${aws_network_interface.interface.id}" +resource "aws_network_interface_sg_attachment" "test" { + count = 4 + network_interface_id = "${aws_network_interface.test.id}" + security_group_id = "${aws_security_group.test.*.id[count.index]}" } -`, sgRaceCheckCount) +`, rName, rName) } diff --git a/aws/resource_aws_network_interface_test.go b/aws/resource_aws_network_interface_test.go index abe1fb08415..3093cf5dbbf 100644 --- a/aws/resource_aws_network_interface_test.go +++ b/aws/resource_aws_network_interface_test.go @@ -60,6 +60,27 @@ func TestAccAWSENI_basic(t *testing.T) { }) } +func TestAccAWSENI_disappears(t *testing.T) { + var networkInterface ec2.NetworkInterface + resourceName := "aws_network_interface.bar" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSENIDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSENIConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSENIExists(resourceName, &networkInterface), + testAccCheckAWSENIDisappears(&networkInterface), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func TestAccAWSENI_updatedDescription(t *testing.T) { var conf ec2.NetworkInterface @@ -299,6 +320,19 @@ func testAccCheckAWSENIDestroy(s *terraform.State) error { return nil } +func testAccCheckAWSENIDisappears(networkInterface *ec2.NetworkInterface) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + input := &ec2.DeleteNetworkInterfaceInput{ + NetworkInterfaceId: networkInterface.NetworkInterfaceId, + } + _, err := conn.DeleteNetworkInterface(input) + + return err + } +} + func testAccCheckAWSENIMakeExternalAttachment(n string, conf *ec2.NetworkInterface) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n]