From a4cd5eeb2bea542623c696e424d27d3b7ee4c710 Mon Sep 17 00:00:00 2001 From: stack72 Date: Mon, 21 Mar 2016 15:49:48 +0000 Subject: [PATCH 1/2] 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 = < Date: Mon, 21 Mar 2016 15:59:56 +0000 Subject: [PATCH 2/2] provider/azurerm: Add the documentation for the AzureRM Template Deployment resource --- .../resource_arm_template_deployment.go | 52 +++++++-- .../resource_arm_template_deployment_test.go | 103 ++++++++++++++++++ .../r/template_deployment.html.markdown | 95 ++++++++++++++++ website/source/layouts/azurerm.erb | 4 + 4 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 website/source/docs/providers/azurerm/r/template_deployment.html.markdown diff --git a/builtin/providers/azurerm/resource_arm_template_deployment.go b/builtin/providers/azurerm/resource_arm_template_deployment.go index 474d722d1..265570297 100644 --- a/builtin/providers/azurerm/resource_arm_template_deployment.go +++ b/builtin/providers/azurerm/resource_arm_template_deployment.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net/http" + "strings" "time" "github.com/Azure/azure-sdk-for-go/arm/resources/resources" @@ -44,6 +45,11 @@ func resourceArmTemplateDeployment() *schema.Resource { Optional: true, }, + "outputs": &schema.Schema{ + Type: schema.TypeMap, + Computed: true, + }, + "deployment_mode": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -58,16 +64,26 @@ func resourceArmTemplateDeploymentCreate(d *schema.ResourceData, meta interface{ name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) - deployment_mode := d.Get("deployment_mode").(string) + deploymentMode := d.Get("deployment_mode").(string) - log.Printf("[INFO] preparing arguments for Azure ARM Virtual Machine creation.") + log.Printf("[INFO] preparing arguments for Azure ARM Template Deployment creation.") properties := resources.DeploymentProperties{ - Mode: resources.DeploymentMode(deployment_mode), + Mode: resources.DeploymentMode(deploymentMode), } if v, ok := d.GetOk("parameters"); ok { params := v.(map[string]interface{}) - properties.Parameters = ¶ms + + newParams := make(map[string]interface{}, len(params)) + for key, val := range params { + newParams[key] = struct { + Value interface{} + }{ + Value: val, + } + } + + properties.Parameters = &newParams } if v, ok := d.GetOk("template_body"); ok { @@ -89,10 +105,10 @@ func resourceArmTemplateDeploymentCreate(d *schema.ResourceData, meta interface{ d.SetId(*resp.ID) - log.Printf("[DEBUG] Waiting for Template Deploymnet (%s) to become available", name) + log.Printf("[DEBUG] Waiting for Template Deployment (%s) to become available", name) stateConf := &resource.StateChangeConf{ - Pending: []string{"Creating", "Updating", "Accepted", "Running"}, - Target: []string{"Succeeded"}, + Pending: []string{"creating", "updating", "accepted", "running"}, + Target: []string{"succeeded"}, Refresh: templateDeploymentStateRefreshFunc(client, resGroup, name), Timeout: 10 * time.Minute, } @@ -123,8 +139,24 @@ func resourceArmTemplateDeploymentRead(d *schema.ResourceData, meta interface{}) return nil } if err != nil { - return fmt.Errorf("Error making Read request on Azure Template Deployment %s: %s", name, err) + return fmt.Errorf("Error making Read request on Azure RM Template Deployment %s: %s", name, err) } + var outputs map[string]string + if resp.Properties.Outputs != nil && len(*resp.Properties.Outputs) > 0 { + for key, output := range *resp.Properties.Outputs { + log.Printf("[INFO] Found Key %s", key) + + outputMap := output.(map[string]interface{}) + outputValue, ok := outputMap["value"] + if !ok { + // No value + continue + } + + outputs[key] = outputValue.(string) + } + } + d.Set("outputs", outputs) return nil } @@ -151,7 +183,7 @@ 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 nil, fmt.Errorf("Error Expanding the template_body for Azure RM Template Deployment") } return templateBody, nil } @@ -176,6 +208,6 @@ func templateDeploymentStateRefreshFunc(client *ArmClient, resourceGroupName str 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 + return res, strings.ToLower(*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 index 5e4f05247..188f33da9 100644 --- a/builtin/providers/azurerm/resource_arm_template_deployment_test.go +++ b/builtin/providers/azurerm/resource_arm_template_deployment_test.go @@ -28,6 +28,25 @@ func TestAccAzureRMTemplateDeployment_basic(t *testing.T) { }) } +func TestAccAzureRMTemplateDeployment_withParams(t *testing.T) { + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMTemplateDeployment_withParams, ri, 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"), + resource.TestCheckResourceAttr("azurerm_template_deployment.test", "outputs.testOutput", "Output Value"), + ), + }, + }, + }) +} + func testCheckAzureRMTemplateDeploymentExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -146,3 +165,87 @@ DEPLOY } ` + +var testAccAzureRMTemplateDeployment_withParams = ` + resource "azurerm_resource_group" "test" { + name = "acctestrg-%d" + location = "West US" + } + + output "test" { + value = "${azurerm_template_deployment.test.outputs.testOutput}" + } + + resource "azurerm_template_deployment" "test" { + name = "acctesttemplate-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + template_body = < + > + azurerm_template_deployment + + > Virtual Machine Resources