diff --git a/azurerm/internal/services/network/nat_gateway_public_ip_association_resource.go b/azurerm/internal/services/network/nat_gateway_public_ip_association_resource.go new file mode 100644 index 000000000000..e1a5e229ce27 --- /dev/null +++ b/azurerm/internal/services/network/nat_gateway_public_ip_association_resource.go @@ -0,0 +1,195 @@ +package network + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-03-01/network" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/validate" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmNatGatewayPublicIpAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceArmNatGatewayPublicIpAssociationCreate, + Read: resourceArmNatGatewayPublicIpAssociationRead, + Delete: resourceArmNatGatewayPublicIpAssociationDelete, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.NatGatewayID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "nat_gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NatGatewayID, + }, + + "public_ip_address_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + }, + } +} + +func resourceArmNatGatewayPublicIpAssociationCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.NatGatewayClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + log.Printf("[INFO] preparing arguments for Nat Gateway <-> Public Ip Association creation.") + natGatewayId := d.Get("nat_gateway_id").(string) + publicIpAddressId := d.Get("public_ip_address_id").(string) + parsedNatGatewayId, err := parse.NatGatewayID(natGatewayId) + if err != nil { + return err + } + + locks.ByName(parsedNatGatewayId.Name, natGatewayResourceName) + defer locks.UnlockByName(parsedNatGatewayId.Name, natGatewayResourceName) + + natGateway, err := client.Get(ctx, parsedNatGatewayId.ResourceGroup, parsedNatGatewayId.Name, "") + if err != nil { + if utils.ResponseWasNotFound(natGateway.Response) { + return fmt.Errorf("Nat Gateway %q (Resource Group %q) was not found.", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup) + } + return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err) + } + + publicIpAddresses := make([]network.SubResource, 0) + if natGateway.PublicIPAddresses != nil { + for _, existingPublicIPAddress := range *natGateway.PublicIPAddresses { + if existingPublicIPAddress.ID != nil { + if *existingPublicIPAddress.ID == publicIpAddressId { + return tf.ImportAsExistsError("azurerm_nat_gateway_public_ip_association", *natGateway.ID) + } + + publicIpAddresses = append(publicIpAddresses, existingPublicIPAddress) + } + } + } + + publicIpAddresses = append(publicIpAddresses, network.SubResource{ + ID: utils.String(publicIpAddressId), + }) + natGateway.PublicIPAddresses = &publicIpAddresses + + future, err := client.CreateOrUpdate(ctx, parsedNatGatewayId.ResourceGroup, parsedNatGatewayId.Name, natGateway) + if err != nil { + return fmt.Errorf("failed to update Public IP Association for Nat Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("failed to wait for completion of Public IP Association for Nat Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err) + } + + resp, err := client.Get(ctx, parsedNatGatewayId.ResourceGroup, parsedNatGatewayId.Name, "") + if err != nil { + return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err) + } + d.SetId(*resp.ID) + + return resourceArmNatGatewayPublicIpAssociationRead(d, meta) +} + +func resourceArmNatGatewayPublicIpAssociationRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.NatGatewayClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.NatGatewayID(d.Id()) + if err != nil { + return err + } + + natGateway, err := client.Get(ctx, id.ResourceGroup, id.Name, "") + if err != nil { + if utils.ResponseWasNotFound(natGateway.Response) { + log.Printf("[DEBUG] Nat Gateway %q (Resource Group %q) could not be found - removing from state!", id.Name, id.ResourceGroup) + d.SetId("") + return nil + } + return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + if natGateway.PublicIPAddresses == nil { + log.Printf("[DEBUG] Nat Gateway %q (Resource Group %q) doesn't have a Public IP - removing from state!", id.Name, id.ResourceGroup) + d.SetId("") + return nil + } + + d.Set("nat_gateway_id", natGateway.ID) + + return nil +} + +func resourceArmNatGatewayPublicIpAssociationDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.NatGatewayClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.NatGatewayID(d.Id()) + if err != nil { + return err + } + + publicIpAddressId := d.Get("public_ip_address_id").(string) + + locks.ByName(id.Name, natGatewayResourceName) + defer locks.UnlockByName(id.Name, natGatewayResourceName) + + natGateway, err := client.Get(ctx, id.ResourceGroup, id.Name, "") + if err != nil { + if utils.ResponseWasNotFound(natGateway.Response) { + return fmt.Errorf("Nat Gateway %q (Resource Group %q) was not found.", id.Name, id.ResourceGroup) + } + + return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + publicIpAddresses := make([]network.SubResource, 0) + if publicIPAddresses := natGateway.PublicIPAddresses; publicIPAddresses != nil { + for _, publicIPAddress := range *publicIPAddresses { + if publicIPAddress.ID == nil { + continue + } + + if *publicIPAddress.ID != publicIpAddressId { + publicIpAddresses = append(publicIpAddresses, publicIPAddress) + } + } + } + natGateway.PublicIPAddresses = &publicIpAddresses + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, natGateway) + if err != nil { + return fmt.Errorf("failed to remove Public Ip Association for Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("failed to wait for removal of Public Ip Association for Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + return nil +} diff --git a/azurerm/internal/services/network/nat_gateway_resource.go b/azurerm/internal/services/network/nat_gateway_resource.go index 46b7f7a0dd27..cc9783747c88 100644 --- a/azurerm/internal/services/network/nat_gateway_resource.go +++ b/azurerm/internal/services/network/nat_gateway_resource.go @@ -60,10 +60,12 @@ func resourceArmNatGateway() *schema.Resource { "public_ip_address_ids": { Type: schema.TypeSet, Optional: true, + Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: azure.ValidateResourceID, }, + Deprecated: "Deprecated in favor of `azurerm_nat_gateway_public_ip_association`. The dependency relation between `azurerm_nat_gateway` and `azurerm_public_ip` isn't detected by implicit dependency. So `azurerm_nat_gateway_public_ip_association` is added to resolve this issue.", }, "public_ip_prefix_ids": { diff --git a/azurerm/internal/services/network/parse/nat_gateway.go b/azurerm/internal/services/network/parse/nat_gateway.go new file mode 100644 index 000000000000..c730d001f34d --- /dev/null +++ b/azurerm/internal/services/network/parse/nat_gateway.go @@ -0,0 +1,31 @@ +package parse + +import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type NatGatewayId struct { + Name string + ResourceGroup string +} + +func NatGatewayID(input string) (*NatGatewayId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + natGateway := NatGatewayId{ + ResourceGroup: id.ResourceGroup, + } + + if natGateway.Name, err = id.PopSegment("natGateways"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &natGateway, nil +} diff --git a/azurerm/internal/services/network/parse/nat_gateway_test.go b/azurerm/internal/services/network/parse/nat_gateway_test.go new file mode 100644 index 000000000000..d93cb021cf27 --- /dev/null +++ b/azurerm/internal/services/network/parse/nat_gateway_test.go @@ -0,0 +1,70 @@ +package parse + +import ( + "testing" +) + +func TestNatGatewayID(t *testing.T) { + testData := []struct { + Name string + Input string + Error bool + Expect *NatGatewayId + }{ + { + Name: "Empty", + Input: "", + Error: true, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Error: true, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups", + Error: true, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1", + Error: true, + }, + { + Name: "Missing Nat Gateway Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/natGateways", + Error: true, + }, + { + Name: "Nat Gateway ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/natGateways/gateway1", + Error: false, + Expect: &NatGatewayId{ + Name: "gateway1", + ResourceGroup: "group1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := NatGatewayID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.Name != v.Expect.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expect.Name, actual.Name) + } + + if actual.ResourceGroup != v.Expect.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expect.ResourceGroup, actual.ResourceGroup) + } + } +} diff --git a/azurerm/internal/services/network/registration.go b/azurerm/internal/services/network/registration.go index 1bd4e2ba5bc4..c6bdf4c1e225 100644 --- a/azurerm/internal/services/network/registration.go +++ b/azurerm/internal/services/network/registration.go @@ -86,6 +86,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_private_endpoint": resourceArmPrivateEndpoint(), "azurerm_private_link_service": resourceArmPrivateLinkService(), "azurerm_public_ip": resourceArmPublicIp(), + "azurerm_nat_gateway_public_ip_association": resourceArmNatGatewayPublicIpAssociation(), "azurerm_public_ip_prefix": resourceArmPublicIpPrefix(), "azurerm_network_security_group": resourceArmNetworkSecurityGroup(), "azurerm_network_security_rule": resourceArmNetworkSecurityRule(), diff --git a/azurerm/internal/services/network/tests/nat_gateway_public_ip_association_resource_test.go b/azurerm/internal/services/network/tests/nat_gateway_public_ip_association_resource_test.go new file mode 100644 index 000000000000..39aa699e72ce --- /dev/null +++ b/azurerm/internal/services/network/tests/nat_gateway_public_ip_association_resource_test.go @@ -0,0 +1,293 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-03-01/network" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" +) + +func TestAccAzureRMNatGatewayPublicIpAssociation_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_nat_gateway_public_ip_association", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + // intentional as this is a Virtual Resource + CheckDestroy: testCheckAzureRMNatGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNatGatewayPublicIpAssociation_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNatGatewayPublicIpAssociationExists(data.ResourceName), + ), + }, + // `public_ip_address_id` cannot be retrieved in read function while importing. + data.ImportStep("public_ip_address_id"), + }, + }) +} + +func TestAccAzureRMNatGatewayPublicIpAssociation_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_nat_gateway_public_ip_association", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + // intentional as this is a Virtual Resource + CheckDestroy: testCheckAzureRMNatGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNatGatewayPublicIpAssociation_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNatGatewayPublicIpAssociationExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMNatGatewayPublicIpAssociation_requiresImport), + }, + }) +} + +func TestAccAzureRMNatGatewayPublicIpAssociation_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_nat_gateway_public_ip_association", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + // intentional as this is a Virtual Resource + CheckDestroy: testCheckAzureRMNatGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNatGatewayPublicIpAssociation_complete(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNatGatewayPublicIpAssociationExists(data.ResourceName), + ), + }, + data.ImportStep("public_ip_address_id"), + }, + }) +} + +func TestAccAzureRMNatGatewayPublicIpAssociation_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_nat_gateway_public_ip_association", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + // intentional as this is a Virtual Resource + CheckDestroy: testCheckAzureRMNatGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNatGatewayPublicIpAssociation_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNatGatewayPublicIpAssociationExists(data.ResourceName), + ), + }, + data.ImportStep("public_ip_address_id"), + { + Config: testAccAzureRMNatGatewayPublicIpAssociation_update(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNatGatewayPublicIpAssociationExists(data.ResourceName), + ), + }, + data.ImportStep("public_ip_address_id"), + }, + }) +} + +func TestAccAzureRMNatGatewayPublicIpAssociation_deleted(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_nat_gateway_public_ip_association", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + // intentional as this is a Virtual Resource + CheckDestroy: testCheckAzureRMNatGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNatGatewayPublicIpAssociation_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNatGatewayPublicIpAssociationExists(data.ResourceName), + testCheckAzureRMNatGatewayPublicIpAssociationDisappears(data.ResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccAzureRMNatGatewayPublicIpAssociation_requiresImport(data acceptance.TestData) string { + template := testAccAzureRMNatGatewayPublicIpAssociation_basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_nat_gateway_public_ip_association" "import" { + nat_gateway_id = azurerm_nat_gateway_public_ip_association.test.nat_gateway_id + public_ip_address_id = azurerm_nat_gateway_public_ip_association.test.public_ip_address_id +} +`, template) +} + +func testCheckAzureRMNatGatewayPublicIpAssociationExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Network.NatGatewayClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + id, err := parse.NatGatewayID(rs.Primary.ID) + if err != nil { + return err + } + publicIpAddressId := rs.Primary.Attributes["public_ip_address_id"] + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name, "") + if err != nil { + return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + if publicIpAddresses := resp.PublicIPAddresses; publicIpAddresses != nil { + for _, publicIpAddress := range *publicIpAddresses { + if *publicIpAddress.ID == publicIpAddressId { + return nil + } + } + } + + return fmt.Errorf("Association between Nat Gateway %q and Public Ip %q was not found.", id.Name, publicIpAddressId) + } +} + +func testCheckAzureRMNatGatewayPublicIpAssociationDisappears(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Network.NatGatewayClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + id, err := parse.NatGatewayID(rs.Primary.ID) + if err != nil { + return err + } + publicIpAddressId := rs.Primary.Attributes["public_ip_address_id"] + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name, "") + if err != nil { + return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + updatedAddresses := make([]network.SubResource, 0) + if publicIpAddresses := resp.PublicIPAddresses; publicIpAddresses != nil { + for _, publicIpAddress := range *publicIpAddresses { + if *publicIpAddress.ID != publicIpAddressId { + updatedAddresses = append(updatedAddresses, publicIpAddress) + } + } + } + resp.PublicIPAddresses = &updatedAddresses + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, resp) + if err != nil { + return fmt.Errorf("failed to remove Nat Gateway Public Ip Association for Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("failed to wait for removal of Nat Gateway Public Ip Association for Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + return nil + } +} + +func testAccAzureRMNatGatewayPublicIpAssociation_basic(data acceptance.TestData) string { + template := testAccAzureRMNatGatewayPublicIpAssociation_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_nat_gateway_public_ip_association" "test" { + nat_gateway_id = azurerm_nat_gateway.test.id + public_ip_address_id = azurerm_public_ip.test.id +} +`, template) +} + +func testAccAzureRMNatGatewayPublicIpAssociation_complete(data acceptance.TestData) string { + template := testAccAzureRMNatGatewayPublicIpAssociation_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_nat_gateway_public_ip_association" "test" { + nat_gateway_id = azurerm_nat_gateway.test.id + public_ip_address_id = azurerm_public_ip.test.id +} + +resource "azurerm_public_ip" "test2" { + name = "acctest-PIP2-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + allocation_method = "Static" + sku = "Standard" +} + +resource "azurerm_nat_gateway_public_ip_association" "test2" { + nat_gateway_id = azurerm_nat_gateway.test.id + public_ip_address_id = azurerm_public_ip.test2.id +} +`, template, data.RandomInteger) +} + +func testAccAzureRMNatGatewayPublicIpAssociation_update(data acceptance.TestData) string { + template := testAccAzureRMNatGatewayPublicIpAssociation_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_nat_gateway" "test2" { + name = "acctest-NatGateway2-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku_name = "Standard" +} + +resource "azurerm_nat_gateway_public_ip_association" "test" { + nat_gateway_id = azurerm_nat_gateway.test2.id + public_ip_address_id = azurerm_public_ip.test.id +} +`, template, data.RandomInteger) +} + +func testAccAzureRMNatGatewayPublicIpAssociation_template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-ngpi-%d" + location = "%s" +} + +resource "azurerm_public_ip" "test" { + name = "acctest-PIP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + allocation_method = "Static" + sku = "Standard" +} + +resource "azurerm_nat_gateway" "test" { + name = "acctest-NatGateway-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku_name = "Standard" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} diff --git a/azurerm/internal/services/network/validate/nat_gateway.go b/azurerm/internal/services/network/validate/nat_gateway.go new file mode 100644 index 000000000000..7ec9cdd42e6f --- /dev/null +++ b/azurerm/internal/services/network/validate/nat_gateway.go @@ -0,0 +1,22 @@ +package validate + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" +) + +func NatGatewayID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := parse.NatGatewayID(v); err != nil { + errors = append(errors, fmt.Errorf("Can not parse %q as a resource id: %v", k, err)) + return + } + + return warnings, errors +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 25d10b1ed772..972c812fe1e3 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -2016,6 +2016,10 @@ azurerm_nat_gateway +
  • + azurerm_nat_gateway_public_ip_association +
  • +
  • azurerm_network_interface
  • diff --git a/website/docs/d/nat_gateway.html.markdown b/website/docs/d/nat_gateway.html.markdown index 6fa9f69d3b7a..2f43f56a643f 100644 --- a/website/docs/d/nat_gateway.html.markdown +++ b/website/docs/d/nat_gateway.html.markdown @@ -38,6 +38,8 @@ The following attributes are exported: * `zones` - A list of Availability Zones which the NAT Gateway exists in. +~> **NOTE:** The field `public_ip_address_ids` has been deprecated in favor of `azurerm_nat_gateway_public_ip_association`. + ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: diff --git a/website/docs/r/nat_gateway.html.markdown b/website/docs/r/nat_gateway.html.markdown index d114170ea0b5..c72b3f88e7c1 100644 --- a/website/docs/r/nat_gateway.html.markdown +++ b/website/docs/r/nat_gateway.html.markdown @@ -58,7 +58,7 @@ The following arguments are supported: * `idle_timeout_in_minutes` - (Optional) The idle timeout which should be used in minutes. Defaults to `4`. -* `public_ip_address_ids` - (Optional) A list of Public IP Address ID's which should be associated with the NAT Gateway resource. +* `public_ip_address_ids` - (Optional / **Deprecated in favour of `azurerm_nat_gateway_public_ip_association`**) A list of Public IP Address ID's which should be associated with the NAT Gateway resource. * `public_ip_prefix_ids` - (Optional) A list of Public IP Prefix ID's which should be associated with the NAT Gateway resource. diff --git a/website/docs/r/nat_gateway_public_ip_association.html.markdown b/website/docs/r/nat_gateway_public_ip_association.html.markdown new file mode 100644 index 000000000000..238523351371 --- /dev/null +++ b/website/docs/r/nat_gateway_public_ip_association.html.markdown @@ -0,0 +1,75 @@ +--- +subcategory: "Network" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_nat_gateway_public_ip_association" +description: |- + Manages the association between a Nat Gateway and a Public IP. + +--- + +# azurerm_nat_gateway_public_ip_association + +Manages the association between a Nat Gateway and a Public IP. + +## Example Usage + +```hcl +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_public_ip" "example" { + name = "example-PIP" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + allocation_method = "Static" + sku = "Standard" +} + +resource "azurerm_nat_gateway" "example" { + name = "example-NatGateway" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku_name = "Standard" +} + +resource "azurerm_nat_gateway_public_ip_association" "example" { + nat_gateway_id = azurerm_nat_gateway.example.id + public_ip_address_id = azurerm_public_ip.example.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `nat_gateway_id` - (Required) The ID of the Nat Gateway. Changing this forces a new resource to be created. + +* `public_ip_address_id` - (Required) The ID of the Public IP which this Nat Gateway which should be connected to. Changing this forces a new resource to be created. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The (Terraform specific) ID of the Association between the Nat Gateway and the Public IP. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the association between the Nat Gateway and the Public IP. +* `read` - (Defaults to 5 minutes) Used when retrieving the association between the Nat Gateway and the Public IP. +* `delete` - (Defaults to 30 minutes) Used when deleting the association between the Nat Gateway and the Public IP. + +## Import + +Associations between Nat Gateway and Public IP Addresses can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_nat_gateway_public_ip_association.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/natGateways/gateway1 +```