From b96d9a2a883a75e0bb302d830381e313fea68183 Mon Sep 17 00:00:00 2001 From: jackofallops Date: Thu, 4 Feb 2021 16:07:01 +0000 Subject: [PATCH 01/13] templatespec added to template deployments --- .../services/resource/client/client.go | 26 +- ...ment_group_template_deployment_resource.go | 370 ++++++++ .../management_group_template_deployment.go | 59 ++ ...nagement_group_template_deployment_test.go | 96 ++ .../resource/parse/template_spec_version.go | 75 ++ .../parse/template_spec_version_test.go | 128 +++ .../parse/tenant_template_deployment.go | 53 ++ .../parse/tenant_template_deployment_test.go | 80 ++ .../services/resource/registration.go | 17 +- ...urce_group_template_deployment_resource.go | 50 +- ...group_template_deployment_resource_test.go | 42 + .../internal/services/resource/resourceids.go | 3 + ...bscription_template_deployment_resource.go | 51 +- ...ption_template_deployment_resource_test.go | 50 + .../template_spec_version_data_source.go | 66 ++ .../tenant_template_deployment_resource.go | 369 +++++++ ...management_group_template_deployment_id.go | 23 + ...ement_group_template_deployment_id_test.go | 64 ++ .../validate/template_spec_version_id.go | 23 + .../validate/template_spec_version_id_test.go | 88 ++ .../validate/tenant_template_deployment_id.go | 23 + .../tenant_template_deployment_id_test.go | 52 + .../templatespecs/CHANGELOG.md | 5 + .../templatespecs/client.go | 53 ++ .../2019-06-01-preview/templatespecs/enums.go | 65 ++ .../templatespecs/models.go | 897 ++++++++++++++++++ .../templatespecs/templatespecs.go | 662 +++++++++++++ .../templatespecs/version.go | 30 + .../templatespecs/versions.go | 565 +++++++++++ vendor/modules.txt | 1 + 30 files changed, 4055 insertions(+), 31 deletions(-) create mode 100644 azurerm/internal/services/resource/management_group_template_deployment_resource.go create mode 100644 azurerm/internal/services/resource/parse/management_group_template_deployment.go create mode 100644 azurerm/internal/services/resource/parse/management_group_template_deployment_test.go create mode 100644 azurerm/internal/services/resource/parse/template_spec_version.go create mode 100644 azurerm/internal/services/resource/parse/template_spec_version_test.go create mode 100644 azurerm/internal/services/resource/parse/tenant_template_deployment.go create mode 100644 azurerm/internal/services/resource/parse/tenant_template_deployment_test.go create mode 100644 azurerm/internal/services/resource/template_spec_version_data_source.go create mode 100644 azurerm/internal/services/resource/tenant_template_deployment_resource.go create mode 100644 azurerm/internal/services/resource/validate/management_group_template_deployment_id.go create mode 100644 azurerm/internal/services/resource/validate/management_group_template_deployment_id_test.go create mode 100644 azurerm/internal/services/resource/validate/template_spec_version_id.go create mode 100644 azurerm/internal/services/resource/validate/template_spec_version_id_test.go create mode 100644 azurerm/internal/services/resource/validate/tenant_template_deployment_id.go create mode 100644 azurerm/internal/services/resource/validate/tenant_template_deployment_id_test.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/CHANGELOG.md create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/client.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/enums.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/models.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/templatespecs.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/version.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/versions.go diff --git a/azurerm/internal/services/resource/client/client.go b/azurerm/internal/services/resource/client/client.go index 25f00336ee47..e4bbcf54d77b 100644 --- a/azurerm/internal/services/resource/client/client.go +++ b/azurerm/internal/services/resource/client/client.go @@ -2,17 +2,19 @@ package client import ( providers "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources" + "github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-09-01/locks" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2020-06-01/resources" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common" ) type Client struct { - DeploymentsClient *resources.DeploymentsClient - GroupsClient *resources.GroupsClient - LocksClient *locks.ManagementLocksClient - ProvidersClient *providers.ProvidersClient - ResourcesClient *resources.Client + DeploymentsClient *resources.DeploymentsClient + GroupsClient *resources.GroupsClient + LocksClient *locks.ManagementLocksClient + ProvidersClient *providers.ProvidersClient + ResourcesClient *resources.Client + TemplateSpecsVersionsClient *templatespecs.VersionsClient } func NewClient(o *common.ClientOptions) *Client { @@ -32,11 +34,15 @@ func NewClient(o *common.ClientOptions) *Client { resourcesClient := resources.NewClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&resourcesClient.Client, o.ResourceManagerAuthorizer) + templatespecsVersionsClient := templatespecs.NewVersionsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&templatespecsVersionsClient.Client, o.ResourceManagerAuthorizer) + return &Client{ - GroupsClient: &groupsClient, - DeploymentsClient: &deploymentsClient, - LocksClient: &locksClient, - ProvidersClient: &providersClient, - ResourcesClient: &resourcesClient, + GroupsClient: &groupsClient, + DeploymentsClient: &deploymentsClient, + LocksClient: &locksClient, + ProvidersClient: &providersClient, + ResourcesClient: &resourcesClient, + TemplateSpecsVersionsClient: &templatespecsVersionsClient, } } diff --git a/azurerm/internal/services/resource/management_group_template_deployment_resource.go b/azurerm/internal/services/resource/management_group_template_deployment_resource.go new file mode 100644 index 000000000000..7540762f3c50 --- /dev/null +++ b/azurerm/internal/services/resource/management_group_template_deployment_resource.go @@ -0,0 +1,370 @@ +package resource + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2020-06-01/resources" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/resource/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/resource/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func managementGroupTemplateDeploymentResource() *schema.Resource { + return &schema.Resource{ + Create: managementGroupTemplateDeploymentResourceCreate, + Read: managementGroupTemplateDeploymentResourceRead, + Update: managementGroupTemplateDeploymentResourceUpdate, + Delete: managementGroupTemplateDeploymentResourceDelete, + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.ManagementGroupTemplateDeploymentID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(180 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(180 * time.Minute), + Delete: schema.DefaultTimeout(180 * time.Minute), + }, + + // (@jackofallops - lintignore needed as we need to make sure the JSON is usable in `output_content`) + + //lintignore:S033 + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.TemplateDeploymentName, + }, + + "location": location.Schema(), + + "template_content": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{ + "template_content", + "template_spec_version_id", + }, + StateFunc: utils.NormalizeJson, + }, + + "template_spec_version_id": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{ + "template_content", + "template_spec_version_id", + }, + ValidateFunc: validate.TemplateSpecVersionID, + }, + + // Optional + "debug_level": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(templateDeploymentDebugLevels, false), + }, + + "parameters_content": { + Type: schema.TypeString, + Optional: true, + Computed: true, + StateFunc: utils.NormalizeJson, + }, + + "tags": tags.Schema(), + + // Computed + "output_content": { + Type: schema.TypeString, + Computed: true, + StateFunc: utils.NormalizeJson, + // NOTE: outputs can be strings, ints, objects etc - whilst using a nested object was considered + // parsing the JSON using `jsondecode` allows the users to interact with/map objects as required + }, + }, + } +} + +func managementGroupTemplateDeploymentResourceCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Resource.DeploymentsClient + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id := parse.NewManagementGroupTemplateDeploymentID(subscriptionId, d.Get("name").(string)) + + existing, err := client.GetAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + } + if existing.Properties != nil { + return tf.ImportAsExistsError("azurerm_subscription_template_deployment", id.ID()) + } + + deployment := resources.ScopedDeployment{ + Location: utils.String(location.Normalize(d.Get("location").(string))), + Properties: &resources.DeploymentProperties{ + DebugSetting: expandTemplateDeploymentDebugSetting(d.Get("debug_level").(string)), + Mode: resources.Incremental, + }, + Tags: tags.Expand(d.Get("tags").(map[string]interface{})), + } + + if templateRaw, ok := d.GetOk("template_content"); ok { + template, err := expandTemplateDeploymentBody(templateRaw.(string)) + if err != nil { + return fmt.Errorf("expanding `template_content`: %+v", err) + } + deployment.Properties.Template = template + } + + if templateSpecVersionID, ok := d.GetOk("template_spec_version_id"); ok { + deployment.Properties.TemplateLink = &resources.TemplateLink{ + ID: utils.String(templateSpecVersionID.(string)), + } + } + + if v, ok := d.GetOk("parameters_content"); ok && v != "" { + parameters, err := expandTemplateDeploymentBody(v.(string)) + if err != nil { + return fmt.Errorf("expanding `parameters_content`: %+v", err) + } + deployment.Properties.Parameters = parameters + } + + log.Printf("[DEBUG] Running validation of Subscription Template Deployment %q..", id.DeploymentName) + if err := validateManagementGroupTemplateDeployment(ctx, id, deployment, client); err != nil { + return fmt.Errorf("validating Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + log.Printf("[DEBUG] Validated Subscription Template Deployment %q..", id.DeploymentName) + + log.Printf("[DEBUG] Provisioning Subscription Template Deployment %q..", id.DeploymentName) + future, err := client.CreateOrUpdateAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName, deployment) + if err != nil { + return fmt.Errorf("creating Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + + log.Printf("[DEBUG] Waiting for deployment of Subscription Template Deployment %q..", id.DeploymentName) + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + + d.SetId(id.ID()) + return managementGroupTemplateDeploymentResourceRead(d, meta) +} + +func managementGroupTemplateDeploymentResourceUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Resource.DeploymentsClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ManagementGroupTemplateDeploymentID(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Retrieving Subscription Template Deployment %q..", id.DeploymentName) + template, err := client.GetAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) + if err != nil { + return fmt.Errorf("retrieving Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + if template.Properties == nil { + return fmt.Errorf("retrieving Subscription Template Deployment %q: `properties` was nil", id.DeploymentName) + } + + // the API doesn't have a Patch operation, so we'll need to build one + deployment := resources.ScopedDeployment{ + Location: template.Location, + Properties: &resources.DeploymentProperties{ + DebugSetting: template.Properties.DebugSetting, + Mode: resources.Incremental, + }, + Tags: template.Tags, + } + + if d.HasChange("debug_level") { + deployment.Properties.DebugSetting = expandTemplateDeploymentDebugSetting(d.Get("debug_level").(string)) + } + + if d.HasChange("parameters_content") { + parameters, err := expandTemplateDeploymentBody(d.Get("parameters_content").(string)) + if err != nil { + return fmt.Errorf("expanding `parameters_content`: %+v", err) + } + deployment.Properties.Parameters = parameters + } + + if d.HasChange("template_content") { + templateContents, err := expandTemplateDeploymentBody(d.Get("template_content").(string)) + if err != nil { + return fmt.Errorf("expanding `template_content`: %+v", err) + } + + deployment.Properties.Template = templateContents + } else { + // retrieve the existing content and reuse that + exportedTemplate, err := client.ExportTemplateAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) + if err != nil { + return fmt.Errorf("retrieving Contents for Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + + deployment.Properties.Template = exportedTemplate.Template + } + + if d.HasChange("template_spec_version_id") { + deployment.Properties.TemplateLink = &resources.TemplateLink{ + ID: utils.String(d.Get("template_spec_version_id").(string)), + } + } + + if d.HasChange("tags") { + deployment.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) + } + + log.Printf("[DEBUG] Running validation of Subscription Template Deployment %q..", id.DeploymentName) + if err := validateManagementGroupTemplateDeployment(ctx, *id, deployment, client); err != nil { + return fmt.Errorf("validating Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + log.Printf("[DEBUG] Validated Subscription Template Deployment %q..", id.DeploymentName) + + log.Printf("[DEBUG] Provisioning Subscription Template Deployment %q)..", id.DeploymentName) + future, err := client.CreateOrUpdateAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName, deployment) + if err != nil { + return fmt.Errorf("creating Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + + log.Printf("[DEBUG] Waiting for deployment of Subscription Template Deployment %q..", id.DeploymentName) + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + + return managementGroupTemplateDeploymentResourceRead(d, meta) +} + +func managementGroupTemplateDeploymentResourceRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Resource.DeploymentsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ManagementGroupTemplateDeploymentID(d.Id()) + if err != nil { + return err + } + + resp, err := client.GetAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] Subscription Template Deployment %q was not found - removing from state", id.DeploymentName) + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + + templateContents, err := client.ExportTemplateAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) + if err != nil { + return fmt.Errorf("retrieving Template Content for Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + + d.Set("name", id.DeploymentName) + d.Set("location", location.NormalizeNilable(resp.Location)) + + if props := resp.Properties; props != nil { + d.Set("debug_level", flattenTemplateDeploymentDebugSetting(props.DebugSetting)) + + filteredParams := filterOutTemplateDeploymentParameters(props.Parameters) + flattenedParams, err := flattenTemplateDeploymentBody(filteredParams) + if err != nil { + return fmt.Errorf("flattening `parameters_content`: %+v", err) + } + d.Set("parameters_content", flattenedParams) + + flattenedOutputs, err := flattenTemplateDeploymentBody(props.Outputs) + if err != nil { + return fmt.Errorf("flattening `output_content`: %+v", err) + } + d.Set("output_content", flattenedOutputs) + + if props.TemplateLink != nil { + if props.TemplateLink.ID != nil { + d.Set("template_spec_version_id", props.TemplateLink.ID) + } + } + } + + flattenedTemplate, err := flattenTemplateDeploymentBody(templateContents.Template) + if err != nil { + return fmt.Errorf("flattening `template_content`: %+v", err) + } + d.Set("template_content", flattenedTemplate) + + return tags.FlattenAndSet(d, resp.Tags) +} + +func managementGroupTemplateDeploymentResourceDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Resource.DeploymentsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ManagementGroupTemplateDeploymentID(d.Id()) + if err != nil { + return err + } + + // at this time unfortunately the Resources RP doesn't expose a means of deleting top-level objects + // so we're unable to delete these during deletion - this'll need to be detailed in the docs + + log.Printf("[DEBUG] Deleting Subscription Template Deployment %q..", id.DeploymentName) + future, err := client.DeleteAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) + if err != nil { + return fmt.Errorf("deleting Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + + log.Printf("[DEBUG] Waiting for deletion of Subscription Template Deployment %q..", id.DeploymentName) + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for deletion of Subscription Template Deployment %q: %+v", id.DeploymentName, err) + } + log.Printf("[DEBUG] Deleted Subscription Template Deployment %q.", id.DeploymentName) + + return nil +} + +func validateManagementGroupTemplateDeployment(ctx context.Context, id parse.ManagementGroupTemplateDeploymentId, deployment resources.ScopedDeployment, client *resources.DeploymentsClient) error { + validationFuture, err := client.ValidateAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName, deployment) + if err != nil { + return fmt.Errorf("requesting validating: %+v", err) + } + if err := validationFuture.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for validation: %+v", err) + } + validationResult, err := validationFuture.Result(*client) + if err != nil { + return fmt.Errorf("retrieving validation result: %+v", err) + } + if validationResult.Error != nil { + if validationResult.Error.Message != nil { + return fmt.Errorf("%s", *validationResult.Error.Message) + } + return fmt.Errorf("%+v", *validationResult.Error) + } + + return nil +} diff --git a/azurerm/internal/services/resource/parse/management_group_template_deployment.go b/azurerm/internal/services/resource/parse/management_group_template_deployment.go new file mode 100644 index 000000000000..782dda3514a5 --- /dev/null +++ b/azurerm/internal/services/resource/parse/management_group_template_deployment.go @@ -0,0 +1,59 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type ManagementGroupTemplateDeploymentId struct { + ManagementGroupName string + DeploymentName string +} + +func NewManagementGroupTemplateDeploymentID(managementGroupName, deploymentName string) ManagementGroupTemplateDeploymentId { + return ManagementGroupTemplateDeploymentId{ + ManagementGroupName: managementGroupName, + DeploymentName: deploymentName, + } +} + +func (id ManagementGroupTemplateDeploymentId) String() string { + segments := []string{ + fmt.Sprintf("Deployment Name %q", id.DeploymentName), + fmt.Sprintf("Management Group Name %q", id.ManagementGroupName), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Management Group Template Deployment", segmentsStr) +} + +func (id ManagementGroupTemplateDeploymentId) ID() string { + fmtString := "/providers/Microsoft.Management/managementGroups/%s/providers/Microsoft.Resources/deployments/%s" + return fmt.Sprintf(fmtString, id.ManagementGroupName, id.DeploymentName) +} + +// ManagementGroupTemplateDeploymentID parses a ManagementGroupTemplateDeployment ID into an ManagementGroupTemplateDeploymentId struct +func ManagementGroupTemplateDeploymentID(input string) (*ManagementGroupTemplateDeploymentId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := ManagementGroupTemplateDeploymentId{} + + if resourceId.ManagementGroupName, err = id.PopSegment("managementGroups"); err != nil { + return nil, err + } + if resourceId.DeploymentName, err = id.PopSegment("deployments"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/resource/parse/management_group_template_deployment_test.go b/azurerm/internal/services/resource/parse/management_group_template_deployment_test.go new file mode 100644 index 000000000000..f5141287edb4 --- /dev/null +++ b/azurerm/internal/services/resource/parse/management_group_template_deployment_test.go @@ -0,0 +1,96 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = ManagementGroupTemplateDeploymentId{} + +func TestManagementGroupTemplateDeploymentIDFormatter(t *testing.T) { + actual := NewManagementGroupTemplateDeploymentID("my-management-group-id", "deploy1").ID() + expected := "/providers/Microsoft.Management/managementGroups/my-management-group-id/providers/Microsoft.Resources/deployments/deploy1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestManagementGroupTemplateDeploymentID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *ManagementGroupTemplateDeploymentId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing ManagementGroupName + Input: "/providers/Microsoft.Management/", + Error: true, + }, + + { + // missing value for ManagementGroupName + Input: "/providers/Microsoft.Management/managementGroups/", + Error: true, + }, + + { + // missing DeploymentName + Input: "/providers/Microsoft.Management/managementGroups/my-management-group-id/providers/Microsoft.Resources/", + Error: true, + }, + + { + // missing value for DeploymentName + Input: "/providers/Microsoft.Management/managementGroups/my-management-group-id/providers/Microsoft.Resources/deployments/", + Error: true, + }, + + { + // valid + Input: "/providers/Microsoft.Management/managementGroups/my-management-group-id/providers/Microsoft.Resources/deployments/deploy1", + Expected: &ManagementGroupTemplateDeploymentId{ + ManagementGroupName: "my-management-group-id", + DeploymentName: "deploy1", + }, + }, + + { + // upper-cased + Input: "/PROVIDERS/MICROSOFT.MANAGEMENT/MANAGEMENTGROUPS/MY-MANAGEMENT-GROUP-ID/PROVIDERS/MICROSOFT.RESOURCES/DEPLOYMENTS/DEPLOY1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := ManagementGroupTemplateDeploymentID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.ManagementGroupName != v.Expected.ManagementGroupName { + t.Fatalf("Expected %q but got %q for ManagementGroupName", v.Expected.ManagementGroupName, actual.ManagementGroupName) + } + if actual.DeploymentName != v.Expected.DeploymentName { + t.Fatalf("Expected %q but got %q for DeploymentName", v.Expected.DeploymentName, actual.DeploymentName) + } + } +} diff --git a/azurerm/internal/services/resource/parse/template_spec_version.go b/azurerm/internal/services/resource/parse/template_spec_version.go new file mode 100644 index 000000000000..29deaf59cfee --- /dev/null +++ b/azurerm/internal/services/resource/parse/template_spec_version.go @@ -0,0 +1,75 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type TemplateSpecVersionId struct { + SubscriptionId string + ResourceGroup string + TemplateSpecName string + VersionName string +} + +func NewTemplateSpecVersionID(subscriptionId, resourceGroup, templateSpecName, versionName string) TemplateSpecVersionId { + return TemplateSpecVersionId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + TemplateSpecName: templateSpecName, + VersionName: versionName, + } +} + +func (id TemplateSpecVersionId) String() string { + segments := []string{ + fmt.Sprintf("Version Name %q", id.VersionName), + fmt.Sprintf("Template Spec Name %q", id.TemplateSpecName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Template Spec Version", segmentsStr) +} + +func (id TemplateSpecVersionId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Resources/templateSpecs/%s/versions/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.TemplateSpecName, id.VersionName) +} + +// TemplateSpecVersionID parses a TemplateSpecVersion ID into an TemplateSpecVersionId struct +func TemplateSpecVersionID(input string) (*TemplateSpecVersionId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := TemplateSpecVersionId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.TemplateSpecName, err = id.PopSegment("templateSpecs"); err != nil { + return nil, err + } + if resourceId.VersionName, err = id.PopSegment("versions"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/resource/parse/template_spec_version_test.go b/azurerm/internal/services/resource/parse/template_spec_version_test.go new file mode 100644 index 000000000000..9069a7595898 --- /dev/null +++ b/azurerm/internal/services/resource/parse/template_spec_version_test.go @@ -0,0 +1,128 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = TemplateSpecVersionId{} + +func TestTemplateSpecVersionIDFormatter(t *testing.T) { + actual := NewTemplateSpecVersionID("12345678-1234-9876-4563-123456789012", "templateSpecRG", "templateSpec1", "v1.0").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/templateSpecRG/providers/Microsoft.Resources/templateSpecs/templateSpec1/versions/v1.0" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestTemplateSpecVersionID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *TemplateSpecVersionId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing TemplateSpecName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/templateSpecRG/providers/Microsoft.Resources/", + Error: true, + }, + + { + // missing value for TemplateSpecName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/templateSpecRG/providers/Microsoft.Resources/templateSpecs/", + Error: true, + }, + + { + // missing VersionName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/templateSpecRG/providers/Microsoft.Resources/templateSpecs/templateSpec1/", + Error: true, + }, + + { + // missing value for VersionName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/templateSpecRG/providers/Microsoft.Resources/templateSpecs/templateSpec1/versions/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/templateSpecRG/providers/Microsoft.Resources/templateSpecs/templateSpec1/versions/v1.0", + Expected: &TemplateSpecVersionId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "templateSpecRG", + TemplateSpecName: "templateSpec1", + VersionName: "v1.0", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/TEMPLATESPECRG/PROVIDERS/MICROSOFT.RESOURCES/TEMPLATESPECS/TEMPLATESPEC1/VERSIONS/V1.0", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := TemplateSpecVersionID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.TemplateSpecName != v.Expected.TemplateSpecName { + t.Fatalf("Expected %q but got %q for TemplateSpecName", v.Expected.TemplateSpecName, actual.TemplateSpecName) + } + if actual.VersionName != v.Expected.VersionName { + t.Fatalf("Expected %q but got %q for VersionName", v.Expected.VersionName, actual.VersionName) + } + } +} diff --git a/azurerm/internal/services/resource/parse/tenant_template_deployment.go b/azurerm/internal/services/resource/parse/tenant_template_deployment.go new file mode 100644 index 000000000000..ee08a7718a08 --- /dev/null +++ b/azurerm/internal/services/resource/parse/tenant_template_deployment.go @@ -0,0 +1,53 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type TenantTemplateDeploymentId struct { + DeploymentName string +} + +func NewTenantTemplateDeploymentID(deploymentName string) TenantTemplateDeploymentId { + return TenantTemplateDeploymentId{ + DeploymentName: deploymentName, + } +} + +func (id TenantTemplateDeploymentId) String() string { + segments := []string{ + fmt.Sprintf("Deployment Name %q", id.DeploymentName), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Tenant Template Deployment", segmentsStr) +} + +func (id TenantTemplateDeploymentId) ID() string { + fmtString := "/providers/Microsoft.Resources/deployments/%s" + return fmt.Sprintf(fmtString, id.DeploymentName) +} + +// TenantTemplateDeploymentID parses a TenantTemplateDeployment ID into an TenantTemplateDeploymentId struct +func TenantTemplateDeploymentID(input string) (*TenantTemplateDeploymentId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := TenantTemplateDeploymentId{} + + if resourceId.DeploymentName, err = id.PopSegment("deployments"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/resource/parse/tenant_template_deployment_test.go b/azurerm/internal/services/resource/parse/tenant_template_deployment_test.go new file mode 100644 index 000000000000..1b129ac618c1 --- /dev/null +++ b/azurerm/internal/services/resource/parse/tenant_template_deployment_test.go @@ -0,0 +1,80 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = TenantTemplateDeploymentId{} + +func TestTenantTemplateDeploymentIDFormatter(t *testing.T) { + actual := NewTenantTemplateDeploymentID("deploy1").ID() + expected := "/providers/Microsoft.Resources/deployments/deploy1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestTenantTemplateDeploymentID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *TenantTemplateDeploymentId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing DeploymentName + Input: "/providers/Microsoft.Resources/", + Error: true, + }, + + { + // missing value for DeploymentName + Input: "/providers/Microsoft.Resources/deployments/", + Error: true, + }, + + { + // valid + Input: "/providers/Microsoft.Resources/deployments/deploy1", + Expected: &TenantTemplateDeploymentId{ + DeploymentName: "deploy1", + }, + }, + + { + // upper-cased + Input: "/PROVIDERS/MICROSOFT.RESOURCES/DEPLOYMENTS/DEPLOY1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := TenantTemplateDeploymentID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.DeploymentName != v.Expected.DeploymentName { + t.Fatalf("Expected %q but got %q for DeploymentName", v.Expected.DeploymentName, actual.DeploymentName) + } + } +} diff --git a/azurerm/internal/services/resource/registration.go b/azurerm/internal/services/resource/registration.go index e78d8c5dd4b9..0facd2b622ce 100644 --- a/azurerm/internal/services/resource/registration.go +++ b/azurerm/internal/services/resource/registration.go @@ -24,19 +24,22 @@ func (r Registration) WebsiteCategories() []string { // SupportedDataSources returns the supported Data Sources supported by this Service func (r Registration) SupportedDataSources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_resources": dataSourceResources(), - "azurerm_resource_group": dataSourceResourceGroup(), + "azurerm_resources": dataSourceResources(), + "azurerm_resource_group": dataSourceResourceGroup(), + "azurerm_template_spec_version": dataSourceTemplateSpecVersion(), } } // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_management_lock": resourceManagementLock(), - "azurerm_resource_group": resourceResourceGroup(), - "azurerm_resource_group_template_deployment": resourceGroupTemplateDeploymentResource(), - "azurerm_subscription_template_deployment": subscriptionTemplateDeploymentResource(), - "azurerm_template_deployment": resourceTemplateDeployment(), + "azurerm_management_lock": resourceManagementLock(), + "azurerm_management_group_template_deployment": managementGroupTemplateDeploymentResource(), + "azurerm_resource_group": resourceResourceGroup(), + "azurerm_resource_group_template_deployment": resourceGroupTemplateDeploymentResource(), + "azurerm_subscription_template_deployment": subscriptionTemplateDeploymentResource(), + "azurerm_template_deployment": resourceTemplateDeployment(), + "azurerm_tenant_template_deployment": tenantTemplateDeploymentResource(), } } diff --git a/azurerm/internal/services/resource/resource_group_template_deployment_resource.go b/azurerm/internal/services/resource/resource_group_template_deployment_resource.go index eb93e6c71845..32ab1ddf8936 100644 --- a/azurerm/internal/services/resource/resource_group_template_deployment_resource.go +++ b/azurerm/internal/services/resource/resource_group_template_deployment_resource.go @@ -61,11 +61,26 @@ func resourceGroupTemplateDeploymentResource() *schema.Resource { }, "template_content": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{ + "template_content", + "template_spec_version_id", + }, StateFunc: utils.NormalizeJson, }, + "template_spec_version_id": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{ + "template_content", + "template_spec_version_id", + }, + ValidateFunc: validate.TemplateSpecVersionID, + }, + // Optional "debug_level": { Type: schema.TypeString, @@ -112,19 +127,28 @@ func resourceGroupTemplateDeploymentResourceCreate(d *schema.ResourceData, meta return tf.ImportAsExistsError("azurerm_resource_group_template_deployment", id.ID()) } - template, err := expandTemplateDeploymentBody(d.Get("template_content").(string)) - if err != nil { - return fmt.Errorf("expanding `template_content`: %+v", err) - } deployment := resources.Deployment{ Properties: &resources.DeploymentProperties{ DebugSetting: expandTemplateDeploymentDebugSetting(d.Get("debug_level").(string)), Mode: resources.DeploymentMode(d.Get("deployment_mode").(string)), - Template: template, }, Tags: tags.Expand(d.Get("tags").(map[string]interface{})), } + if templateRaw, ok := d.GetOk("template_content"); ok { + template, err := expandTemplateDeploymentBody(templateRaw.(string)) + if err != nil { + return fmt.Errorf("expanding `template_content`: %+v", err) + } + deployment.Properties.Template = template + } + + if templateSpecVersionID, ok := d.GetOk("template_spec_version_id"); ok { + deployment.Properties.TemplateLink = &resources.TemplateLink{ + ID: utils.String(templateSpecVersionID.(string)), + } + } + if v, ok := d.GetOk("parameters_content"); ok && v != "" { parameters, err := expandTemplateDeploymentBody(v.(string)) if err != nil { @@ -215,6 +239,12 @@ func resourceGroupTemplateDeploymentResourceUpdate(d *schema.ResourceData, meta deployment.Properties.Template = exportedTemplate.Template } + if d.HasChange("template_spec_version_id") { + deployment.Properties.TemplateLink = &resources.TemplateLink{ + ID: utils.String(d.Get("template_spec_version_id").(string)), + } + } + if d.HasChange("tags") { deployment.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) } @@ -284,6 +314,12 @@ func resourceGroupTemplateDeploymentResourceRead(d *schema.ResourceData, meta in return fmt.Errorf("flattening `output_content`: %+v", err) } d.Set("output_content", flattenedOutputs) + + if props.TemplateLink != nil { + if props.TemplateLink.ID != nil { + d.Set("template_spec_version_id", props.TemplateLink.ID) + } + } } flattenedTemplate, err := flattenTemplateDeploymentBody(templateContents.Template) diff --git a/azurerm/internal/services/resource/resource_group_template_deployment_resource_test.go b/azurerm/internal/services/resource/resource_group_template_deployment_resource_test.go index 15d3d821b1c4..d3c912d56b2a 100644 --- a/azurerm/internal/services/resource/resource_group_template_deployment_resource_test.go +++ b/azurerm/internal/services/resource/resource_group_template_deployment_resource_test.go @@ -204,6 +204,21 @@ func TestAccResourceGroupTemplateDeployment_childItems(t *testing.T) { }) } +func TestAccResourceGroupTemplateDeployment_templateSpec(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_group_template_deployment", "test") + r := ResourceGroupTemplateDeploymentResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.templateSpecVersionConfig(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t ResourceGroupTemplateDeploymentResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { id, err := parse.ResourceGroupTemplateDeploymentID(state.ID) if err != nil { @@ -247,6 +262,33 @@ TEMPLATE `, data.RandomInteger, data.Locations.Primary, deploymentMode) } +func (ResourceGroupTemplateDeploymentResource) templateSpecVersionConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestrg-%d" + location = %q +} + +data "azurerm_template_spec_version" "test" { + name = "acctest-standing-data-for-rg" + resource_group_name = "stedev-201130" + version = "v1.0.0" +} + +resource "azurerm_resource_group_template_deployment" "test" { + name = "acctest" + resource_group_name = azurerm_resource_group.test.name + deployment_mode = "Incremental" + + template_spec_version_id = data.azurerm_template_spec_version.test.id +} +`, data.RandomInteger, data.Locations.Primary) +} + func (ResourceGroupTemplateDeploymentResource) emptyWithTagsConfig(data acceptance.TestData, deploymentMode string) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/azurerm/internal/services/resource/resourceids.go b/azurerm/internal/services/resource/resourceids.go index a30b0f8b68de..2a05b8c8159d 100644 --- a/azurerm/internal/services/resource/resourceids.go +++ b/azurerm/internal/services/resource/resourceids.go @@ -1,7 +1,10 @@ package resource //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ResourceGroup -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ManagementGroupTemplateDeployment -id=/providers/Microsoft.Management/managementGroups/my-management-group-id/providers/Microsoft.Resources/deployments/deploy1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ResourceGroupTemplateDeployment -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Resources/deployments/deploy1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=SubscriptionTemplateDeployment -id=/subscriptions/12345678-1234-9876-4563-123456789012/providers/Microsoft.Resources/deployments/deploy1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=TemplateSpecVersion -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/templateSpecRG/providers/Microsoft.Resources/templateSpecs/templateSpec1/versions/v1.0 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=TenantTemplateDeployment -id=/providers/Microsoft.Resources/deployments/deploy1 // ResourceProvider is manually maintained since the generator doesn't support outputting this information at this time diff --git a/azurerm/internal/services/resource/subscription_template_deployment_resource.go b/azurerm/internal/services/resource/subscription_template_deployment_resource.go index 660621f1b579..c15185cf0f8d 100644 --- a/azurerm/internal/services/resource/subscription_template_deployment_resource.go +++ b/azurerm/internal/services/resource/subscription_template_deployment_resource.go @@ -52,11 +52,26 @@ func subscriptionTemplateDeploymentResource() *schema.Resource { "location": location.Schema(), "template_content": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{ + "template_content", + "template_spec_version_id", + }, StateFunc: utils.NormalizeJson, }, + "template_spec_version_id": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{ + "template_content", + "template_spec_version_id", + }, + ValidateFunc: validate.TemplateSpecVersionID, + }, + // Optional "debug_level": { Type: schema.TypeString, @@ -102,20 +117,30 @@ func subscriptionTemplateDeploymentResourceCreate(d *schema.ResourceData, meta i if existing.Properties != nil { return tf.ImportAsExistsError("azurerm_subscription_template_deployment", id.ID()) } - template, err := expandTemplateDeploymentBody(d.Get("template_content").(string)) - if err != nil { - return fmt.Errorf("expanding `template_content`: %+v", err) - } + deployment := resources.Deployment{ Location: utils.String(location.Normalize(d.Get("location").(string))), Properties: &resources.DeploymentProperties{ DebugSetting: expandTemplateDeploymentDebugSetting(d.Get("debug_level").(string)), Mode: resources.Incremental, - Template: template, }, Tags: tags.Expand(d.Get("tags").(map[string]interface{})), } + if templateRaw, ok := d.GetOk("template_content"); ok { + template, err := expandTemplateDeploymentBody(templateRaw.(string)) + if err != nil { + return fmt.Errorf("expanding `template_content`: %+v", err) + } + deployment.Properties.Template = template + } + + if templateSpecVersionID, ok := d.GetOk("template_spec_version_id"); ok { + deployment.Properties.TemplateLink = &resources.TemplateLink{ + ID: utils.String(templateSpecVersionID.(string)), + } + } + if v, ok := d.GetOk("parameters_content"); ok && v != "" { parameters, err := expandTemplateDeploymentBody(v.(string)) if err != nil { @@ -203,6 +228,12 @@ func subscriptionTemplateDeploymentResourceUpdate(d *schema.ResourceData, meta i deployment.Properties.Template = exportedTemplate.Template } + if d.HasChange("template_spec_version_id") { + deployment.Properties.TemplateLink = &resources.TemplateLink{ + ID: utils.String(d.Get("template_spec_version_id").(string)), + } + } + if d.HasChange("tags") { deployment.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) } @@ -271,6 +302,12 @@ func subscriptionTemplateDeploymentResourceRead(d *schema.ResourceData, meta int return fmt.Errorf("flattening `output_content`: %+v", err) } d.Set("output_content", flattenedOutputs) + + if props.TemplateLink != nil { + if props.TemplateLink.ID != nil { + d.Set("template_spec_version_id", props.TemplateLink.ID) + } + } } flattenedTemplate, err := flattenTemplateDeploymentBody(templateContents.Template) diff --git a/azurerm/internal/services/resource/subscription_template_deployment_resource_test.go b/azurerm/internal/services/resource/subscription_template_deployment_resource_test.go index a9992ca27a8d..9b99bb64ac2f 100644 --- a/azurerm/internal/services/resource/subscription_template_deployment_resource_test.go +++ b/azurerm/internal/services/resource/subscription_template_deployment_resource_test.go @@ -40,6 +40,21 @@ func TestAccSubscriptionTemplateDeployment_empty(t *testing.T) { }) } +func TestAccSubscriptionTemplateDeployment_templateSpecVersion(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_subscription_template_deployment", "test") + r := SubscriptionTemplateDeploymentResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.templateSpecVersionConfig(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccSubscriptionTemplateDeployment_singleItemUpdatingParams(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_subscription_template_deployment", "test") r := SubscriptionTemplateDeploymentResource{} @@ -137,6 +152,41 @@ TEMPLATE `, data.RandomInteger, data.Locations.Primary) } +func (SubscriptionTemplateDeploymentResource) templateSpecVersionConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +data "azurerm_template_spec_version" "test" { + name = "acctest-standing-data-for-sub" + resource_group_name = "stedev-201130" + version = "v1.0.0" +} + +resource "azurerm_subscription_template_deployment" "test" { + name = "acctestsubdeploy-%d" + location = %[2]q + + template_spec_version_id = data.azurerm_template_spec_version.test.id + + parameters_content = < 0 { + queryParameters["$expand"] = autorest.Encode("query", expand) + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Resources/templateSpecs/{templateSpecName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// GetSender sends the Get request. The method will close the +// http.Response Body if it receives an error. +func (client Client) GetSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// GetResponder handles the response to the Get request. The method always +// closes the http.Response Body. +func (client Client) GetResponder(resp *http.Response) (result TemplateSpec, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListByResourceGroup lists all the Template Specs within the specified resource group. +// Parameters: +// resourceGroupName - the name of the resource group. The name is case insensitive. +// expand - allows for expansion of additional Template Spec details in the response. Optional. +func (client Client) ListByResourceGroup(ctx context.Context, resourceGroupName string, expand TemplateSpecExpandKind) (result ListResultPage, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/Client.ListByResourceGroup") + defer func() { + sc := -1 + if result.lr.Response.Response != nil { + sc = result.lr.Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + if err := validation.Validate([]validation.Validation{ + {TargetValue: resourceGroupName, + Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}}); err != nil { + return result, validation.NewError("templatespecs.Client", "ListByResourceGroup", err.Error()) + } + + result.fn = client.listByResourceGroupNextResults + req, err := client.ListByResourceGroupPreparer(ctx, resourceGroupName, expand) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.Client", "ListByResourceGroup", nil, "Failure preparing request") + return + } + + resp, err := client.ListByResourceGroupSender(req) + if err != nil { + result.lr.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "templatespecs.Client", "ListByResourceGroup", resp, "Failure sending request") + return + } + + result.lr, err = client.ListByResourceGroupResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.Client", "ListByResourceGroup", resp, "Failure responding to request") + return + } + if result.lr.hasNextLink() && result.lr.IsEmpty() { + err = result.NextWithContext(ctx) + return + } + + return +} + +// ListByResourceGroupPreparer prepares the ListByResourceGroup request. +func (client Client) ListByResourceGroupPreparer(ctx context.Context, resourceGroupName string, expand TemplateSpecExpandKind) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + const APIVersion = "2019-06-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + if len(string(expand)) > 0 { + queryParameters["$expand"] = autorest.Encode("query", expand) + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Resources/templateSpecs/", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// ListByResourceGroupSender sends the ListByResourceGroup request. The method will close the +// http.Response Body if it receives an error. +func (client Client) ListByResourceGroupSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// ListByResourceGroupResponder handles the response to the ListByResourceGroup request. The method always +// closes the http.Response Body. +func (client Client) ListByResourceGroupResponder(resp *http.Response) (result ListResult, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// listByResourceGroupNextResults retrieves the next set of results, if any. +func (client Client) listByResourceGroupNextResults(ctx context.Context, lastResults ListResult) (result ListResult, err error) { + req, err := lastResults.listResultPreparer(ctx) + if err != nil { + return result, autorest.NewErrorWithError(err, "templatespecs.Client", "listByResourceGroupNextResults", nil, "Failure preparing next results request") + } + if req == nil { + return + } + resp, err := client.ListByResourceGroupSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "templatespecs.Client", "listByResourceGroupNextResults", resp, "Failure sending next results request") + } + result, err = client.ListByResourceGroupResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.Client", "listByResourceGroupNextResults", resp, "Failure responding to next results request") + } + return +} + +// ListByResourceGroupComplete enumerates all values, automatically crossing page boundaries as required. +func (client Client) ListByResourceGroupComplete(ctx context.Context, resourceGroupName string, expand TemplateSpecExpandKind) (result ListResultIterator, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/Client.ListByResourceGroup") + defer func() { + sc := -1 + if result.Response().Response.Response != nil { + sc = result.page.Response().Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + result.page, err = client.ListByResourceGroup(ctx, resourceGroupName, expand) + return +} + +// ListBySubscription lists all the Template Specs within the specified subscriptions. +// Parameters: +// expand - allows for expansion of additional Template Spec details in the response. Optional. +func (client Client) ListBySubscription(ctx context.Context, expand TemplateSpecExpandKind) (result ListResultPage, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/Client.ListBySubscription") + defer func() { + sc := -1 + if result.lr.Response.Response != nil { + sc = result.lr.Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + result.fn = client.listBySubscriptionNextResults + req, err := client.ListBySubscriptionPreparer(ctx, expand) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.Client", "ListBySubscription", nil, "Failure preparing request") + return + } + + resp, err := client.ListBySubscriptionSender(req) + if err != nil { + result.lr.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "templatespecs.Client", "ListBySubscription", resp, "Failure sending request") + return + } + + result.lr, err = client.ListBySubscriptionResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.Client", "ListBySubscription", resp, "Failure responding to request") + return + } + if result.lr.hasNextLink() && result.lr.IsEmpty() { + err = result.NextWithContext(ctx) + return + } + + return +} + +// ListBySubscriptionPreparer prepares the ListBySubscription request. +func (client Client) ListBySubscriptionPreparer(ctx context.Context, expand TemplateSpecExpandKind) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + const APIVersion = "2019-06-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + if len(string(expand)) > 0 { + queryParameters["$expand"] = autorest.Encode("query", expand) + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/Microsoft.Resources/templateSpecs/", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// ListBySubscriptionSender sends the ListBySubscription request. The method will close the +// http.Response Body if it receives an error. +func (client Client) ListBySubscriptionSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// ListBySubscriptionResponder handles the response to the ListBySubscription request. The method always +// closes the http.Response Body. +func (client Client) ListBySubscriptionResponder(resp *http.Response) (result ListResult, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// listBySubscriptionNextResults retrieves the next set of results, if any. +func (client Client) listBySubscriptionNextResults(ctx context.Context, lastResults ListResult) (result ListResult, err error) { + req, err := lastResults.listResultPreparer(ctx) + if err != nil { + return result, autorest.NewErrorWithError(err, "templatespecs.Client", "listBySubscriptionNextResults", nil, "Failure preparing next results request") + } + if req == nil { + return + } + resp, err := client.ListBySubscriptionSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "templatespecs.Client", "listBySubscriptionNextResults", resp, "Failure sending next results request") + } + result, err = client.ListBySubscriptionResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.Client", "listBySubscriptionNextResults", resp, "Failure responding to next results request") + } + return +} + +// ListBySubscriptionComplete enumerates all values, automatically crossing page boundaries as required. +func (client Client) ListBySubscriptionComplete(ctx context.Context, expand TemplateSpecExpandKind) (result ListResultIterator, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/Client.ListBySubscription") + defer func() { + sc := -1 + if result.Response().Response.Response != nil { + sc = result.page.Response().Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + result.page, err = client.ListBySubscription(ctx, expand) + return +} + +// Update updates Template Spec tags with specified values. +// Parameters: +// resourceGroupName - the name of the resource group. The name is case insensitive. +// templateSpecName - name of the Template Spec. +// templateSpec - template Spec resource with the tags to be updated. +func (client Client) Update(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpec *UpdateModel) (result TemplateSpec, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/Client.Update") + defer func() { + sc := -1 + if result.Response.Response != nil { + sc = result.Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + if err := validation.Validate([]validation.Validation{ + {TargetValue: resourceGroupName, + Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecName, + Constraints: []validation.Constraint{{Target: "templateSpecName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}}); err != nil { + return result, validation.NewError("templatespecs.Client", "Update", err.Error()) + } + + req, err := client.UpdatePreparer(ctx, resourceGroupName, templateSpecName, templateSpec) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.Client", "Update", nil, "Failure preparing request") + return + } + + resp, err := client.UpdateSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "templatespecs.Client", "Update", resp, "Failure sending request") + return + } + + result, err = client.UpdateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.Client", "Update", resp, "Failure responding to request") + return + } + + return +} + +// UpdatePreparer prepares the Update request. +func (client Client) UpdatePreparer(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpec *UpdateModel) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + "templateSpecName": autorest.Encode("path", templateSpecName), + } + + const APIVersion = "2019-06-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPatch(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Resources/templateSpecs/{templateSpecName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + if templateSpec != nil { + preparer = autorest.DecoratePreparer(preparer, + autorest.WithJSON(templateSpec)) + } + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// UpdateSender sends the Update request. The method will close the +// http.Response Body if it receives an error. +func (client Client) UpdateSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// UpdateResponder handles the response to the Update request. The method always +// closes the http.Response Body. +func (client Client) UpdateResponder(resp *http.Response) (result TemplateSpec, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/version.go b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/version.go new file mode 100644 index 000000000000..317192320017 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/version.go @@ -0,0 +1,30 @@ +package templatespecs + +import "github.com/Azure/azure-sdk-for-go/version" + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +// UserAgent returns the UserAgent string to use when sending http.Requests. +func UserAgent() string { + return "Azure-SDK-For-Go/" + Version() + " templatespecs/2019-06-01-preview" +} + +// Version returns the semantic version (see http://semver.org) of the client. +func Version() string { + return version.Number +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/versions.go b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/versions.go new file mode 100644 index 000000000000..c9d0de61d3ff --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs/versions.go @@ -0,0 +1,565 @@ +package templatespecs + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +import ( + "context" + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/validation" + "github.com/Azure/go-autorest/tracing" + "net/http" +) + +// VersionsClient is the the APIs listed in this specification can be used to manage Template Spec resources through +// the Azure Resource Manager. +type VersionsClient struct { + BaseClient +} + +// NewVersionsClient creates an instance of the VersionsClient client. +func NewVersionsClient(subscriptionID string) VersionsClient { + return NewVersionsClientWithBaseURI(DefaultBaseURI, subscriptionID) +} + +// NewVersionsClientWithBaseURI creates an instance of the VersionsClient client using a custom endpoint. Use this +// when interacting with an Azure cloud that uses a non-standard base URI (sovereign clouds, Azure stack). +func NewVersionsClientWithBaseURI(baseURI string, subscriptionID string) VersionsClient { + return VersionsClient{NewWithBaseURI(baseURI, subscriptionID)} +} + +// CreateOrUpdate creates or updates a Template Spec version. +// Parameters: +// resourceGroupName - the name of the resource group. The name is case insensitive. +// templateSpecName - name of the Template Spec. +// templateSpecVersion - the version of the Template Spec. +// templateSpecVersionModel - template Spec Version supplied to the operation. +func (client VersionsClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpecVersion string, templateSpecVersionModel VersionTemplatespecs) (result VersionTemplatespecs, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/VersionsClient.CreateOrUpdate") + defer func() { + sc := -1 + if result.Response.Response != nil { + sc = result.Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + if err := validation.Validate([]validation.Validation{ + {TargetValue: resourceGroupName, + Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecName, + Constraints: []validation.Constraint{{Target: "templateSpecName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecVersion, + Constraints: []validation.Constraint{{Target: "templateSpecVersion", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecVersion", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecVersion", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecVersionModel, + Constraints: []validation.Constraint{{Target: "templateSpecVersionModel.Location", Name: validation.Null, Rule: true, Chain: nil}, + {Target: "templateSpecVersionModel.VersionProperties", Name: validation.Null, Rule: true, + Chain: []validation.Constraint{{Target: "templateSpecVersionModel.VersionProperties.Description", Name: validation.Null, Rule: false, + Chain: []validation.Constraint{{Target: "templateSpecVersionModel.VersionProperties.Description", Name: validation.MaxLength, Rule: 4096, Chain: nil}}}, + }}}}}); err != nil { + return result, validation.NewError("templatespecs.VersionsClient", "CreateOrUpdate", err.Error()) + } + + req, err := client.CreateOrUpdatePreparer(ctx, resourceGroupName, templateSpecName, templateSpecVersion, templateSpecVersionModel) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "CreateOrUpdate", nil, "Failure preparing request") + return + } + + resp, err := client.CreateOrUpdateSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "CreateOrUpdate", resp, "Failure sending request") + return + } + + result, err = client.CreateOrUpdateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "CreateOrUpdate", resp, "Failure responding to request") + return + } + + return +} + +// CreateOrUpdatePreparer prepares the CreateOrUpdate request. +func (client VersionsClient) CreateOrUpdatePreparer(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpecVersion string, templateSpecVersionModel VersionTemplatespecs) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + "templateSpecName": autorest.Encode("path", templateSpecName), + "templateSpecVersion": autorest.Encode("path", templateSpecVersion), + } + + const APIVersion = "2019-06-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Resources/templateSpecs/{templateSpecName}/versions/{templateSpecVersion}", pathParameters), + autorest.WithJSON(templateSpecVersionModel), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// CreateOrUpdateSender sends the CreateOrUpdate request. The method will close the +// http.Response Body if it receives an error. +func (client VersionsClient) CreateOrUpdateSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// CreateOrUpdateResponder handles the response to the CreateOrUpdate request. The method always +// closes the http.Response Body. +func (client VersionsClient) CreateOrUpdateResponder(resp *http.Response) (result VersionTemplatespecs, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// Delete deletes a specific version from a Template Spec. When operation completes, status code 200 returned without +// content. +// Parameters: +// resourceGroupName - the name of the resource group. The name is case insensitive. +// templateSpecName - name of the Template Spec. +// templateSpecVersion - the version of the Template Spec. +func (client VersionsClient) Delete(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpecVersion string) (result autorest.Response, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/VersionsClient.Delete") + defer func() { + sc := -1 + if result.Response != nil { + sc = result.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + if err := validation.Validate([]validation.Validation{ + {TargetValue: resourceGroupName, + Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecName, + Constraints: []validation.Constraint{{Target: "templateSpecName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecVersion, + Constraints: []validation.Constraint{{Target: "templateSpecVersion", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecVersion", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecVersion", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}}); err != nil { + return result, validation.NewError("templatespecs.VersionsClient", "Delete", err.Error()) + } + + req, err := client.DeletePreparer(ctx, resourceGroupName, templateSpecName, templateSpecVersion) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "Delete", nil, "Failure preparing request") + return + } + + resp, err := client.DeleteSender(req) + if err != nil { + result.Response = resp + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "Delete", resp, "Failure sending request") + return + } + + result, err = client.DeleteResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "Delete", resp, "Failure responding to request") + return + } + + return +} + +// DeletePreparer prepares the Delete request. +func (client VersionsClient) DeletePreparer(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpecVersion string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + "templateSpecName": autorest.Encode("path", templateSpecName), + "templateSpecVersion": autorest.Encode("path", templateSpecVersion), + } + + const APIVersion = "2019-06-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsDelete(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Resources/templateSpecs/{templateSpecName}/versions/{templateSpecVersion}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// DeleteSender sends the Delete request. The method will close the +// http.Response Body if it receives an error. +func (client VersionsClient) DeleteSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// DeleteResponder handles the response to the Delete request. The method always +// closes the http.Response Body. +func (client VersionsClient) DeleteResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusNoContent), + autorest.ByClosing()) + result.Response = resp + return +} + +// Get gets a Template Spec version from a specific Template Spec. +// Parameters: +// resourceGroupName - the name of the resource group. The name is case insensitive. +// templateSpecName - name of the Template Spec. +// templateSpecVersion - the version of the Template Spec. +func (client VersionsClient) Get(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpecVersion string) (result VersionTemplatespecs, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/VersionsClient.Get") + defer func() { + sc := -1 + if result.Response.Response != nil { + sc = result.Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + if err := validation.Validate([]validation.Validation{ + {TargetValue: resourceGroupName, + Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecName, + Constraints: []validation.Constraint{{Target: "templateSpecName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecVersion, + Constraints: []validation.Constraint{{Target: "templateSpecVersion", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecVersion", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecVersion", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}}); err != nil { + return result, validation.NewError("templatespecs.VersionsClient", "Get", err.Error()) + } + + req, err := client.GetPreparer(ctx, resourceGroupName, templateSpecName, templateSpecVersion) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "Get", nil, "Failure preparing request") + return + } + + resp, err := client.GetSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "Get", resp, "Failure sending request") + return + } + + result, err = client.GetResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "Get", resp, "Failure responding to request") + return + } + + return +} + +// GetPreparer prepares the Get request. +func (client VersionsClient) GetPreparer(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpecVersion string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + "templateSpecName": autorest.Encode("path", templateSpecName), + "templateSpecVersion": autorest.Encode("path", templateSpecVersion), + } + + const APIVersion = "2019-06-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Resources/templateSpecs/{templateSpecName}/versions/{templateSpecVersion}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// GetSender sends the Get request. The method will close the +// http.Response Body if it receives an error. +func (client VersionsClient) GetSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// GetResponder handles the response to the Get request. The method always +// closes the http.Response Body. +func (client VersionsClient) GetResponder(resp *http.Response) (result VersionTemplatespecs, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// List lists all the Template Spec versions in the specified Template Spec. +// Parameters: +// resourceGroupName - the name of the resource group. The name is case insensitive. +// templateSpecName - name of the Template Spec. +func (client VersionsClient) List(ctx context.Context, resourceGroupName string, templateSpecName string) (result VersionsListResultPage, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/VersionsClient.List") + defer func() { + sc := -1 + if result.vlr.Response.Response != nil { + sc = result.vlr.Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + if err := validation.Validate([]validation.Validation{ + {TargetValue: resourceGroupName, + Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecName, + Constraints: []validation.Constraint{{Target: "templateSpecName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}}); err != nil { + return result, validation.NewError("templatespecs.VersionsClient", "List", err.Error()) + } + + result.fn = client.listNextResults + req, err := client.ListPreparer(ctx, resourceGroupName, templateSpecName) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "List", nil, "Failure preparing request") + return + } + + resp, err := client.ListSender(req) + if err != nil { + result.vlr.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "List", resp, "Failure sending request") + return + } + + result.vlr, err = client.ListResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "List", resp, "Failure responding to request") + return + } + if result.vlr.hasNextLink() && result.vlr.IsEmpty() { + err = result.NextWithContext(ctx) + return + } + + return +} + +// ListPreparer prepares the List request. +func (client VersionsClient) ListPreparer(ctx context.Context, resourceGroupName string, templateSpecName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + "templateSpecName": autorest.Encode("path", templateSpecName), + } + + const APIVersion = "2019-06-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Resources/templateSpecs/{templateSpecName}/versions", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// ListSender sends the List request. The method will close the +// http.Response Body if it receives an error. +func (client VersionsClient) ListSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// ListResponder handles the response to the List request. The method always +// closes the http.Response Body. +func (client VersionsClient) ListResponder(resp *http.Response) (result VersionsListResult, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// listNextResults retrieves the next set of results, if any. +func (client VersionsClient) listNextResults(ctx context.Context, lastResults VersionsListResult) (result VersionsListResult, err error) { + req, err := lastResults.versionsListResultPreparer(ctx) + if err != nil { + return result, autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "listNextResults", nil, "Failure preparing next results request") + } + if req == nil { + return + } + resp, err := client.ListSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "listNextResults", resp, "Failure sending next results request") + } + result, err = client.ListResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "listNextResults", resp, "Failure responding to next results request") + } + return +} + +// ListComplete enumerates all values, automatically crossing page boundaries as required. +func (client VersionsClient) ListComplete(ctx context.Context, resourceGroupName string, templateSpecName string) (result VersionsListResultIterator, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/VersionsClient.List") + defer func() { + sc := -1 + if result.Response().Response.Response != nil { + sc = result.page.Response().Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + result.page, err = client.List(ctx, resourceGroupName, templateSpecName) + return +} + +// Update updates Template Spec Version tags with specified values. +// Parameters: +// resourceGroupName - the name of the resource group. The name is case insensitive. +// templateSpecName - name of the Template Spec. +// templateSpecVersion - the version of the Template Spec. +// templateSpecVersionUpdateModel - template Spec Version resource with the tags to be updated. +func (client VersionsClient) Update(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpecVersion string, templateSpecVersionUpdateModel *VersionUpdateModel) (result VersionTemplatespecs, err error) { + if tracing.IsEnabled() { + ctx = tracing.StartSpan(ctx, fqdn+"/VersionsClient.Update") + defer func() { + sc := -1 + if result.Response.Response != nil { + sc = result.Response.Response.StatusCode + } + tracing.EndSpan(ctx, sc, err) + }() + } + if err := validation.Validate([]validation.Validation{ + {TargetValue: resourceGroupName, + Constraints: []validation.Constraint{{Target: "resourceGroupName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "resourceGroupName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "resourceGroupName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecName, + Constraints: []validation.Constraint{{Target: "templateSpecName", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecName", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecName", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}, + {TargetValue: templateSpecVersion, + Constraints: []validation.Constraint{{Target: "templateSpecVersion", Name: validation.MaxLength, Rule: 90, Chain: nil}, + {Target: "templateSpecVersion", Name: validation.MinLength, Rule: 1, Chain: nil}, + {Target: "templateSpecVersion", Name: validation.Pattern, Rule: `^[-\w\._\(\)]+$`, Chain: nil}}}}); err != nil { + return result, validation.NewError("templatespecs.VersionsClient", "Update", err.Error()) + } + + req, err := client.UpdatePreparer(ctx, resourceGroupName, templateSpecName, templateSpecVersion, templateSpecVersionUpdateModel) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "Update", nil, "Failure preparing request") + return + } + + resp, err := client.UpdateSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "Update", resp, "Failure sending request") + return + } + + result, err = client.UpdateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "templatespecs.VersionsClient", "Update", resp, "Failure responding to request") + return + } + + return +} + +// UpdatePreparer prepares the Update request. +func (client VersionsClient) UpdatePreparer(ctx context.Context, resourceGroupName string, templateSpecName string, templateSpecVersion string, templateSpecVersionUpdateModel *VersionUpdateModel) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + "templateSpecName": autorest.Encode("path", templateSpecName), + "templateSpecVersion": autorest.Encode("path", templateSpecVersion), + } + + const APIVersion = "2019-06-01-preview" + queryParameters := map[string]interface{}{ + "api-version": APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPatch(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Resources/templateSpecs/{templateSpecName}/versions/{templateSpecVersion}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + if templateSpecVersionUpdateModel != nil { + preparer = autorest.DecoratePreparer(preparer, + autorest.WithJSON(templateSpecVersionUpdateModel)) + } + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// UpdateSender sends the Update request. The method will close the +// http.Response Body if it receives an error. +func (client VersionsClient) UpdateSender(req *http.Request) (*http.Response, error) { + return client.Send(req, azure.DoRetryWithRegistration(client.Client)) +} + +// UpdateResponder handles the response to the Update request. The method always +// closes the http.Response Body. +func (client VersionsClient) UpdateResponder(resp *http.Response) (result VersionTemplatespecs, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6cbf737e8a90..a5f3263ac01c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -81,6 +81,7 @@ github.com/Azure/azure-sdk-for-go/services/preview/operationsmanagement/mgmt/201 github.com/Azure/azure-sdk-for-go/services/preview/policyinsights/mgmt/2019-10-01-preview/policyinsights github.com/Azure/azure-sdk-for-go/services/preview/portal/mgmt/2019-01-01-preview/portal github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2018-03-01-preview/managementgroups +github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs github.com/Azure/azure-sdk-for-go/services/preview/security/mgmt/v3.0/security github.com/Azure/azure-sdk-for-go/services/preview/securityinsight/mgmt/2019-01-01-preview/securityinsight github.com/Azure/azure-sdk-for-go/services/preview/servicebus/mgmt/2018-01-01-preview/servicebus From b361a77d10311cfd83b2708b95ffa438088caba6 Mon Sep 17 00:00:00 2001 From: jackofallops Date: Fri, 5 Feb 2021 13:55:46 +0000 Subject: [PATCH 02/13] added tests for new resources and fixed error message typos --- ...ment_group_template_deployment_resource.go | 65 ++++---- ...group_template_deployment_resource_test.go | 156 ++++++++++++++++++ .../management_group_template_deployment.go | 39 ++++- .../parse/tenant_template_deployment.go | 39 ++++- .../internal/services/resource/resourceids.go | 2 - .../tenant_template_deployment_resource.go | 56 +++---- ...enant_template_deployment_resource_test.go | 107 ++++++++++++ ...management_group_template_deployment_id.go | 2 - 8 files changed, 397 insertions(+), 69 deletions(-) create mode 100644 azurerm/internal/services/resource/management_group_template_deployment_resource_test.go create mode 100644 azurerm/internal/services/resource/tenant_template_deployment_resource_test.go diff --git a/azurerm/internal/services/resource/management_group_template_deployment_resource.go b/azurerm/internal/services/resource/management_group_template_deployment_resource.go index 7540762f3c50..dc7f82605010 100644 --- a/azurerm/internal/services/resource/management_group_template_deployment_resource.go +++ b/azurerm/internal/services/resource/management_group_template_deployment_resource.go @@ -49,6 +49,11 @@ func managementGroupTemplateDeploymentResource() *schema.Resource { ValidateFunc: validate.TemplateDeploymentName, }, + "management_group_name": { + Type: schema.TypeString, + Required: true, + }, + "location": location.Schema(), "template_content": { @@ -102,20 +107,19 @@ func managementGroupTemplateDeploymentResource() *schema.Resource { func managementGroupTemplateDeploymentResourceCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Resource.DeploymentsClient - subscriptionId := meta.(*clients.Client).Account.SubscriptionId ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - id := parse.NewManagementGroupTemplateDeploymentID(subscriptionId, d.Get("name").(string)) + id := parse.NewManagementGroupTemplateDeploymentID(d.Get("management_group_name").(string), d.Get("name").(string)) existing, err := client.GetAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for presence of existing Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("checking for presence of existing Management Group Template Deployment %q: %+v", id.DeploymentName, err) } } if existing.Properties != nil { - return tf.ImportAsExistsError("azurerm_subscription_template_deployment", id.ID()) + return tf.ImportAsExistsError("azurerm_management_group_template_deployment", id.ID()) } deployment := resources.ScopedDeployment{ @@ -149,21 +153,21 @@ func managementGroupTemplateDeploymentResourceCreate(d *schema.ResourceData, met deployment.Properties.Parameters = parameters } - log.Printf("[DEBUG] Running validation of Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Running validation of Management Group Template Deployment %q..", id.DeploymentName) if err := validateManagementGroupTemplateDeployment(ctx, id, deployment, client); err != nil { - return fmt.Errorf("validating Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("validating Management Group Template Deployment %q: %+v", id.DeploymentName, err) } - log.Printf("[DEBUG] Validated Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Validated Management Group Template Deployment %q..", id.DeploymentName) - log.Printf("[DEBUG] Provisioning Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Provisioning Management Group Template Deployment %q..", id.DeploymentName) future, err := client.CreateOrUpdateAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName, deployment) if err != nil { - return fmt.Errorf("creating Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("creating Management Group Template Deployment %q: %+v", id.DeploymentName, err) } - log.Printf("[DEBUG] Waiting for deployment of Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Waiting for deployment of Management Group Template Deployment %q..", id.DeploymentName) if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting for creation of Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("waiting for creation of Management Group Template Deployment %q: %+v", id.DeploymentName, err) } d.SetId(id.ID()) @@ -180,13 +184,13 @@ func managementGroupTemplateDeploymentResourceUpdate(d *schema.ResourceData, met return err } - log.Printf("[DEBUG] Retrieving Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Retrieving Management Group Template Deployment %q..", id.DeploymentName) template, err := client.GetAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) if err != nil { - return fmt.Errorf("retrieving Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("retrieving Management Group Template Deployment %q: %+v", id.DeploymentName, err) } if template.Properties == nil { - return fmt.Errorf("retrieving Subscription Template Deployment %q: `properties` was nil", id.DeploymentName) + return fmt.Errorf("retrieving Management Group Template Deployment %q: `properties` was nil", id.DeploymentName) } // the API doesn't have a Patch operation, so we'll need to build one @@ -222,7 +226,7 @@ func managementGroupTemplateDeploymentResourceUpdate(d *schema.ResourceData, met // retrieve the existing content and reuse that exportedTemplate, err := client.ExportTemplateAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) if err != nil { - return fmt.Errorf("retrieving Contents for Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("retrieving Contents for Management Group Template Deployment %q: %+v", id.DeploymentName, err) } deployment.Properties.Template = exportedTemplate.Template @@ -238,21 +242,21 @@ func managementGroupTemplateDeploymentResourceUpdate(d *schema.ResourceData, met deployment.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) } - log.Printf("[DEBUG] Running validation of Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Running validation of Management Group Template Deployment %q..", id.DeploymentName) if err := validateManagementGroupTemplateDeployment(ctx, *id, deployment, client); err != nil { - return fmt.Errorf("validating Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("validating Management Group Template Deployment %q: %+v", id.DeploymentName, err) } - log.Printf("[DEBUG] Validated Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Validated Management Group Template Deployment %q..", id.DeploymentName) - log.Printf("[DEBUG] Provisioning Subscription Template Deployment %q)..", id.DeploymentName) + log.Printf("[DEBUG] Provisioning Management Group Template Deployment %q)..", id.DeploymentName) future, err := client.CreateOrUpdateAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName, deployment) if err != nil { - return fmt.Errorf("creating Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("creating Management Group Template Deployment %q: %+v", id.DeploymentName, err) } - log.Printf("[DEBUG] Waiting for deployment of Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Waiting for deployment of Management Group Template Deployment %q..", id.DeploymentName) if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting for creation of Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("waiting for creation of Management Group Template Deployment %q: %+v", id.DeploymentName, err) } return managementGroupTemplateDeploymentResourceRead(d, meta) @@ -271,20 +275,21 @@ func managementGroupTemplateDeploymentResourceRead(d *schema.ResourceData, meta resp, err := client.GetAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { - log.Printf("[DEBUG] Subscription Template Deployment %q was not found - removing from state", id.DeploymentName) + log.Printf("[DEBUG] Management Group Template Deployment %q was not found - removing from state", id.DeploymentName) d.SetId("") return nil } - return fmt.Errorf("retrieving Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("retrieving Management Group Template Deployment %q: %+v", id.DeploymentName, err) } templateContents, err := client.ExportTemplateAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) if err != nil { - return fmt.Errorf("retrieving Template Content for Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("retrieving Template Content for Management Group Template Deployment %q: %+v", id.DeploymentName, err) } d.Set("name", id.DeploymentName) + d.Set("management_group_name", id.ManagementGroupName) d.Set("location", location.NormalizeNilable(resp.Location)) if props := resp.Properties; props != nil { @@ -332,17 +337,17 @@ func managementGroupTemplateDeploymentResourceDelete(d *schema.ResourceData, met // at this time unfortunately the Resources RP doesn't expose a means of deleting top-level objects // so we're unable to delete these during deletion - this'll need to be detailed in the docs - log.Printf("[DEBUG] Deleting Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Deleting Management Group Template Deployment %q..", id.DeploymentName) future, err := client.DeleteAtManagementGroupScope(ctx, id.ManagementGroupName, id.DeploymentName) if err != nil { - return fmt.Errorf("deleting Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("deleting Management Group Template Deployment %q: %+v", id.DeploymentName, err) } - log.Printf("[DEBUG] Waiting for deletion of Subscription Template Deployment %q..", id.DeploymentName) + log.Printf("[DEBUG] Waiting for deletion of Management Group Template Deployment %q..", id.DeploymentName) if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting for deletion of Subscription Template Deployment %q: %+v", id.DeploymentName, err) + return fmt.Errorf("waiting for deletion of Management Group Template Deployment %q: %+v", id.DeploymentName, err) } - log.Printf("[DEBUG] Deleted Subscription Template Deployment %q.", id.DeploymentName) + log.Printf("[DEBUG] Deleted Management Group Template Deployment %q.", id.DeploymentName) return nil } diff --git a/azurerm/internal/services/resource/management_group_template_deployment_resource_test.go b/azurerm/internal/services/resource/management_group_template_deployment_resource_test.go new file mode 100644 index 000000000000..66c1a3ef1b38 --- /dev/null +++ b/azurerm/internal/services/resource/management_group_template_deployment_resource_test.go @@ -0,0 +1,156 @@ +package resource_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/resource/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type ManagementGroupTemplateDeploymentResource struct{} + +func TestAccManagementGroupTemplateDeployment_empty(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_management_group_template_deployment", "test") + r := ManagementGroupTemplateDeploymentResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.emptyConfig(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + // set some tags + Config: r.emptyWithTagsConfig(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccManagementGroupTemplateDeployment_templateSpec(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_management_group_template_deployment", "test") + r := ManagementGroupTemplateDeploymentResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.templateSpecVersionConfig(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (ManagementGroupTemplateDeploymentResource) templateSpecVersionConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_management_group" "test" { + name = "TestAcc-Deployment-%[1]d" +} + +data "azurerm_template_spec_version" "test" { + name = "acctest-standing-data-empty" + resource_group_name = "stedev-201130" + version = "v1.0.0" +} + +resource "azurerm_management_group_template_deployment" "test" { + name = "acctestMGdeploy-%[1]d" + management_group_name = azurerm_management_group.test.name + location = %[2]q + + template_spec_version_id = data.azurerm_template_spec_version.test.id + +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func (ManagementGroupTemplateDeploymentResource) emptyConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_management_group" "test" { + name = "TestAcc-Deployment-%[1]d" +} + +resource "azurerm_management_group_template_deployment" "test" { + name = "acctestMGdeploy-%[1]d" + management_group_name = azurerm_management_group.test.name + location = %[2]q + + template_content = <