diff --git a/azurerm/internal/services/privatedns/client.go b/azurerm/internal/services/privatedns/client.go index 3b2222dde87b..49d2feb879eb 100644 --- a/azurerm/internal/services/privatedns/client.go +++ b/azurerm/internal/services/privatedns/client.go @@ -6,20 +6,24 @@ import ( ) type Client struct { - RecordSetsClient *privatedns.RecordSetsClient - PrivateZonesClient *privatedns.PrivateZonesClient + RecordSetsClient *privatedns.RecordSetsClient + PrivateZonesClient *privatedns.PrivateZonesClient + VirtualNetworkLinksClient *privatedns.VirtualNetworkLinksClient } func BuildClient(o *common.ClientOptions) *Client { - RecordSetsClient := privatedns.NewRecordSetsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&RecordSetsClient.Client, o.ResourceManagerAuthorizer) PrivateZonesClient := privatedns.NewPrivateZonesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&PrivateZonesClient.Client, o.ResourceManagerAuthorizer) + virtualNetworkLinksClient := privatedns.NewVirtualNetworkLinksClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&virtualNetworkLinksClient.Client, o.ResourceManagerAuthorizer) + return &Client{ - RecordSetsClient: &RecordSetsClient, - PrivateZonesClient: &PrivateZonesClient, + RecordSetsClient: &RecordSetsClient, + PrivateZonesClient: &PrivateZonesClient, + VirtualNetworkLinksClient: &virtualNetworkLinksClient, } } diff --git a/azurerm/provider.go b/azurerm/provider.go index bbdc6ea9267a..15957309c8c4 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -347,6 +347,7 @@ func Provider() terraform.ResourceProvider { "azurerm_private_dns_zone": resourceArmPrivateDnsZone(), "azurerm_private_dns_a_record": resourceArmPrivateDnsARecord(), "azurerm_private_dns_cname_record": resourceArmPrivateDnsCNameRecord(), + "azurerm_private_dns_zone_virtual_network_link": resourceArmPrivateDnsZoneVirtualNetworkLink(), "azurerm_proximity_placement_group": resourceArmProximityPlacementGroup(), "azurerm_public_ip": resourceArmPublicIp(), "azurerm_public_ip_prefix": resourceArmPublicIpPrefix(), diff --git a/azurerm/resource_arm_private_dns_zone_virtual_network_link.go b/azurerm/resource_arm_private_dns_zone_virtual_network_link.go new file mode 100644 index 000000000000..519e531f94d6 --- /dev/null +++ b/azurerm/resource_arm_private_dns_zone_virtual_network_link.go @@ -0,0 +1,218 @@ +package azurerm + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmPrivateDnsZoneVirtualNetworkLink() *schema.Resource { + return &schema.Resource{ + Create: resourceArmPrivateDnsZoneVirtualNetworkLinkCreateUpdate, + Read: resourceArmPrivateDnsZoneVirtualNetworkLinkRead, + Update: resourceArmPrivateDnsZoneVirtualNetworkLinkCreateUpdate, + Delete: resourceArmPrivateDnsZoneVirtualNetworkLinkDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "private_dns_zone_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "virtual_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "registration_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "resource_group_name": azure.SchemaResourceGroupNameDiffSuppress(), + + "tags": tagsSchema(), + }, + } +} + +func resourceArmPrivateDnsZoneVirtualNetworkLinkCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).privateDns.VirtualNetworkLinksClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + dnsZoneName := d.Get("private_dns_zone_name").(string) + vNetID := d.Get("virtual_network_id").(string) + registrationEnabled := d.Get("registration_enabled").(bool) + resGroup := d.Get("resource_group_name").(string) + + if requireResourcesToBeImported && d.IsNewResource() { + existing, err := client.Get(ctx, resGroup, dnsZoneName, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("error checking for presence of existing Private DNS Zone Virtual network link %q (Resource Group %q): %s", name, resGroup, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_private_dns_zone_virtual_network_link", *existing.ID) + } + } + + location := "global" + tags := d.Get("tags").(map[string]interface{}) + + parameters := privatedns.VirtualNetworkLink{ + Location: &location, + Tags: expandTags(tags), + VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ + VirtualNetwork: &privatedns.SubResource{ + ID: &vNetID, + }, + RegistrationEnabled: ®istrationEnabled, + }, + } + + etag := "" + ifNoneMatch := "" // set to empty to allow updates to records after creation + + future, err := client.CreateOrUpdate(ctx, resGroup, dnsZoneName, name, parameters, etag, ifNoneMatch) + if err != nil { + return fmt.Errorf("error creating/updating Private DNS Zone Virtual network link %q (Resource Group %q): %s", name, resGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("error waiting for Private DNS Zone Virtual network link %q to become available: %+v", name, err) + } + + resp, err := client.Get(ctx, resGroup, dnsZoneName, name) + if err != nil { + return fmt.Errorf("error retrieving Private DNS Zone Virtual network link %q (Resource Group %q): %s", name, resGroup, err) + } + + if resp.ID == nil { + return fmt.Errorf("cannot read Private DNS Zone Virtual network link %q (Resource Group %q) ID", name, resGroup) + } + + d.SetId(*resp.ID) + + return resourceArmPrivateDnsZoneVirtualNetworkLinkRead(d, meta) +} + +func resourceArmPrivateDnsZoneVirtualNetworkLinkRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).privateDns.VirtualNetworkLinksClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + dnsZoneName := id.Path["privateDnsZones"] + name := id.Path["virtualNetworkLinks"] + + resp, err := client.Get(ctx, resGroup, dnsZoneName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("error reading Private DNS Zone Virtual network link %q (Resource Group %q): %+v", name, resGroup, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resGroup) + d.Set("private_dns_zone_name", dnsZoneName) + + if props := resp.VirtualNetworkLinkProperties; props != nil { + d.Set("registration_enabled", props.RegistrationEnabled) + + if network := props.VirtualNetwork; network != nil { + d.Set("virtual_network_id", network.ID) + } + } + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func resourceArmPrivateDnsZoneVirtualNetworkLinkDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).privateDns.VirtualNetworkLinksClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + dnsZoneName := id.Path["privateDnsZones"] + name := id.Path["virtualNetworkLinks"] + + etag := "" + if future, err := client.Delete(ctx, resGroup, dnsZoneName, name, etag); err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("error deleting Virtual Network Link %q (Private DNS Zone %q / Resource Group %q): %+v", name, dnsZoneName, resGroup, err) + } + + // whilst the Delete above returns a Future, the Azure API's broken such that even though it's marked as "gone" + // it's still kicking around - so we have to poll until this is actually gone + log.Printf("[DEBUG] Waiting for Virtual Network Link %q (Private DNS Zone %q / Resource Group %q) to be deleted", name, dnsZoneName, resGroup) + stateConf := &resource.StateChangeConf{ + Pending: []string{"Available"}, + Target: []string{"NotFound"}, + Refresh: func() (interface{}, string, error) { + log.Printf("[DEBUG] Checking to see if Virtual Network Link %q (Private DNS Zone %q / Resource Group %q) is available", name, dnsZoneName, resGroup) + resp, err := client.Get(ctx, resGroup, dnsZoneName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] Virtual Network Link %q (Private DNS Zone %q / Resource Group %q) was not found", name, dnsZoneName, resGroup) + return "NotFound", "NotFound", nil + } + + return "", "error", err + } + + log.Printf("[DEBUG] Virtual Network Link %q (Private DNS Zone %q / Resource Group %q) still exists", name, dnsZoneName, resGroup) + return "Available", "Available", nil + }, + Timeout: 30 * time.Minute, + Delay: 30 * time.Second, + PollInterval: 10 * time.Second, + ContinuousTargetOccurence: 10, + } + + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("error waiting for deletion of Virtual Network Link %q (Private DNS Zone %q / Resource Group %q): %+v", name, dnsZoneName, resGroup, err) + } + + return nil +} diff --git a/azurerm/resource_arm_private_dns_zone_virtual_network_link_test.go b/azurerm/resource_arm_private_dns_zone_virtual_network_link_test.go new file mode 100644 index 000000000000..5fd95d10ba09 --- /dev/null +++ b/azurerm/resource_arm_private_dns_zone_virtual_network_link_test.go @@ -0,0 +1,284 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMPrivateDnsZoneVirtualNetworkLink_basic(t *testing.T) { + resourceName := "azurerm_private_dns_zone_virtual_network_link.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMPrivateDnsZoneVirtualNetworkLink_basic(ri, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPrivateDnsZoneVirtualNetworkLinkDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPrivateDnsZoneVirtualNetworkLinkExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMPrivateDnsZoneVirtualNetworkLink_requiresImport(t *testing.T) { + if !requireResourcesToBeImported { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_private_dns_zone_virtual_network_link.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPrivateDnsZoneVirtualNetworkLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMPrivateDnsZoneVirtualNetworkLink_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPrivateDnsZoneVirtualNetworkLinkExists(resourceName), + ), + }, + { + Config: testAccAzureRMPrivateDnsZoneVirtualNetworkLink_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_private_dns_zone_virtual_network_link"), + }, + }, + }) +} + +func TestAccAzureRMPrivateDnsZoneVirtualNetworkLink_withTags(t *testing.T) { + resourceName := "azurerm_private_dns_zone_virtual_network_link.test" + ri := tf.AccRandTimeInt() + location := testLocation() + preConfig := testAccAzureRMPrivateDnsZoneVirtualNetworkLink_withTags(ri, location) + postConfig := testAccAzureRMPrivateDnsZoneVirtualNetworkLink_withTagsUpdate(ri, location) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPrivateDnsZoneVirtualNetworkLinkDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPrivateDnsZoneVirtualNetworkLinkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPrivateDnsZoneVirtualNetworkLinkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureRMPrivateDnsZoneVirtualNetworkLinkExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // 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) + } + + name := rs.Primary.Attributes["name"] + dnsZoneName := rs.Primary.Attributes["private_dns_zone_name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Private DNS zone virtual network link: %s", name) + } + + client := testAccProvider.Meta().(*ArmClient).privateDns.VirtualNetworkLinksClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, dnsZoneName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: virtual network link %q (Private DNS zone %q / resource group: %s) does not exist", name, dnsZoneName, resourceGroup) + } + + return fmt.Errorf("Bad: Get Private DNS zone virtual network link: %+v", err) + } + + return nil + } +} + +func testCheckAzureRMPrivateDnsZoneVirtualNetworkLinkDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).privateDns.VirtualNetworkLinksClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_private_dns_zone_virtual_network_link" { + continue + } + + name := rs.Primary.Attributes["name"] + dnsZoneName := rs.Primary.Attributes["private_dns_zone_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(ctx, resourceGroup, dnsZoneName, name) + if err != nil { + if resp.StatusCode == http.StatusNotFound { + return nil + } + + return err + } + + return fmt.Errorf("Private DNS zone virtual network link still exists:\n%#v", resp) + } + + return nil +} + +func testAccAzureRMPrivateDnsZoneVirtualNetworkLink_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "vnet%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["10.0.0.0/16"] + + subnet { + name = "subnet1" + address_prefix = "10.0.1.0/24" + } +} + +resource "azurerm_private_dns_zone" "test" { + name = "acctestzone%d.com" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_private_dns_zone_virtual_network_link" "test" { + name = "acctest%d" + private_dns_zone_name = "${azurerm_private_dns_zone.test.name}" + virtual_network_id = "${azurerm_virtual_network.test.id}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureRMPrivateDnsZoneVirtualNetworkLink_requiresImport(rInt int, location string) string { + template := testAccAzureRMPrivateDnsZoneVirtualNetworkLink_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_private_dns_zone_virtual_network_link" "import" { + name = "${azurerm_private_dns_zone_virtual_network_link.test.name} + private_dns_zone_name = "${azurerm_private_dns_zone_virtual_network_link.test.private_dns_zone_name}" + virtual_network_id = "${azurerm_private_dns_zone_virtual_network_link.test.virtual_network_id}" + resource_group_name = "${azurerm_private_dns_zone_virtual_network_link.test.resource_group_name}" +} +`, template) +} + +func testAccAzureRMPrivateDnsZoneVirtualNetworkLink_withTags(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "vnet%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["10.0.0.0/16"] + + subnet { + name = "subnet1" + address_prefix = "10.0.1.0/24" + } +} + +resource "azurerm_private_dns_zone" "test" { + name = "acctestzone%d.com" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_private_dns_zone_virtual_network_link" "test" { + name = "acctest%d" + private_dns_zone_name = "${azurerm_private_dns_zone.test.name}" + virtual_network_id = "${azurerm_virtual_network.test.id}" + resource_group_name = "${azurerm_resource_group.test.name}" + + tags = { + environment = "Production" + cost_center = "MSFT" + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureRMPrivateDnsZoneVirtualNetworkLink_withTagsUpdate(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "vnet%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["10.0.0.0/16"] + + subnet { + name = "subnet1" + address_prefix = "10.0.1.0/24" + } +} + +resource "azurerm_private_dns_zone" "test" { + name = "acctestzone%d.com" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_private_dns_zone_virtual_network_link" "test" { + name = "acctestzone%d.com" + private_dns_zone_name = "${azurerm_private_dns_zone.test.name}" + virtual_network_id = "${azurerm_virtual_network.test.id}" + resource_group_name = "${azurerm_resource_group.test.name}" + + tags = { + environment = "staging" + } +} +`, rInt, location, rInt, rInt, rInt) +} diff --git a/examples/go.mod b/examples/go.mod index 0582517c5dba..3853212dee31 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -1,3 +1,5 @@ module github.com/terraform-providers/terraform-provider-azurerm/examples require github.com/tombuildsstuff/terraform-configuration-tester v0.0.0-20190313185320-aaea37c43183 + +go 1.13 diff --git a/go.mod b/go.mod index 148c2032d8b5..96a556d80ac7 100644 --- a/go.mod +++ b/go.mod @@ -22,3 +22,5 @@ require ( golang.org/x/net v0.0.0-20190502183928-7f726cade0ab gopkg.in/yaml.v2 v2.2.2 ) + +go 1.13 diff --git a/website/azurerm.erb b/website/azurerm.erb index 1db13c8d855b..5d9b92e868f7 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -1534,15 +1534,18 @@