diff --git a/azurerm/config.go b/azurerm/config.go index 06c646de77c9c..5f75ec675f258 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -34,6 +34,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-04-30-preview/postgresql" "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2016-06-01/recoveryservices" "github.com/Azure/azure-sdk-for-go/services/redis/mgmt/2018-03-01/redis" + "github.com/Azure/azure-sdk-for-go/services/relay/mgmt/2017-04-01/relay" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-06-01/subscriptions" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-09-01/locks" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-12-01/policy" @@ -170,6 +171,9 @@ type ArmClient struct { // Recovery Services recoveryServicesVaultsClient recoveryservices.VaultsClient + // Relay + relayNamespacesClient relay.NamespacesClient + // Resources managementLocksClient locks.ManagementLocksClient deploymentsClient resources.DeploymentsClient @@ -380,6 +384,7 @@ func getArmClient(c *authentication.Config) (*ArmClient, error) { client.registerOperationalInsightsClients(endpoint, c.SubscriptionID, auth, sender) client.registerRecoveryServiceClients(endpoint, c.SubscriptionID, auth) client.registerRedisClients(endpoint, c.SubscriptionID, auth, sender) + client.registerRelayClients(endpoint, c.SubscriptionID, auth, sender) client.registerResourcesClients(endpoint, c.SubscriptionID, auth) client.registerSearchClients(endpoint, c.SubscriptionID, auth) client.registerServiceBusClients(endpoint, c.SubscriptionID, auth) @@ -812,6 +817,12 @@ func (c *ArmClient) registerRedisClients(endpoint, subscriptionId string, auth a c.redisPatchSchedulesClient = patchSchedulesClient } +func (c *ArmClient) registerRelayClients(endpoint, subscriptionId string, auth autorest.Authorizer, sender autorest.Sender) { + relayNamespacesClient := relay.NewNamespacesClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&relayNamespacesClient.Client, auth) + c.relayNamespacesClient = relayNamespacesClient +} + func (c *ArmClient) registerResourcesClients(endpoint, subscriptionId string, auth autorest.Authorizer) { locksClient := locks.NewManagementLocksClientWithBaseURI(endpoint, subscriptionId) c.configureClient(&locksClient.Client, auth) diff --git a/azurerm/import_arm_relay_namespace_test.go b/azurerm/import_arm_relay_namespace_test.go new file mode 100644 index 0000000000000..415bc931b184e --- /dev/null +++ b/azurerm/import_arm_relay_namespace_test.go @@ -0,0 +1,54 @@ +package azurerm + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAzureRMRelayNamespace_importBasic(t *testing.T) { + resourceName := "azurerm_relay_namespace.test" + + ri := acctest.RandInt() + config := testAccAzureRMRelayNamespace_basic(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRelayNamespaceDestroy, + Steps: []resource.TestStep{ + { + Config: config, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMRelayNamespace_importComplete(t *testing.T) { + resourceName := "azurerm_relay_namespace.test" + + ri := acctest.RandInt() + config := testAccAzureRMRelayNamespace_completeĀ§(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRelayNamespaceDestroy, + Steps: []resource.TestStep{ + { + Config: config, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/azurerm/provider.go b/azurerm/provider.go index 0b82bcfd4f2fd..1f4352bd71760 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -185,6 +185,7 @@ func Provider() terraform.ResourceProvider { "azurerm_postgresql_firewall_rule": resourceArmPostgreSQLFirewallRule(), "azurerm_postgresql_server": resourceArmPostgreSQLServer(), "azurerm_public_ip": resourceArmPublicIp(), + "azurerm_relay_namespace": resourceArmRelayNamespace(), "azurerm_recovery_services_vault": resourceArmRecoveryServicesVault(), "azurerm_redis_cache": resourceArmRedisCache(), "azurerm_redis_firewall_rule": resourceArmRedisFirewallRule(), diff --git a/azurerm/resource_arm_relay_namespace.go b/azurerm/resource_arm_relay_namespace.go new file mode 100644 index 0000000000000..049a1cf840ecd --- /dev/null +++ b/azurerm/resource_arm_relay_namespace.go @@ -0,0 +1,257 @@ +package azurerm + +import ( + "context" + "fmt" + "log" + + "time" + + "github.com/Azure/azure-sdk-for-go/services/relay/mgmt/2017-04-01/relay" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmRelayNamespace() *schema.Resource { + return &schema.Resource{ + Create: resourceArmRelayNamespaceCreateUpdate, + Read: resourceArmRelayNamespaceRead, + Update: resourceArmRelayNamespaceCreateUpdate, + Delete: resourceArmRelayNamespaceDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(6, 50), + }, + + "location": locationSchema(), + + "resource_group_name": resourceGroupNameSchema(), + + "sku": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + ValidateFunc: validation.StringInSlice([]string{ + string(relay.Standard), + }, true), + }, + }, + }, + }, + + "metric_id": { + Type: schema.TypeString, + Computed: true, + }, + + "primary_connection_string": { + Type: schema.TypeString, + Computed: true, + }, + + "secondary_connection_string": { + Type: schema.TypeString, + Computed: true, + }, + + "primary_key": { + Type: schema.TypeString, + Computed: true, + }, + + "secondary_key": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceArmRelayNamespaceCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).relayNamespacesClient + ctx := meta.(*ArmClient).StopContext + log.Printf("[INFO] preparing arguments for Relay Namespace creation.") + + name := d.Get("name").(string) + location := azureRMNormalizeLocation(d.Get("location").(string)) + resourceGroup := d.Get("resource_group_name").(string) + + sku := expandRelayNamespaceSku(d) + tags := d.Get("tags").(map[string]interface{}) + expandedTags := expandTags(tags) + + parameters := relay.Namespace{ + Location: utils.String(location), + Sku: sku, + NamespaceProperties: &relay.NamespaceProperties{}, + Tags: expandedTags, + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, name, parameters) + if err != nil { + return err + } + + err = future.WaitForCompletion(ctx, client.Client) + if err != nil { + return err + } + + read, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read Relay Namespace %q (resource group %s) ID", name, resourceGroup) + } + + d.SetId(*read.ID) + + return resourceArmRelayNamespaceRead(d, meta) +} + +func resourceArmRelayNamespaceRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).relayNamespacesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["namespaces"] + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on Relay Namespace %q (Resource Group %q): %s", name, resourceGroup, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + if location := resp.Location; location != nil { + d.Set("location", azureRMNormalizeLocation(*location)) + } + + if sku := resp.Sku; sku != nil { + flattenedSku := flattenRelayNamespaceSku(sku) + if err := d.Set("sku", flattenedSku); err != nil { + return fmt.Errorf("Error setting `sku`: %+v", err) + } + } + + if props := resp.NamespaceProperties; props != nil { + d.Set("metric_id", props.MetricID) + } + + keysResp, err := client.ListKeys(ctx, resourceGroup, name, "RootManageSharedAccessKey") + if err != nil { + return fmt.Errorf("Error making ListKeys request on Relay Namespace %q (Resource Group %q): %s", name, resourceGroup, err) + } + + d.Set("primary_connection_string", keysResp.PrimaryConnectionString) + d.Set("primary_key", keysResp.PrimaryKey) + d.Set("secondary_connection_string", keysResp.SecondaryConnectionString) + d.Set("secondary_key", keysResp.SecondaryKey) + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func resourceArmRelayNamespaceDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).relayNamespacesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["namespaces"] + + future, err := client.Delete(ctx, resourceGroup, name) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + + return err + } + + // we can't make use of the Future here due to a bug where 404 isn't tracked as Successful + log.Printf("[DEBUG] Waiting for Relay Namespace %q (Resource Group %q) to be deleted", name, resourceGroup) + stateConf := &resource.StateChangeConf{ + Pending: []string{"Pending"}, + Target: []string{"Deleted"}, + Refresh: relayNamespaceDeleteRefreshFunc(ctx, client, resourceGroup, name), + Timeout: 60 * time.Minute, + MinTimeout: 15 * time.Second, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for Relay Namespace %q (Resource Group %q) to be deleted: %s", name, resourceGroup, err) + } + + return nil +} + +func relayNamespaceDeleteRefreshFunc(ctx context.Context, client relay.NamespacesClient, resourceGroupName string, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + res, err := client.Get(ctx, resourceGroupName, name) + if err != nil { + if utils.ResponseWasNotFound(res.Response) { + return res, "Deleted", nil + } + + return nil, "Error", fmt.Errorf("Error issuing read request in relayNamespaceDeleteRefreshFunc to Relay Namespace %q (Resource Group %q): %s", name, resourceGroupName, err) + } + + return res, "Pending", nil + } +} + +func expandRelayNamespaceSku(d *schema.ResourceData) *relay.Sku { + vs := d.Get("sku").([]interface{}) + v := vs[0].(map[string]interface{}) + + name := v["name"].(string) + + return &relay.Sku{ + Name: utils.String(name), + Tier: relay.SkuTier(name), + } +} + +func flattenRelayNamespaceSku(input *relay.Sku) []interface{} { + if input == nil { + return []interface{}{} + } + + output := make(map[string]interface{}, 0) + if name := input.Name; name != nil { + output["name"] = *name + } + return []interface{}{output} +} diff --git a/azurerm/resource_arm_relay_namespace_test.go b/azurerm/resource_arm_relay_namespace_test.go new file mode 100644 index 0000000000000..78c169aef22e4 --- /dev/null +++ b/azurerm/resource_arm_relay_namespace_test.go @@ -0,0 +1,158 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAzureRMRelayNamespace_basic(t *testing.T) { + resourceName := "azurerm_relay_namespace.test" + ri := acctest.RandInt() + location := testLocation() + config := testAccAzureRMRelayNamespace_basic(ri, location) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRelayNamespaceDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRelayNamespaceExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "metric_id"), + resource.TestCheckResourceAttrSet(resourceName, "primary_connection_string"), + resource.TestCheckResourceAttrSet(resourceName, "secondary_connection_string"), + resource.TestCheckResourceAttrSet(resourceName, "primary_key"), + resource.TestCheckResourceAttrSet(resourceName, "secondary_key"), + ), + }, + }, + }) +} + +func TestAccAzureRMRelayNamespace_complete(t *testing.T) { + resourceName := "azurerm_relay_namespace.test" + ri := acctest.RandInt() + location := testLocation() + config := testAccAzureRMRelayNamespace_complete(ri, location) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRelayNamespaceDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRelayNamespaceExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "metric_id"), + resource.TestCheckResourceAttrSet(resourceName, "primary_connection_string"), + resource.TestCheckResourceAttrSet(resourceName, "secondary_connection_string"), + resource.TestCheckResourceAttrSet(resourceName, "primary_key"), + resource.TestCheckResourceAttrSet(resourceName, "secondary_key"), + ), + }, + }, + }) +} + +func testCheckAzureRMRelayNamespaceExists(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) + } + + resourceGroup := rs.Primary.Attributes["resource_group_name"] + name := rs.Primary.Attributes["name"] + + // Ensure resource group exists in API + client := testAccProvider.Meta().(*ArmClient).relayNamespacesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Bad: Get on relayNamespacesClient: %+v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: Relay Namespace %q (Resource Group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMRelayNamespaceDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).relayNamespacesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_relay_namespace" { + continue + } + + resourceGroup := rs.Primary.Attributes["resource_group_name"] + name := rs.Primary.Attributes["name"] + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Relay Namespace still exists:\n%#v", resp) + } + } + + return nil +} + +func testAccAzureRMRelayNamespace_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_relay_namespace" "test" { + name = "acctestrn-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + name = "Standard" + } +} +`, rInt, location, rInt) +} + +func testAccAzureRMRelayNamespace_complete(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_relay_namespace" "test" { + name = "acctestrn-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + name = "Standard" + } + + tags { + "Hello" = "World" + } +} +`, rInt, location, rInt) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 8a205cdae715f..7d118afd0049e 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -526,6 +526,10 @@ azurerm_iothub + > + azurerm_relay_namespace + + > azurerm_servicebus_namespace diff --git a/website/docs/r/relay_namespace.html.markdown b/website/docs/r/relay_namespace.html.markdown new file mode 100644 index 0000000000000..8dfc02ead48ba --- /dev/null +++ b/website/docs/r/relay_namespace.html.markdown @@ -0,0 +1,82 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_relay_namespace" +sidebar_current: "docs-azurerm-resource-relay-namespace" +description: |- + Manages an Azure Relay Namespace. + +--- + +# azurerm_relay_namespace + +Manages an Azure Relay Namespace. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "test" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_relay_namespace" "test" { + name = "example-relay" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku { + name = "Standard" + } + + tags { + source = "terraform" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Azure Relay Namespace. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to create the Azure Relay Namespace. + +* `location` - (Required) Specifies the supported Azure location where the Azure Relay Namespace exists. Changing this forces a new resource to be created. + +* `sku` - (Required) A `sku` block as defined below. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +--- + +A `sku` block contains: + +* `name` - (Required) The name of the SKU to use. At this time the only supported value is `Standard`. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Azure Relay Namespace ID. + +The following attributes are exported only if there is an authorization rule named +`RootManageSharedAccessKey` which is created automatically by Azure. + +* `primary_connection_string` - The primary connection string for the authorization rule `RootManageSharedAccessKey`. + +* `secondary_connection_string` - The secondary connection string for the authorization rule `RootManageSharedAccessKey`. + +* `primary_key` - The primary access key for the authorization rule `RootManageSharedAccessKey`. + +* `secondary_key` - The secondary access key for the authorization rule `RootManageSharedAccessKey`. + +* `metric_id` - The Identifier for Azure Insights metrics. + +## Import + +Azure Relay Namespace's can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_relay_namespace.relay1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Relay/namespaces/relay1 +```