From a4cd5eeb2bea542623c696e424d27d3b7ee4c710 Mon Sep 17 00:00:00 2001 From: stack72 Date: Mon, 21 Mar 2016 15:49:48 +0000 Subject: [PATCH] provider/azurerm: Scaffold the Azure RM Template Deployment resource --- builtin/providers/azurerm/config.go | 8 + builtin/providers/azurerm/provider.go | 3 +- .../resource_arm_template_deployment.go | 181 ++++++++++++++++++ .../resource_arm_template_deployment_test.go | 148 ++++++++++++++ 4 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/azurerm/resource_arm_template_deployment.go create mode 100644 builtin/providers/azurerm/resource_arm_template_deployment_test.go diff --git a/builtin/providers/azurerm/config.go b/builtin/providers/azurerm/config.go index bc795ce2a..73f0f6120 100644 --- a/builtin/providers/azurerm/config.go +++ b/builtin/providers/azurerm/config.go @@ -58,6 +58,8 @@ type ArmClient struct { storageServiceClient storage.AccountsClient storageUsageClient storage.UsageOperationsClient + + deploymentsClient resources.DeploymentsClient } func withRequestLogging() autorest.SendDecorator { @@ -295,6 +297,12 @@ func (c *Config) getArmClient() (*ArmClient, error) { cec.Sender = autorest.CreateSender(withRequestLogging()) client.cdnEndpointsClient = cec + dc := resources.NewDeploymentsClient(c.SubscriptionID) + setUserAgent(&dc.Client) + dc.Authorizer = spt + dc.Sender = autorest.CreateSender(withRequestLogging()) + client.deploymentsClient = dc + return &client, nil } diff --git a/builtin/providers/azurerm/provider.go b/builtin/providers/azurerm/provider.go index 6ce290be0..179c2a2d2 100644 --- a/builtin/providers/azurerm/provider.go +++ b/builtin/providers/azurerm/provider.go @@ -74,6 +74,7 @@ func Provider() terraform.ResourceProvider { "azurerm_storage_container": resourceArmStorageContainer(), "azurerm_storage_queue": resourceArmStorageQueue(), "azurerm_subnet": resourceArmSubnet(), + "azurerm_template_deployment": resourceArmTemplateDeployment(), "azurerm_virtual_machine": resourceArmVirtualMachine(), "azurerm_virtual_network": resourceArmVirtualNetwork(), }, @@ -143,7 +144,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { func registerAzureResourceProvidersWithSubscription(config *Config, client *ArmClient) error { providerClient := client.providers - providers := []string{"Microsoft.Network", "Microsoft.Compute", "Microsoft.Cdn", "Microsoft.Storage", "Microsoft.Sql", "Microsoft.Search"} + providers := []string{"Microsoft.Network", "Microsoft.Compute", "Microsoft.Cdn", "Microsoft.Storage", "Microsoft.Sql", "Microsoft.Search", "Microsoft.Resources"} for _, v := range providers { res, err := providerClient.Register(v) diff --git a/builtin/providers/azurerm/resource_arm_template_deployment.go b/builtin/providers/azurerm/resource_arm_template_deployment.go new file mode 100644 index 000000000..474d722d1 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_template_deployment.go @@ -0,0 +1,181 @@ +package azurerm + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + "github.com/Azure/azure-sdk-for-go/arm/resources/resources" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceArmTemplateDeployment() *schema.Resource { + return &schema.Resource{ + Create: resourceArmTemplateDeploymentCreate, + Read: resourceArmTemplateDeploymentRead, + Update: resourceArmTemplateDeploymentCreate, + Delete: resourceArmTemplateDeploymentDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "template_body": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + StateFunc: normalizeJson, + }, + + "parameters": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + }, + + "deployment_mode": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceArmTemplateDeploymentCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + deployClient := client.deploymentsClient + + name := d.Get("name").(string) + resGroup := d.Get("resource_group_name").(string) + deployment_mode := d.Get("deployment_mode").(string) + + log.Printf("[INFO] preparing arguments for Azure ARM Virtual Machine creation.") + properties := resources.DeploymentProperties{ + Mode: resources.DeploymentMode(deployment_mode), + } + + if v, ok := d.GetOk("parameters"); ok { + params := v.(map[string]interface{}) + properties.Parameters = ¶ms + } + + if v, ok := d.GetOk("template_body"); ok { + template, err := expandTemplateBody(v.(string)) + if err != nil { + return err + } + + properties.Template = &template + } + + deployment := resources.Deployment{ + Properties: &properties, + } + resp, err := deployClient.CreateOrUpdate(resGroup, name, deployment) + if err != nil { + return nil + } + + d.SetId(*resp.ID) + + log.Printf("[DEBUG] Waiting for Template Deploymnet (%s) to become available", name) + stateConf := &resource.StateChangeConf{ + Pending: []string{"Creating", "Updating", "Accepted", "Running"}, + Target: []string{"Succeeded"}, + Refresh: templateDeploymentStateRefreshFunc(client, resGroup, name), + Timeout: 10 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for Template Deployment (%s) to become available: %s", name, err) + } + + return resourceArmTemplateDeploymentRead(d, meta) +} + +func resourceArmTemplateDeploymentRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + deployClient := client.deploymentsClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + name := id.Path["deployments"] + if name == "" { + name = id.Path["Deployments"] + } + + resp, err := deployClient.Get(resGroup, name) + if resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("Error making Read request on Azure Template Deployment %s: %s", name, err) + } + + return nil +} + +func resourceArmTemplateDeploymentDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + deployClient := client.deploymentsClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + name := id.Path["deployments"] + if name == "" { + name = id.Path["Deployments"] + } + + _, err = deployClient.Delete(resGroup, name) + return nil +} + +func expandTemplateBody(template string) (map[string]interface{}, error) { + var templateBody map[string]interface{} + err := json.Unmarshal([]byte(template), &templateBody) + if err != nil { + return nil, fmt.Errorf("dont be a dumb fuck") + } + return templateBody, nil +} + +func normalizeJson(jsonString interface{}) string { + if jsonString == nil || jsonString == "" { + return "" + } + var j interface{} + err := json.Unmarshal([]byte(jsonString.(string)), &j) + if err != nil { + return fmt.Sprintf("Error parsing JSON: %s", err) + } + b, _ := json.Marshal(j) + return string(b[:]) +} + +func templateDeploymentStateRefreshFunc(client *ArmClient, resourceGroupName string, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + res, err := client.deploymentsClient.Get(resourceGroupName, name) + if err != nil { + return nil, "", fmt.Errorf("Error issuing read request in templateDeploymentStateRefreshFunc to Azure ARM for Template Deployment '%s' (RG: '%s'): %s", name, resourceGroupName, err) + } + + return res, *res.Properties.ProvisioningState, nil + } +} diff --git a/builtin/providers/azurerm/resource_arm_template_deployment_test.go b/builtin/providers/azurerm/resource_arm_template_deployment_test.go new file mode 100644 index 000000000..5e4f05247 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_template_deployment_test.go @@ -0,0 +1,148 @@ +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 TestAccAzureRMTemplateDeployment_basic(t *testing.T) { + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMTemplateDeployment_basicExample, ri, ri) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMTemplateDeploymentDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMTemplateDeploymentExists("azurerm_template_deployment.test"), + ), + }, + }, + }) +} + +func testCheckAzureRMTemplateDeploymentExists(name 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[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for template deployment: %s", name) + } + + conn := testAccProvider.Meta().(*ArmClient).deploymentsClient + + resp, err := conn.Get(resourceGroup, name) + if err != nil { + return fmt.Errorf("Bad: Get on deploymentsClient: %s", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: TemplateDeployment %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMTemplateDeploymentDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).vmClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_template_deployment" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(resourceGroup, name, "") + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Template Deployment still exists:\n%#v", resp.Properties) + } + } + + return nil +} + +var testAccAzureRMTemplateDeployment_basicExample = ` + resource "azurerm_resource_group" "test" { + name = "acctestrg-%d" + location = "West US" + } + + resource "azurerm_template_deployment" "test" { + name = "acctesttemplate-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + template_body = <