Merge pull request #5758 from stack72/f-arm-template

provider/azurerm: Adding `azurerm_template_deployment` resource
This commit is contained in:
Paul Hinze 2016-03-21 14:38:49 -05:00
commit 1d6cecf514
6 changed files with 573 additions and 1 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -0,0 +1,213 @@
package azurerm
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"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,
},
"outputs": &schema.Schema{
Type: schema.TypeMap,
Computed: 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)
deploymentMode := d.Get("deployment_mode").(string)
log.Printf("[INFO] preparing arguments for Azure ARM Template Deployment creation.")
properties := resources.DeploymentProperties{
Mode: resources.DeploymentMode(deploymentMode),
}
if v, ok := d.GetOk("parameters"); ok {
params := v.(map[string]interface{})
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 {
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 Deployment (%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 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
}
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("Error Expanding the template_body for Azure RM Template Deployment")
}
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, strings.ToLower(*res.Properties.ProvisioningState), nil
}
}

View File

@ -0,0 +1,251 @@
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 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
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 = <<DEPLOY
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_ZRS"
],
"metadata": {
"description": "Storage Account type"
}
}
},
"variables": {
"location": "[resourceGroup().location]",
"storageAccountName": "[concat(uniquestring(resourceGroup().id), 'storage')]",
"publicIPAddressName": "[concat('myPublicIp', uniquestring(resourceGroup().id))]",
"publicIPAddressType": "Dynamic",
"apiVersion": "2015-06-15",
"dnsLabelPrefix": "terraform-acctest"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageAccountName')]",
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"properties": {
"accountType": "[parameters('storageAccountType')]"
}
},
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "[variables('apiVersion')]",
"name": "[variables('publicIPAddressName')]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[variables('dnsLabelPrefix')]"
}
}
}
]
}
DEPLOY
deployment_mode = "Complete"
}
`
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 = <<DEPLOY
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_ZRS"
],
"metadata": {
"description": "Storage Account type"
}
},
"dnsLabelPrefix": {
"type": "string",
"metadata": {
"description": "DNS Label for the Public IP. Must be lowercase. It should match with the following regular expression: ^[a-z][a-z0-9-]{1,61}[a-z0-9]$ or it will raise an error."
}
}
},
"variables": {
"location": "[resourceGroup().location]",
"storageAccountName": "[concat(uniquestring(resourceGroup().id), 'storage')]",
"publicIPAddressName": "[concat('myPublicIp', uniquestring(resourceGroup().id))]",
"publicIPAddressType": "Dynamic",
"apiVersion": "2015-06-15"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageAccountName')]",
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"properties": {
"accountType": "[parameters('storageAccountType')]"
}
},
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "[variables('apiVersion')]",
"name": "[variables('publicIPAddressName')]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsLabelPrefix')]"
}
}
}
],
"outputs": {
"testOutput": {
"type": "string",
"value": "Output Value"
}
}
}
DEPLOY
parameters {
dnsLabelPrefix = "terraform-test-%d"
storageAccountType = "Standard_GRS"
}
deployment_mode = "Complete"
}
`

View File

@ -0,0 +1,95 @@
---
layout: "azurerm"
page_title: "Azure Resource Manager: azurerm_template_deployment"
sidebar_current: "docs-azurerm-resource-template-deployment"
description: |-
Create a template deployment of resources.
---
# azurerm\_template\_deployment
Create a template deployment of resources
## Example Usage
```
resource "azurerm_resource_group" "test" {
name = "acctestrg-01"
location = "West US"
}
resource "azurerm_template_deployment" "test" {
name = "acctesttemplate-01"
resource_group_name = "${azurerm_resource_group.test.name}"
template_body = <<DEPLOY
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_ZRS"
],
"metadata": {
"description": "Storage Account type"
}
}
},
"variables": {
"location": "[resourceGroup().location]",
"storageAccountName": "[concat(uniquestring(resourceGroup().id), 'storage')]",
"publicIPAddressName": "[concat('myPublicIp', uniquestring(resourceGroup().id))]",
"publicIPAddressType": "Dynamic",
"apiVersion": "2015-06-15",
"dnsLabelPrefix": "terraform-acctest"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageAccountName')]",
"apiVersion": "[variables('apiVersion')]",
"location": "[variables('location')]",
"properties": {
"accountType": "[parameters('storageAccountType')]"
}
},
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "[variables('apiVersion')]",
"name": "[variables('publicIPAddressName')]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[variables('dnsLabelPrefix')]"
}
}
}
]
}
DEPLOY
deployment_mode = "Complete"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) Specifies the name of the template deployment. Changing this forces a
new resource to be created.
* `resource_group_name` - (Required) The name of the resource group in which to
create the template deployment.
* `template_body` - (Optional) Specifies the JSON definition for the template.
* `parameters` - (Optional) Specifies the name and value pairs that define the deployment parameters for the template.
* `deploymnet_mode` - (Optional) Specifies the mode that is used to deploy resources. This value could be either `Incremental` or `Complete`.
## Attributes Reference
The following attributes are exported:
* `id` - The Template Deployment ID.

View File

@ -163,6 +163,10 @@
</ul>
</li>
<li<%= sidebar_current("docs-azurerm-resource-template-deployment") %>>
<a href="/docs/providers/azurerm/r/template_deployment.html">azurerm_template_deployment</a>
</li>
<li<%= sidebar_current(/^docs-azurerm-resource-virtualmachine/) %>>
<a href="#">Virtual Machine Resources</a>
<ul class="nav nav-visible">