From 31c08a11dbc3185c820523a62fb12d1e101d7343 Mon Sep 17 00:00:00 2001 From: njucz <740360112@qq.com> Date: Tue, 28 Apr 2020 12:05:17 +0800 Subject: [PATCH 1/6] new resource "azurerm_maintenance_assignment" --- .../services/maintenance/client/client.go | 9 +- .../parse/maintenance_assignment.go | 96 +++++ .../parse/maintenance_assignment_test.go | 121 ++++++ .../services/maintenance/registration.go | 1 + .../resource_arm_maintenance_assignment.go | 177 +++++++++ ...esource_arm_maintenance_assignment_test.go | 349 ++++++++++++++++++ .../validate/maintenance_configuration.go | 22 ++ website/azurerm.erb | 4 + .../r/maintenance_assignment.html.markdown | 123 ++++++ 9 files changed, 900 insertions(+), 2 deletions(-) create mode 100644 azurerm/internal/services/maintenance/parse/maintenance_assignment.go create mode 100644 azurerm/internal/services/maintenance/parse/maintenance_assignment_test.go create mode 100644 azurerm/internal/services/maintenance/resource_arm_maintenance_assignment.go create mode 100644 azurerm/internal/services/maintenance/tests/resource_arm_maintenance_assignment_test.go create mode 100644 azurerm/internal/services/maintenance/validate/maintenance_configuration.go create mode 100644 website/docs/r/maintenance_assignment.html.markdown diff --git a/azurerm/internal/services/maintenance/client/client.go b/azurerm/internal/services/maintenance/client/client.go index 5fb54aed666c..4205000e7e4e 100644 --- a/azurerm/internal/services/maintenance/client/client.go +++ b/azurerm/internal/services/maintenance/client/client.go @@ -6,14 +6,19 @@ import ( ) type Client struct { - ConfigurationsClient *maintenance.ConfigurationsClient + ConfigurationsClient *maintenance.ConfigurationsClient + ConfigurationAssignmentsClient *maintenance.ConfigurationAssignmentsClient } func NewClient(o *common.ClientOptions) *Client { configurationsClient := maintenance.NewConfigurationsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&configurationsClient.Client, o.ResourceManagerAuthorizer) + configurationAssignmentsClient := maintenance.NewConfigurationAssignmentsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&configurationAssignmentsClient.Client, o.ResourceManagerAuthorizer) + return &Client{ - ConfigurationsClient: &configurationsClient, + ConfigurationsClient: &configurationsClient, + ConfigurationAssignmentsClient: &configurationAssignmentsClient, } } diff --git a/azurerm/internal/services/maintenance/parse/maintenance_assignment.go b/azurerm/internal/services/maintenance/parse/maintenance_assignment.go new file mode 100644 index 000000000000..c2f62cbcc2ca --- /dev/null +++ b/azurerm/internal/services/maintenance/parse/maintenance_assignment.go @@ -0,0 +1,96 @@ +package parse + +import ( + "fmt" + "regexp" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type MaintenanceAssignmentId struct { + *TargetResourceId + ResourceId string + Name string +} + +func MaintenanceAssignmentID(input string) (*MaintenanceAssignmentId, error) { + // two types of ID: + // 1: /subscriptions//resourcegroups//providers/microsoft.compute/virtualmachines//providers/Microsoft.Maintenance/configurationAssignments/ + // 2: /subscriptions//resourcegroups//providers//////providers/Microsoft.Maintenance/configurationAssignments/ + groups := regexp.MustCompile(`^(.+)/providers/Microsoft\.Maintenance/configurationAssignments/([^/]+)$`).FindStringSubmatch(input) + if len(groups) != 3 { + return nil, fmt.Errorf("parsing Maintenance Assignment ID: %q", input) + } + + targetResourceId, name := groups[1], groups[2] + id, err := TargetResourceID(targetResourceId) + if err != nil { + return nil, err + } + + return &MaintenanceAssignmentId{ + TargetResourceId: id, + ResourceId: targetResourceId, + Name: name, + }, nil +} + +type TargetResourceId struct { + HasParentResource bool + ResourceGroup string + ResourceProvider string + ResourceParentType string + ResourceParentName string + ResourceType string + ResourceName string +} + +func TargetResourceID(input string) (*TargetResourceId, error) { + // two types of ID: + // 1: /subscriptions//resourcegroups//providers/// + // 2: /subscriptions//resourcegroups//providers///// + + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("parsing target resource id %q: %+v", input, err) + } + + var resourceParentType, resourceParentName, resourceType, resourceName string + var hasParentResource bool + if len(id.Path) != 1 && len(id.Path) != 2 { + return nil, fmt.Errorf("parsing target resource id %q: %+v", input, err) + } + + if len(id.Path) == 1 { + // assume there is only one left key value pair + for k, v := range id.Path { + resourceType = k + resourceName = v + } + } else { + hasParentResource = true + + input = strings.TrimPrefix(input, "/") + input = strings.TrimSuffix(input, "/") + groups := regexp.MustCompile(`^subscriptions/.+/resource[gG]roups/.+/providers/.+/(.+)/(.+)/(.+)/(.+)$`).FindStringSubmatch(input) + if len(groups) != 5 { + return nil, fmt.Errorf("parsing target resource id: %q", input) + } + + resourceParentType = groups[1] + resourceParentName = groups[2] + resourceType = groups[3] + resourceName = groups[4] + } + + return &TargetResourceId{ + ResourceGroup: id.ResourceGroup, + ResourceProvider: id.Provider, + ResourceParentType: resourceParentType, + ResourceParentName: resourceParentName, + ResourceType: resourceType, + ResourceName: resourceName, + HasParentResource: hasParentResource, + }, nil +} diff --git a/azurerm/internal/services/maintenance/parse/maintenance_assignment_test.go b/azurerm/internal/services/maintenance/parse/maintenance_assignment_test.go new file mode 100644 index 000000000000..7b535afa6ca3 --- /dev/null +++ b/azurerm/internal/services/maintenance/parse/maintenance_assignment_test.go @@ -0,0 +1,121 @@ +package parse + +import ( + "testing" +) + +func TestMaintenanceAssignmentID(t *testing.T) { + testData := []struct { + Name string + Input string + Error bool + Expect *MaintenanceAssignmentId + }{ + { + Name: "Empty", + Input: "", + Error: true, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Error: true, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + { + Name: "No target resource type", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resGroup1/providers/microsoft.compute/", + Error: true, + }, + { + Name: "No target resource name", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resGroup1/providers/microsoft.compute/virtualmachines/", + Error: true, + }, + { + Name: "No Maintenance Assignment Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resGroup1/providers/microsoft.compute/virtualmachines/vm1/providers/Microsoft.Maintenance/", + Error: true, + }, + { + Name: "No Maintenance Assignment name", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resGroup1/providers/microsoft.compute/virtualmachines/vm1/providers/Microsoft.Maintenance/configurationAssignments/", + Error: true, + }, + { + Name: "ID of Maintenance Assignment to vm", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resGroup1/providers/microsoft.compute/virtualmachines/vm1/providers/Microsoft.Maintenance/configurationAssignments/assign1", + Error: false, + Expect: &MaintenanceAssignmentId{ + TargetResourceId: &TargetResourceId{ + HasParentResource: false, + ResourceGroup: "resGroup1", + ResourceProvider: "microsoft.compute", + ResourceType: "virtualmachines", + ResourceName: "vm1", + }, + ResourceId: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resGroup1/providers/microsoft.compute/virtualmachines/vm1", + Name: "assign1", + }, + }, + { + Name: "ID of Maintenance Assignment to dedicated host", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resGroup1/providers/microsoft.compute/hostGroups/group1/hosts/host1/providers/Microsoft.Maintenance/configurationAssignments/assign1", + Error: false, + Expect: &MaintenanceAssignmentId{ + TargetResourceId: &TargetResourceId{ + HasParentResource: true, + ResourceGroup: "resGroup1", + ResourceProvider: "microsoft.compute", + ResourceParentType: "hostGroups", + ResourceParentName: "group1", + ResourceType: "hosts", + ResourceName: "host1", + }, + ResourceId: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resGroup1/providers/microsoft.compute/virtualmachines/hostGroups/group1/hosts/host1", + Name: "assign1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resGroup1/providers/microsoft.compute/virtualmachines/vm1/providers/Microsoft.Maintenance/ConfigurationAssignments/assign1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q..", v.Name) + + actual, err := MaintenanceAssignmentID(v.Input) + if err != nil { + if v.Expect == nil { + continue + } + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.ResourceGroup != v.Expect.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expect.ResourceGroup, actual.ResourceGroup) + } + + if actual.ResourceProvider != v.Expect.ResourceProvider { + t.Fatalf("Expected %q but got %q for ResourceProvider", v.Expect.ResourceProvider, actual.ResourceProvider) + } + + if actual.ResourceType != v.Expect.ResourceType { + t.Fatalf("Expected %q but got %q for ResourceType", v.Expect.ResourceType, actual.ResourceType) + } + + if actual.ResourceName != v.Expect.ResourceName { + t.Fatalf("Expected %q but got %q for ResourceName", v.Expect.ResourceName, actual.ResourceName) + } + + if actual.Name != v.Expect.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expect.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/maintenance/registration.go b/azurerm/internal/services/maintenance/registration.go index d461b1a485d8..c624ae833766 100644 --- a/azurerm/internal/services/maintenance/registration.go +++ b/azurerm/internal/services/maintenance/registration.go @@ -24,6 +24,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ + "azurerm_maintenance_assignment": resourceArmMaintenanceAssignment(), "azurerm_maintenance_configuration": resourceArmMaintenanceConfiguration(), } } diff --git a/azurerm/internal/services/maintenance/resource_arm_maintenance_assignment.go b/azurerm/internal/services/maintenance/resource_arm_maintenance_assignment.go new file mode 100644 index 000000000000..f0f8cf9f1677 --- /dev/null +++ b/azurerm/internal/services/maintenance/resource_arm_maintenance_assignment.go @@ -0,0 +1,177 @@ +package maintenance + +import ( + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/maintenance/mgmt/2018-06-01-preview/maintenance" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "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/maintenance/parse" + maintenanceValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/maintenance/validate" + 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 resourceArmMaintenanceAssignment() *schema.Resource { + return &schema.Resource{ + Create: resourceArmMaintenanceAssignmentCreate, + Read: resourceArmMaintenanceAssignmentRead, + Update: nil, + Delete: resourceArmMaintenanceAssignmentDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.MaintenanceAssignmentID(id) + return err + }), + + Schema: map[string]*schema.Schema{ + "location": azure.SchemaLocation(), + + "maintenance_configuration_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: maintenanceValidate.MaintenanceConfigurationID, + }, + + "target_resource_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + }, + } +} + +func resourceArmMaintenanceAssignmentCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Maintenance.ConfigurationAssignmentsClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + targetResourceId := d.Get("target_resource_id").(string) + id, err := parse.TargetResourceID(targetResourceId) + if err != nil { + return err + } + + var listResp maintenance.ListConfigurationAssignmentsResult + if id.HasParentResource { + listResp, err = client.ListParent(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceParentType, id.ResourceParentName, id.ResourceType, id.ResourceName) + } else { + listResp, err = client.List(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceType, id.ResourceName) + } + if err != nil { + if !utils.ResponseWasNotFound(listResp.Response) { + return fmt.Errorf("checking for presense of existing Maintenance assignment to resource %q: %+v", targetResourceId, err) + } + } + if listResp.Value != nil && len(*listResp.Value) > 0 { + return tf.ImportAsExistsError("azurerm_maintenance_assignment", *(*listResp.Value)[0].ID) + } + + maintenanceConfigurationID := d.Get("maintenance_configuration_id").(string) + configurationId, _ := parse.MaintenanceConfigurationID(maintenanceConfigurationID) + + // set assignment name to configuration name + assignmentName := configurationId.Name + assignment := maintenance.ConfigurationAssignment{ + Name: utils.String(assignmentName), + Location: utils.String(location.Normalize(d.Get("location").(string))), + ConfigurationAssignmentProperties: &maintenance.ConfigurationAssignmentProperties{ + MaintenanceConfigurationID: utils.String(maintenanceConfigurationID), + ResourceID: utils.String(targetResourceId), + }, + } + + var resp maintenance.ConfigurationAssignment + if id.HasParentResource { + resp, err = client.CreateOrUpdateParent(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceParentType, id.ResourceParentName, id.ResourceType, id.ResourceName, assignmentName, assignment) + } else { + resp, err = client.CreateOrUpdate(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceType, id.ResourceName, assignmentName, assignment) + } + if err != nil { + return fmt.Errorf("creating Maintenance Assignment to resource %q: %+v", targetResourceId, err) + } + + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("cannot read Maintenance Assignment to resource %q", targetResourceId) + } + + d.SetId(*resp.ID) + return resourceArmMaintenanceAssignmentRead(d, meta) +} + +func resourceArmMaintenanceAssignmentRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Maintenance.ConfigurationAssignmentsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.MaintenanceAssignmentID(d.Id()) + if err != nil { + return err + } + + var listResp maintenance.ListConfigurationAssignmentsResult + if id.HasParentResource { + listResp, err = client.ListParent(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceParentType, id.ResourceParentName, id.ResourceType, id.ResourceName) + } else { + listResp, err = client.List(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceType, id.ResourceName) + } + if err != nil { + if !utils.ResponseWasNotFound(listResp.Response) { + return fmt.Errorf("checking for present of existing Maintenance assignment to resource %q: %+v", id.ResourceId, err) + } + return fmt.Errorf("listing Maintenance assignment to resource %q: %+v", id.ResourceId, err) + } + if listResp.Value == nil || len(*listResp.Value) == 0 { + return fmt.Errorf("could not find Maintenance assignment to resource %q", id.ResourceId) + } + + assignment := (*listResp.Value)[0] + if assignment.ID == nil || *assignment.ID == "" { + return fmt.Errorf("cannot read Maintenance Assignment to resource %q ID", id.ResourceId) + } + + // in list api, `ResourceID` returned is always nil + d.Set("target_resource_id", id.ResourceId) + if props := assignment.ConfigurationAssignmentProperties; props != nil { + d.Set("maintenance_configuration_id", props.MaintenanceConfigurationID) + } + return nil +} + +func resourceArmMaintenanceAssignmentDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Maintenance.ConfigurationAssignmentsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.MaintenanceAssignmentID(d.Id()) + if err != nil { + return err + } + + if id.HasParentResource { + _, err = client.DeleteParent(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceParentType, id.ResourceParentName, id.ResourceType, id.ResourceName, id.Name) + } else { + _, err = client.Delete(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceType, id.ResourceName, id.Name) + } + if err != nil { + return fmt.Errorf("deleting Maintenance Assignment to resource %q: %+v", id.ResourceId, err) + } + + return nil +} diff --git a/azurerm/internal/services/maintenance/tests/resource_arm_maintenance_assignment_test.go b/azurerm/internal/services/maintenance/tests/resource_arm_maintenance_assignment_test.go new file mode 100644 index 000000000000..91797befdc5c --- /dev/null +++ b/azurerm/internal/services/maintenance/tests/resource_arm_maintenance_assignment_test.go @@ -0,0 +1,349 @@ +package tests + +import ( + "fmt" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/maintenance/mgmt/2018-06-01-preview/maintenance" + "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/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/maintenance/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMMaintenanceAssignment_vmBasic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_maintenance_assignment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMaintenanceAssignmentDestroy, + Steps: []resource.TestStep{ + { + + Config: testAccAzureRMMaintenanceAssignment_vmTemplate(data), + Check: resource.ComposeTestCheckFunc(), + }, + { + // It may take a few minutes after starting a VM for it to become available to assign to a configuration + // for newly created machine, wait several minutes + PreConfig: func() { time.Sleep(3 * time.Minute) }, + Config: testAccAzureRMMaintenanceAssignment_vmBasic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMaintenanceAssignmentExists(data.ResourceName), + ), + }, + // location not returned by list rest api + data.ImportStep("location"), + }, + }) +} + +func TestAccAzureRMMaintenanceAssignment_vmRequiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_maintenance_assignment", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMaintenanceAssignmentDestroy, + Steps: []resource.TestStep{ + { + + Config: testAccAzureRMMaintenanceAssignment_vmTemplate(data), + Check: resource.ComposeTestCheckFunc(), + }, + { + // It may take a few minutes after starting a VM for it to become available to assign to a configuration + // for newly created machine, wait several minutes + PreConfig: func() { time.Sleep(3 * time.Minute) }, + Config: testAccAzureRMMaintenanceAssignment_vmBasic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMaintenanceAssignmentExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMMaintenanceAssignment_vmRequiresImport), + }, + }) +} + +func TestAccAzureRMMaintenanceAssignment_dedicatedHostBasic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_maintenance_assignment", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMaintenanceAssignmentDestroy, + Steps: []resource.TestStep{ + { + + Config: testAccAzureRMMaintenanceAssignment_dedicatedHostTemplate(data), + Check: resource.ComposeTestCheckFunc(), + }, + { + // It may take a few minutes after starting a VM for it to become available to assign to a configuration + // for newly created machine, wait several minutes + PreConfig: func() { time.Sleep(3 * time.Minute) }, + Config: testAccAzureRMMaintenanceAssignment_dedicatedHostBasic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMaintenanceAssignmentExists(data.ResourceName), + ), + }, + data.ImportStep("location"), + }, + }) +} + +func TestAccAzureRMMaintenanceAssignment_dedicatedHostRequiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_maintenance_assignment", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMaintenanceAssignmentDestroy, + Steps: []resource.TestStep{ + { + + Config: testAccAzureRMMaintenanceAssignment_dedicatedHostTemplate(data), + Check: resource.ComposeTestCheckFunc(), + }, + { + // It may take a few minutes after starting a VM for it to become available to assign to a configuration + // for newly created machine, wait several minutes + PreConfig: func() { time.Sleep(3 * time.Minute) }, + Config: testAccAzureRMMaintenanceAssignment_dedicatedHostBasic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMaintenanceAssignmentExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMMaintenanceAssignment_dedicatedHostRequiresImport), + }, + }) +} + +func testCheckAzureRMMaintenanceAssignmentDestroy(s *terraform.State) error { + conn := acceptance.AzureProvider.Meta().(*clients.Client).Maintenance.ConfigurationAssignmentsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_maintenance_assignment" { + continue + } + + id, err := parse.MaintenanceAssignmentID(rs.Primary.ID) + if err != nil { + return err + } + + var listResp maintenance.ListConfigurationAssignmentsResult + if id.HasParentResource { + listResp, err = conn.ListParent(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceParentType, id.ResourceParentName, id.ResourceType, id.ResourceName) + } else { + listResp, err = conn.List(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceType, id.ResourceName) + } + + if err != nil { + if !utils.ResponseWasNotFound(listResp.Response) { + return err + } + return nil + } + if listResp.Value != nil && len(*listResp.Value) > 0 { + return fmt.Errorf("maintenance assignment to resource %q still exists", id.ResourceId) + } + + return nil + } + + return nil +} + +func testCheckAzureRMMaintenanceAssignmentExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acceptance.AzureProvider.Meta().(*clients.Client).Maintenance.ConfigurationAssignmentsClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + + id, err := parse.MaintenanceAssignmentID(rs.Primary.ID) + if err != nil { + return err + } + + var listResp maintenance.ListConfigurationAssignmentsResult + if id.HasParentResource { + listResp, err = conn.ListParent(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceParentType, id.ResourceParentName, id.ResourceType, id.ResourceName) + } else { + listResp, err = conn.List(ctx, id.ResourceGroup, id.ResourceProvider, id.ResourceType, id.ResourceName) + } + + if err != nil { + return fmt.Errorf("bad: list on ConfigurationAssignmentsClient: %+v", err) + } + if listResp.Value == nil || len(*listResp.Value) == 0 { + return fmt.Errorf("could not find Maintenance Assignment to resource %q", id.ResourceId) + } + + return nil + } +} + +func testAccAzureRMMaintenanceAssignment_vmBasic(data acceptance.TestData) string { + template := testAccAzureRMMaintenanceAssignment_vmTemplate(data) + return fmt.Sprintf(` +%s + +resource "azurerm_maintenance_assignment" "test" { + location = azurerm_resource_group.test.location + maintenance_configuration_id = azurerm_maintenance_configuration.test.id + target_resource_id = azurerm_linux_virtual_machine.test.id +} +`, template) +} + +func testAccAzureRMMaintenanceAssignment_vmRequiresImport(data acceptance.TestData) string { + template := testAccAzureRMMaintenanceAssignment_vmBasic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_maintenance_assignment" "import" { + location = azurerm_maintenance_assignment.test.location + maintenance_configuration_id = azurerm_maintenance_assignment.test.maintenance_configuration_id + target_resource_id = azurerm_maintenance_assignment.test.target_resource_id +} +`, template) +} + +func testAccAzureRMMaintenanceAssignment_dedicatedHostBasic(data acceptance.TestData) string { + template := testAccAzureRMMaintenanceAssignment_dedicatedHostTemplate(data) + return fmt.Sprintf(` +%s + +resource "azurerm_maintenance_assignment" "test" { + location = azurerm_resource_group.test.location + maintenance_configuration_id = azurerm_maintenance_configuration.test.id + target_resource_id = azurerm_dedicated_host.test.id +} +`, template) +} + +func testAccAzureRMMaintenanceAssignment_dedicatedHostRequiresImport(data acceptance.TestData) string { + template := testAccAzureRMMaintenanceAssignment_dedicatedHostBasic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_maintenance_assignment" "import" { + location = azurerm_maintenance_assignment.test.location + maintenance_configuration_id = azurerm_maintenance_assignment.test.maintenance_configuration_id + target_resource_id = azurerm_maintenance_assignment.test.target_resource_id +} +`, template) +} + +func testAccAzureRMMaintenanceAssignment_vmTemplate(data acceptance.TestData) string { + template := testAccAzureRMMaintenanceAssignment_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_network" "test" { + name = "acctestnw-%[2]d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "internal" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.0.2.0/24" +} + +resource "azurerm_network_interface" "test" { + name = "acctni-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + ip_configuration { + name = "testconfiguration1" + subnet_id = azurerm_subnet.test.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_linux_virtual_machine" "test" { + name = "acctestVM-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + size = "Standard_D15_v2" + admin_username = "adminuser" + admin_password = "P@$$w0rd1234!" + + disable_password_authentication = false + + network_interface_ids = [ + azurerm_network_interface.test.id, + ] + + os_disk { + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } +} +`, template, data.RandomInteger) +} + +func testAccAzureRMMaintenanceAssignment_dedicatedHostTemplate(data acceptance.TestData) string { + template := testAccAzureRMMaintenanceAssignment_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_dedicated_host_group" "test" { + name = "acctest-DHG-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + platform_fault_domain_count = 2 +} + +resource "azurerm_dedicated_host" "test" { + name = "acctest-DH-%[2]d" + location = azurerm_resource_group.test.location + dedicated_host_group_id = azurerm_dedicated_host_group.test.id + sku_name = "DSv3-Type1" + platform_fault_domain = 1 +} +`, template, data.RandomInteger) +} + +func testAccAzureRMMaintenanceAssignment_template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-maint-%[1]d" + location = "%[2]s" +} + +resource "azurerm_maintenance_configuration" "test" { + name = "acctest-MC%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + scope = "All" +} +`, data.RandomInteger, data.Locations.Primary) +} diff --git a/azurerm/internal/services/maintenance/validate/maintenance_configuration.go b/azurerm/internal/services/maintenance/validate/maintenance_configuration.go new file mode 100644 index 000000000000..aec653423d20 --- /dev/null +++ b/azurerm/internal/services/maintenance/validate/maintenance_configuration.go @@ -0,0 +1,22 @@ +package validate + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/maintenance/parse" +) + +func MaintenanceConfigurationID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := parse.MaintenanceConfigurationID(v); err != nil { + errors = append(errors, fmt.Errorf("Can not parse %q as a resource id: %v", k, err)) + return + } + + return warnings, errors +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 2bd263ca1e84..735954aa4cc4 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -1704,6 +1704,10 @@
  • Maintenance Resources