diff --git a/example/multi-machine/main.tf b/example/multi-machine/main.tf deleted file mode 100644 index a418e1fe..00000000 --- a/example/multi-machine/main.tf +++ /dev/null @@ -1,19 +0,0 @@ -provider "vra7" { - username = var.username - password = var.password - tenant = var.tenant - host = var.host -} - -# Catalog "multi_machine_catalog" contains Linux, Windows and http (apache) designs. -resource "vra7_deployment" "resource_1" { - count = 1 - catalog_item_name = "multi_machine_catalog" - resource_configuration = { - "Windows.cpu" = "2" //Windows Machine CPU - "Linux.cpu" = "2" //Linux Machine CPU - "http.hostname" = "xyz.com" //HTTP (apache) hostname - "http.network_mode" = "bridge" //HTTP (apache) network mode - } -} - diff --git a/example/multi-machine/terraform.tfvars.sample b/example/multi-machine/terraform.tfvars.sample deleted file mode 100644 index e34fe510..00000000 --- a/example/multi-machine/terraform.tfvars.sample +++ /dev/null @@ -1,4 +0,0 @@ -username = "username" -password = "password" -tenant = "tenant" -host = "https://host" diff --git a/example/multi-machine/variables.tf b/example/multi-machine/variables.tf deleted file mode 100644 index 56d162bd..00000000 --- a/example/multi-machine/variables.tf +++ /dev/null @@ -1,12 +0,0 @@ -variable "username" { -} - -variable "password" { -} - -variable "tenant" { -} - -variable "host" { -} - diff --git a/example/multi-machine/versions.tf b/example/multi-machine/versions.tf deleted file mode 100644 index ac97c6ac..00000000 --- a/example/multi-machine/versions.tf +++ /dev/null @@ -1,4 +0,0 @@ - -terraform { - required_version = ">= 0.12" -} diff --git a/example/remote-execute/main.tf b/example/remote-execute/main.tf index 64bbf0ee..cce1b73b 100644 --- a/example/remote-execute/main.tf +++ b/example/remote-execute/main.tf @@ -5,7 +5,7 @@ provider "vra7" { host = var.host } -resource "vra7_deployment" "vm" { +resource "vra7_deployment" "this" { catalog_item_name = var.catalog_item_name reasons = var.description description = var.description @@ -17,10 +17,12 @@ resource "vra7_deployment" "vm" { } resource_configuration = { - "Machine.description" = var.description - "Machine.cpu" = var.cpu - "Machine.memory" = var.ram - "Machine.ip_address" = "" + component_name = "Machine" + configuration = { + description = var.description + cpu = var.cpu + memory = var.ram + } } wait_timeout = var.wait_timeout @@ -28,9 +30,10 @@ resource "vra7_deployment" "vm" { // Connection settings // Connection settings connection { - host = self.resource_configuration["Machine.ip_address"] + host = self.resource_configuration[*].ip_address user = var.ssh_user password = var.ssh_password + type = ssh } // Extend volume to second disk diff --git a/example/simple/main.tf b/example/simple/main.tf index 560c9383..115d5f4a 100644 --- a/example/simple/main.tf +++ b/example/simple/main.tf @@ -5,11 +5,40 @@ provider "vra7" { host = var.host } -resource "vra7_deployment" "machine" { +resource "vra7_deployment" "this" { count = 1 catalog_item_name = "CentOS 7.0 x64" - resource_configuration = { - "Linux.cpu" = "2" + description = "this description" + reason = "this reason" + lease_days = 10 + + deployment_configuration = { + "blueprint_custom_property" = "This is a blueprint custom property" + } + + resource_configuration { + component_name = "Linux 1" + cluster = 2 + configuration = { + cpu = 2 + memory = 2048 + custom_property = "VM custom property" + security_tag = <key_list %v\n", componentNameList) - - // Arrange component names in descending order of text length. - // Component names are sorted this way because '.', which is used as a separator, may also occur within - // component names. In these situations, the longest name match that includes '.'s should win. - sort.Sort(byLength(componentNameList)) - - //Update request template field values with values from user configuration. - for configKey, configValue := range p.ResourceConfiguration { - for _, componentName := range componentNameList { - // User-supplied resource configuration keys are expected to be of the form: - // .. - // Extract the property names and values for each component in the blueprint, and add/update - // them in the right location in the request template. - if strings.HasPrefix(configKey, componentName) { - propertyName := strings.TrimPrefix(configKey, componentName+".") - if len(propertyName) == 0 { - return fmt.Errorf( - "resource_configuration key is not in correct format. Expected %s to start with %s", - configKey, componentName+".") - } - // Function call which changes request template field values with user-supplied values - requestTemplate.Data[componentName] = updateRequestTemplate( - requestTemplate.Data[componentName].(map[string]interface{}), - propertyName, - configValue) - break - } + requestTemplate.BusinessGroupID = p.BusinessGroupID + requestTemplate.Data["_leaseDays"] = p.Lease + for field, value := range p.DeploymentConfiguration { + requestTemplate.Data[field] = utils.UnmarshalJSONStringIfNecessary(field, value) + } + + for _, rConfig := range p.ResourceConfiguration { + rConfig.Configuration["_cluster"] = rConfig.Cluster + for propertyName, propertyValue := range rConfig.Configuration { + requestTemplate.Data[rConfig.ComponentName] = updateRequestTemplate( + requestTemplate.Data[rConfig.ComponentName].(map[string]interface{}), + propertyName, + propertyValue) } } - log.Info("Updated template - %v\n", requestTemplate.Data) + log.Info("The updated catalog item request template is %v\n", requestTemplate.Data) //Fire off a catalog item request to create a deployment. catalogRequest, err := vraClient.RequestCatalogItem(requestTemplate) if err != nil { - log.Errorf("Resource Machine Request Failed: %v", err) - return fmt.Errorf("Resource Machine Request Failed: %v", err) + return fmt.Errorf("The catalog item qequest failed with error %v", err) } _, err = waitForRequestCompletion(d, meta, catalogRequest.ID) if err != nil { return err } d.SetId(catalogRequest.ID) + log.Info("Finished creating the resource vra7_deployment with request id %s", d.Id()) return resourceVra7DeploymentRead(d, meta) } func updateRequestTemplate(templateInterface map[string]interface{}, field string, value interface{}) map[string]interface{} { - var replaced bool - templateInterface, replaced = utils.ReplaceValueInRequestTemplate(templateInterface, field, value) + replaced := utils.ReplaceValueInRequestTemplate(templateInterface, field, value) if !replaced { templateInterface["data"] = utils.AddValueToRequestTemplate(templateInterface["data"].(map[string]interface{}), field, value) @@ -215,127 +213,142 @@ func updateRequestTemplate(templateInterface map[string]interface{}, field strin return templateInterface } -// Terraform call - terraform apply // This function updates the state of a vRA 7 Deployment when changes to a Terraform file are applied. -// The update is performed on the Deployment using supported (day-2) actions. +//The update is performed on the Deployment using supported (day-2) actions. func resourceVra7DeploymentUpdate(d *schema.ResourceData, meta interface{}) error { - vraClient = meta.(*sdk.APIClient) + + log.Info("Updating the resource vra7_deployment with request id %s", d.Id()) + vraClient := meta.(*sdk.APIClient) // Get the ID of the catalog request that was used to provision this Deployment. catalogItemRequestID := d.Id() // Get client handle - p := readProviderConfiguration(d) - requestTemplate, validityErr := p.checkConfigValuesValidity(d) - if validityErr != nil { - return validityErr + p, err := readProviderConfiguration(d, vraClient) + if err != nil { + return err } - validityErr = p.checkResourceConfigValidity(requestTemplate) + _, validityErr := p.checkResourceConfigValidity(vraClient) if validityErr != nil { return validityErr } - resourceActions, err := vraClient.GetResourceActions(catalogItemRequestID) - if err != nil { - log.Errorf("Error while reading resource actions for the request %v: %v ", catalogItemRequestID, err.Error()) - return fmt.Errorf("Error while reading resource actions for the request %v: %v ", catalogItemRequestID, err.Error()) - } + // get the old and new resource_configuration data + old, new := d.GetChange("resource_configuration") + oldResourceConfigList := expandResourceConfiguration(old.(*schema.Set).List()) + newResourceConfigList := expandResourceConfiguration(new.(*schema.Set).List()) - // If any change made in resource_configuration. if d.HasChange("resource_configuration") { - for _, resources := range resourceActions.Content { - if resources.ResourceTypeRef.ID == sdk.InfrastructureVirtual { - var reconfigureEnabled bool - var reconfigureActionID string - for _, op := range resources.Operations { - if op.Name == sdk.Reconfigure { - reconfigureEnabled = true - reconfigureActionID = op.OperationID - break + for _, newResourceConfig := range newResourceConfigList { + oldResourceConfig := GetResourceConfigurationByComponent(oldResourceConfigList, newResourceConfig.ComponentName) + if oldResourceConfig.ComponentName != "" { + deploymentResourceActions, _ := vraClient.GetResourceActions(p.DeploymentID) + deploymentActionsMap := GetActionNameIDMap(deploymentResourceActions) + if oldResourceConfig.Cluster != newResourceConfig.Cluster { + if oldResourceConfig.Cluster < newResourceConfig.Cluster { + // Scale Out Day 2 operation + scaleOutActionID := deploymentActionsMap[sdk.ScaleOut] + // get the action template for scale out + resourceActionTemplate, err := vraClient.GetResourceActionTemplate(p.DeploymentID, scaleOutActionID) + if err != nil { + return err + } + // get the map from the action template corresponding to the key which is the component name + actionTemplateDataMap := GetActionTemplateDataByComponent(resourceActionTemplate.Data, newResourceConfig.ComponentName) + // update the template with the new cluster size + _ = utils.ReplaceValueInRequestTemplate( + actionTemplateDataMap, "_cluster", newResourceConfig.Cluster) + requestID, err := vraClient.PostResourceAction(p.DeploymentID, scaleOutActionID, resourceActionTemplate) + if err != nil { + log.Errorf("The scale out request failed with error: %v ", err) + return err + } + log.Info("The Scale Out operation for the component %v has been submitted", newResourceConfig.ComponentName) + _, err = waitForRequestCompletion(d, meta, requestID) + if err != nil { + log.Errorf("The scale out request failed with error: %v ", err) + return err + } + } else if oldResourceConfig.Cluster > newResourceConfig.Cluster { + // Scale In Day 2 operation + scaleInActionID := deploymentActionsMap[sdk.ScaleIn] + // get the action template for scale in + resourceActionTemplate, err := vraClient.GetResourceActionTemplate(p.DeploymentID, scaleInActionID) + if err != nil { + return err + } + // get the map from the action template corresponding to the key which is the component name + actionTemplateDataMap := GetActionTemplateDataByComponent(resourceActionTemplate.Data, newResourceConfig.ComponentName) + // update the template with the new cluster size + _ = utils.ReplaceValueInRequestTemplate( + actionTemplateDataMap, "_cluster", newResourceConfig.Cluster) + requestID, err := vraClient.PostResourceAction(p.DeploymentID, scaleInActionID, resourceActionTemplate) + if err != nil { + log.Errorf("The scale in request failed with error: %v ", err) + return err + } + log.Info("The Scale In operation for the component %v has been submitted", newResourceConfig.ComponentName) + _, err = waitForRequestCompletion(d, meta, requestID) + if err != nil { + log.Errorf("The scale in request failed with error: %v ", err) + return err + } } } - // if reconfigure action is not available for any resource of the deployment - // return with an error message - if !reconfigureEnabled { - return fmt.Errorf("Update is not allowed for resource %v, your entitlement has no Reconfigure action enabled", resources.ID) - } - resourceData := resources.ResourceData - entries := resourceData.Entries - for _, entry := range entries { - if entry.Key == sdk.Component { - entryValue := entry.Value - var componentName string - for k, v := range entryValue { - if k == "value" { - componentName = v.(string) + } + } + + resources, err := vraClient.GetResources(catalogItemRequestID) + if err != nil { + return fmt.Errorf("Error while getting resources for the request %v: %v ", catalogItemRequestID, err.Error()) + } + // Reconfigure Day 2 operation + for _, resource := range resources.Content { + oldResourceConfig := GetResourceByID(oldResourceConfigList, resource.ID) + if oldResourceConfig.ComponentName != "" { + vmResourceActions, _ := vraClient.GetResourceActions(oldResourceConfig.ResourceID) + vmResourceActionsMap := GetActionNameIDMap(vmResourceActions) + if vmResourceActionsMap[sdk.Reconfigure] != "" { + reconfigureActionID := vmResourceActionsMap[sdk.Reconfigure] + resourceActionTemplate, _ := vraClient.GetResourceActionTemplate(oldResourceConfig.ResourceID, reconfigureActionID) + newResourceConfig := GetResourceConfigurationByComponent(newResourceConfigList, oldResourceConfig.ComponentName) + configChanged := false + actionTemplateDataMap := resourceActionTemplate.Data + for propertyName, propertyValue := range newResourceConfig.Configuration { + if oldResourceConfig.Configuration[propertyName] != propertyValue { + _ = utils.ReplaceValueInRequestTemplate( + actionTemplateDataMap, propertyName, propertyValue) + if !configChanged { + configChanged = true } } - log.Info("Retrieving reconfigure action template for the component: %v ", componentName) - - resourceActionTemplate, err := vraClient.GetResourceActionTemplate(resources.ID, reconfigureActionID) + } + if configChanged { + requestID, err := vraClient.PostResourceAction(oldResourceConfig.ResourceID, reconfigureActionID, resourceActionTemplate) if err != nil { - log.Errorf("Error retrieving reconfigure action template for the component %v: %v ", componentName, err.Error()) - return fmt.Errorf("Error retrieving reconfigure action template for the component %v: %v ", componentName, err.Error()) - } - configChanged := false - for configKey := range p.ResourceConfiguration { - var returnFlag bool - //compare resource list (resource_name) with user configuration fields - if strings.HasPrefix(configKey, componentName+".") { - //If user_configuration contains resource_list element - // then split user configuration key into resource_name and field_name - nameList := strings.Split(configKey, componentName+".") - //actionResponseInterface := actionResponse.(map[string]interface{}) - //Function call which changes the template field values with user values - //Replace existing values with new values in resource child template - resourceActionTemplate.Data, returnFlag = utils.ReplaceValueInRequestTemplate( - resourceActionTemplate.Data, - nameList[1], - p.ResourceConfiguration[configKey]) - if returnFlag { - configChanged = true - } - } + log.Errorf("The reconfigure request failed with error: %v ", err) + return err } - oldData, _ := d.GetChange("resource_configuration") - // If template value got changed then set post call and update resource child - if configChanged { - // This request id is for the reconfigure action on this machine and - // will be used to track the status of the reconfigure request for this resource. - // It will not replace the initial catalog item request id - requestID, err := vraClient.PostResourceAction(resources.ID, reconfigureActionID, resourceActionTemplate) - if err != nil { - err = d.Set("resource_configuration", oldData) - if err != nil { - return err - } - log.Errorf("The update request failed with error: %v ", err) - return err - } - status, err := waitForRequestCompletion(d, meta, requestID) - if err != nil { - // if the update request fails, go back to the old state and return the error - if status == sdk.Failed { - setErr := d.Set("resource_configuration", oldData) - if setErr != nil { - return setErr - } - } - return err - } + log.Info("The Scale In operation for the component %v has been submitted", oldResourceConfig.ComponentName) + _, err = waitForRequestCompletion(d, meta, requestID) + if err != nil { + log.Errorf("The reconfigure request failed with error: %v ", err) + return err } } } } } } + log.Info("Finished updating the resource vra7_deployment with request id %s", d.Id()) return resourceVra7DeploymentRead(d, meta) } -// Terraform call - terraform refresh // This function retrieves the latest state of a vRA 7 deployment. Terraform updates its state based on // the information returned by this function. func resourceVra7DeploymentRead(d *schema.ResourceData, meta interface{}) error { - vraClient = meta.(*sdk.APIClient) + log.Info("Reading the resource vra7_deployment with request id %s ", d.Id()) + vraClient := meta.(*sdk.APIClient) + // Get the ID of the catalog request that was used to provision this Deployment. This id // will remain the same for this deployment across any actions on the machines like reconfigure, etc. catalogItemRequestID := d.Id() @@ -347,123 +360,148 @@ func resourceVra7DeploymentRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("The resource cannot be found") } if errTemplate != nil || len(requestResourceView.Content) == 0 { - return fmt.Errorf("Resource view failed to load: %v", errTemplate) + return fmt.Errorf("Resource view failed to load with the error %v", errTemplate) } - resourceDataMap := make(map[string]map[string]interface{}) + var resourceConfigList []sdk.ResourceConfigurationStruct for _, resource := range requestResourceView.Content { - if resource.ResourceType == sdk.InfrastructureVirtual { - resourceData := resource.ResourcesData - log.Info("The resource data map of the resource %v is: \n%v", resourceData.Component, resource.ResourcesData) - dataVals := make(map[string]interface{}) - resourceDataMap[resourceData.Component] = dataVals - dataVals[sdk.MachineCPU] = resourceData.CPU - dataVals[sdk.MachineStorage] = resourceData.Storage - dataVals[sdk.IPAddress] = resourceData.IPAddress - dataVals[sdk.MachineMemory] = resourceData.Memory - dataVals[sdk.MachineName] = resourceData.MachineName - dataVals[sdk.MachineGuestOs] = resourceData.MachineGuestOperatingSystem - dataVals[sdk.MachineBpName] = resourceData.MachineBlueprintName - dataVals[sdk.MachineType] = resourceData.MachineType - dataVals[sdk.MachineReservationName] = resourceData.MachineReservationName - dataVals[sdk.MachineInterfaceType] = resourceData.MachineInterfaceType - dataVals[sdk.MachineID] = resourceData.MachineID - dataVals[sdk.MachineGroupName] = resourceData.MachineGroupName - dataVals[sdk.MachineDestructionDate] = resourceData.MachineDestructionDate - // Handle Network Info - for idx, netDetails := range resourceData.Networks { - log.Info("The Network list value is for idx %i = %+v", idx, netDetails) - networkIndexName := "Network" + strconv.Itoa(idx) - dataVals[networkIndexName+".IPAddress"] = netDetails.NetworkAddressInfo.IPAddress - dataVals[networkIndexName+".MACAddress"] = netDetails.NetworkAddressInfo.MACAddress - dataVals[networkIndexName+".Name"] = netDetails.NetworkAddressInfo.Name + rMap := resource.(map[string]interface{}) + resourceType := rMap["resourceType"].(string) + name := rMap["name"].(string) + dateCreated := rMap["dateCreated"].(string) + lastUpdated := rMap["lastUpdated"].(string) + resourceID := rMap["resourceId"].(string) + requestID := rMap["requestId"].(string) + requestState := rMap["requestState"].(string) + + // if the resource type is VMs, update the resource_configuration attribute + if resourceType == sdk.InfrastructureVirtual { + data := rMap["data"].(map[string]interface{}) + componentName := data["Component"].(string) + parentResourceID := rMap["parentResourceId"].(string) + var resourceConfigStruct sdk.ResourceConfigurationStruct + resourceConfigStruct.Configuration = data + resourceConfigStruct.ComponentName = componentName + resourceConfigStruct.Name = name + resourceConfigStruct.DateCreated = dateCreated + resourceConfigStruct.LastUpdated = lastUpdated + resourceConfigStruct.ResourceID = resourceID + resourceConfigStruct.ResourceType = resourceType + resourceConfigStruct.RequestID = requestID + resourceConfigStruct.RequestState = requestState + resourceConfigStruct.ParentResourceID = parentResourceID + resourceConfigStruct.IPAddress = data["ip_address"].(string) + + if rMap["description"] != nil { + resourceConfigStruct.Description = rMap["description"].(string) + } + if rMap["status"] != nil { + resourceConfigStruct.Status = rMap["status"].(string) } - } - } - resourceConfiguration, _ := d.Get("resource_configuration").(map[string]interface{}) - resourceConfiguration, changed := utils.UpdateResourceConfigurationMap(resourceConfiguration, resourceDataMap) + // the cluster value is fetched from scale out action template as the resourceViews API does not return that information + deploymentResourceActions, _ := vraClient.GetResourceActions(parentResourceID) + deploymentActionsMap := GetActionNameIDMap(deploymentResourceActions) + scaleOutActionID := deploymentActionsMap["Scale Out"] + resourceActionTemplate, _ := vraClient.GetResourceActionTemplate(parentResourceID, scaleOutActionID) + actionTemplateResourceDataMap := GetActionTemplateDataByComponent(resourceActionTemplate.Data, componentName) + rDataMap := actionTemplateResourceDataMap["data"].(map[string]interface{}) + var cluster int = int(rDataMap["_cluster"].(float64)) + resourceConfigStruct.Cluster = cluster + // end + + resourceConfigList = append(resourceConfigList, resourceConfigStruct) + + } else if resourceType == sdk.DeploymentResourceType { + + leaseMap := rMap["lease"].(map[string]interface{}) + leaseStart := leaseMap["start"].(string) + leaseEnd := leaseMap["end"].(string) + d.Set("lease_start", leaseStart) + d.Set("lease_end", leaseEnd) + // the lease_days are calculated from the lease_start and lease_end dates as the resourceViews API does not return that information + startTime, _ := time.Parse(time.RFC3339, leaseStart) + endTime, _ := time.Parse(time.RFC3339, leaseEnd) + diff := endTime.Sub(startTime) + d.Set("lease_days", int(diff.Hours()/24)) + // end + + d.Set("catalog_item_id", rMap["catalogItemId"].(string)) + d.Set("catalog_item_name", rMap["catalogItemLabel"].(string)) + d.Set("deployment_id", resourceID) + d.Set("date_created", dateCreated) + d.Set("last_updated", lastUpdated) + d.Set("tenant_id", rMap["tenantId"].(string)) + d.Set("owners", rMap["owners"].([]interface{})) + d.Set("name", name) + d.Set("businessgroup_id", rMap["businessGroupId"].(string)) + if rMap["description"] != nil { + d.Set("description", rMap["description"].(string)) + } - if changed { - setError := d.Set("resource_configuration", resourceConfiguration) - if setError != nil { - return fmt.Errorf(setError.Error()) + if rMap["status"] != nil { + d.Set("request_status", rMap["status"].(string)) + } } } + if err := d.Set("resource_configuration", flattenResourceConfigurations(resourceConfigList)); err != nil { + return fmt.Errorf("error setting resource configuration - error: %v", err) + } + + log.Info("Finished reading the resource vra7_deployment with request id %s", d.Id()) return nil } //Function use - To delete resources which are created by terraform and present in state file -//Terraform call - terraform destroy func resourceVra7DeploymentDelete(d *schema.ResourceData, meta interface{}) error { - vraClient = meta.(*sdk.APIClient) - // Get client handle - p := readProviderConfiguration(d) - //Get requester machine ID from schema.dataresource - catalogItemRequestID := d.Id() + log.Info("Deleting the resource vra7_deployment with request id %s", d.Id()) + vraClient := meta.(*sdk.APIClient) + // Throw an error if request ID has no value or empty value if len(d.Id()) == 0 { return fmt.Errorf("Resource not found") } - log.Info("Calling delete resource for the request id %v ", catalogItemRequestID) - - resourceView, err := vraClient.GetRequestResourceView(catalogItemRequestID) - if err != nil { - return fmt.Errorf("Resource view failed to load: %v", err) - } - if len(resourceView.Content) == 0 { - //If resource gets deleted then unset the resource ID from state file - d.SetId("") - return fmt.Errorf("The resource cannot be found") - } - resourceActions, err := vraClient.GetResourceActions(catalogItemRequestID) + p, err := readProviderConfiguration(d, vraClient) if err != nil { return err } - for _, resources := range resourceActions.Content { - if resources.ResourceTypeRef.ID == sdk.DeploymentResourceType { - deploymentName := resources.Name - var destroyEnabled bool - var destroyActionID string - for _, op := range resources.Operations { - if (p.DeploymentDestroy && op.Name == sdk.DeploymentDestroy) || (!p.DeploymentDestroy && op.Name == sdk.Destroy) { - destroyEnabled = true - destroyActionID = op.OperationID - break - } - } - // if destroy deployment action is not available for the deployment - // return with an error message - if !destroyEnabled { - return fmt.Errorf("The deployment %v cannot be destroyed, your entitlement has no Destroy Deployment action enabled", deploymentName) - } - resourceActionTemplate, err := vraClient.GetResourceActionTemplate(resources.ID, destroyActionID) - if err != nil { - log.Errorf(DestroyActionTemplateError, deploymentName, err.Error()) - return fmt.Errorf(DestroyActionTemplateError, deploymentName, err.Error()) - } - requestID, err := vraClient.PostResourceAction(resources.ID, destroyActionID, resourceActionTemplate) - if err != nil { - log.Errorf("The destroy deployment request failed with error: %v ", err) - return err - } - status, err := waitForRequestCompletion(d, meta, requestID) - if err != nil { - if status == sdk.Successful { - d.SetId("") - } - } + deploymentID := d.Get("deployment_id").(string) + deploymentResourceActions, _ := vraClient.GetResourceActions(deploymentID) + deploymentActionsMap := GetActionNameIDMap(deploymentResourceActions) + destroyActionID := deploymentActionsMap["Destroy"] + if p.DeploymentDestroy && destroyActionID != "" { + resourceActionTemplate, _ := vraClient.GetResourceActionTemplate(deploymentID, destroyActionID) + requestID, err := vraClient.PostResourceAction(deploymentID, destroyActionID, resourceActionTemplate) + if err != nil { + log.Errorf("The destroy request failed with error: %v ", err) + return err + } + status, err := waitForRequestCompletion(d, meta, requestID) + if err != nil { + log.Errorf("The destroy request failed with error: %v ", err) + return err + } + if status == sdk.Successful { + d.SetId("") } } + log.Info("Finished destroying the resource vra7_deployment with request id %s", d.Id()) return nil } // check if the resource configuration is valid in the terraform config file -func (p *ProviderSchema) checkResourceConfigValidity(requestTemplate *sdk.CatalogItemRequestTemplate) error { +func (p *ProviderSchema) checkResourceConfigValidity(client *sdk.APIClient) (*sdk.CatalogItemRequestTemplate, error) { log.Info("Checking if the terraform config file is valid") + // Get request template for catalog item. + requestTemplate, err := client.GetCatalogItemRequestTemplate(p.CatalogItemID) + if err != nil { + return nil, err + } + log.Info("The request template data corresponding to the catalog item %v is: \n %v\n", p.CatalogItemID, requestTemplate.Data) + // Get all component names in the blueprint corresponding to the catalog item. componentSet := make(map[string]bool) for field := range requestTemplate.Data { @@ -474,121 +512,96 @@ func (p *ProviderSchema) checkResourceConfigValidity(requestTemplate *sdk.Catalo log.Info("The component name(s) in the blueprint corresponding to the catalog item: %v\n", componentSet) var invalidKeys []string - // check if the keys in the resourceConfiguration map exists in the componentSet - // if the key in config is machine1.vsphere.custom.location, match every string after each dot - // until a matching string is found in componentSet. - // If found, it's a valid key else the component name is invalid - for k := range p.ResourceConfiguration { - var key = k - var isValid bool - for strings.LastIndex(key, ".") != -1 { - lastIndex := strings.LastIndex(key, ".") - key = key[0:lastIndex] - if _, ok := componentSet[key]; ok { - log.Info("The component name %s in the terraform config file is valid ", key) - isValid = true - break - } - } - if !isValid { - invalidKeys = append(invalidKeys, k) + // check if the component of resource_configuration map exists in the componentSet + // retrieved from catalog item request template + for _, k := range p.ResourceConfiguration { + if _, ok := componentSet[k.ComponentName]; !ok { + invalidKeys = append(invalidKeys, k.ComponentName) } } // there are invalid resource config keys in the terraform config file, abort and throw an error if len(invalidKeys) > 0 { - log.Error("The resource_configuration in the config file has invalid component name(s): %v ", strings.Join(invalidKeys, ", ")) - return fmt.Errorf(ConfigInvalidError, strings.Join(invalidKeys, ", ")) + return nil, fmt.Errorf(ConfigInvalidError, strings.Join(invalidKeys, ", ")) } - return nil + + return requestTemplate, nil } // check if the values provided in the config file are valid and set // them in the resource schema. Requires to call APIs -func (p *ProviderSchema) checkConfigValuesValidity(d *schema.ResourceData) (*sdk.CatalogItemRequestTemplate, error) { - // // If catalog_name and catalog_id both not provided then return an error - if len(p.CatalogItemName) <= 0 && len(p.CatalogItemID) <= 0 { - return nil, fmt.Errorf("Either catalog_name or catalog_id should be present in given configuration") - } - - var catalogItemIDFromName string - var catalogItemNameFromID string - var err error - // if catalog item id is provided, fetch the catalog item name - if len(p.CatalogItemName) > 0 { - catalogItemIDFromName, err = vraClient.ReadCatalogItemByName(p.CatalogItemName) - if err != nil || catalogItemIDFromName == "" { - return nil, fmt.Errorf("Error in finding catalog item id corresponding to the catlog item name %v: \n %v", p.CatalogItemName, err) - } - log.Info("The catalog item id provided in the config is %v\n", catalogItemIDFromName) - } +func checkConfigValuesValidity(d *schema.ResourceData) error { - // if catalog item name is provided, fetch the catalog item id - if len(p.CatalogItemID) > 0 { // else if both are provided and matches or just id is provided, use id - catalogItemNameFromID, err = vraClient.ReadCatalogItemNameByID(p.CatalogItemID) - if err != nil || catalogItemNameFromID == "" { - return nil, fmt.Errorf("Error in finding catalog item name corresponding to the catlog item id %v: \n %v", p.CatalogItemID, err) - } - log.Info("The catalog item name corresponding to the catalog item id in the config is: %v\n", catalogItemNameFromID) + catalogItemName := d.Get("catalog_item_name").(string) + catalogItemID := d.Get("catalog_item_id").(string) + businessgroupName := d.Get("businessgroup_name").(string) + businessgroupID := d.Get("businessgroup_id").(string) + + // If catalogItemID and catalogItemName both not provided then return an error + if catalogItemID == "" && catalogItemName == "" { + return fmt.Errorf("Provide either a catalog_item_name or a catalog_item_id in the configuration") } - // if both catalog item name and id are provided but does not belong to the same catalog item, throw an error - if len(p.CatalogItemName) > 0 && len(p.CatalogItemID) > 0 && (catalogItemIDFromName != p.CatalogItemID || catalogItemNameFromID != p.CatalogItemName) { - log.Error(CatalogItemIDNameNotMatchingErr, p.CatalogItemName, p.CatalogItemID) - return nil, fmt.Errorf(CatalogItemIDNameNotMatchingErr, p.CatalogItemName, p.CatalogItemID) - } else if len(p.CatalogItemID) > 0 { // else if both are provided and matches or just id is provided, use id - d.Set("catalog_item_id", p.CatalogItemID) - d.Set("catalog_item_name", catalogItemNameFromID) - } else if len(p.CatalogItemName) > 0 { // else if name is provided, use the id fetched from the name - d.Set("catalog_item_id", catalogItemIDFromName) - d.Set("catalog_item_name", p.CatalogItemName) + // If both catalog_item_name and catalogItemName return an error + if catalogItemID != "" && catalogItemName != "" { + return fmt.Errorf("Provide either a catalog_item_name or a catalog_item_id in the configuration") } - // update the catalogItemID var with the updated id - p.CatalogItemID = d.Get("catalog_item_id").(string) + // If businessgroupID and businessgroupName both not provided then return an error + if businessgroupID == "" && businessgroupName == "" { + return fmt.Errorf("Provide either a businessgroup_id or a businessgroup_name in the configuration") + } - // Get request template for catalog item. - requestTemplate, err := vraClient.GetCatalogItemRequestTemplate(p.CatalogItemID) - if err != nil { - return nil, err + // If both businessgroupID and businessgroupName return an error + if businessgroupID != "" && businessgroupName != "" { + return fmt.Errorf("Provide either a businessgroup_id or a businessgroup_name in the configuration") } - log.Info("The request template data corresponding to the catalog item %v is: \n %v\n", p.CatalogItemID, requestTemplate.Data) + return nil +} - updateRequestTemplateFromDeploymentConfiguration(p.DeploymentConfiguration, requestTemplate) +// read the config file +func readProviderConfiguration(d *schema.ResourceData, vraClient *sdk.APIClient) (*ProviderSchema, error) { - // get the business group id from name - var businessGroupIDFromName string - if len(p.BusinessGroupName) > 0 { - businessGroupIDFromName, err = vraClient.GetBusinessGroupID(p.BusinessGroupName, vraClient.Tenant) - if err != nil || businessGroupIDFromName == "" { - return nil, err - } + log.Info("Reading the provider configuration data.....") + providerSchema := ProviderSchema{ + CatalogItemName: strings.TrimSpace(d.Get("catalog_item_name").(string)), + CatalogItemID: strings.TrimSpace(d.Get("catalog_item_id").(string)), + Description: strings.TrimSpace(d.Get("description").(string)), + Reasons: strings.TrimSpace(d.Get("reasons").(string)), + BusinessGroupName: strings.TrimSpace(d.Get("businessgroup_name").(string)), + BusinessGroupID: strings.TrimSpace(d.Get("businessgroup_id").(string)), + Lease: d.Get("lease_days").(int), + DeploymentID: strings.TrimSpace(d.Get("deployment_id").(string)), + WaitTimeout: d.Get("wait_timeout").(int) * 60, + ResourceConfiguration: expandResourceConfiguration(d.Get("resource_configuration").(*schema.Set).List()), + DeploymentDestroy: d.Get("deployment_destroy").(bool), + DeploymentConfiguration: d.Get("deployment_configuration").(map[string]interface{}), } - //if both business group name and id are provided but does not belong to the same business group, throw an error - if len(p.BusinessGroupName) > 0 && len(p.BusinessGroupID) > 0 && businessGroupIDFromName != p.BusinessGroupID { - log.Error(BusinessGroupIDNameNotMatchingErr, p.BusinessGroupName, p.BusinessGroupID) - return nil, fmt.Errorf(BusinessGroupIDNameNotMatchingErr, p.BusinessGroupName, p.BusinessGroupID) - } else if len(p.BusinessGroupID) > 0 { // else if both are provided and matches or just id is provided, use id - log.Info("Setting business group id %s ", p.BusinessGroupID) - requestTemplate.BusinessGroupID = p.BusinessGroupID - } else if len(p.BusinessGroupName) > 0 { // else if name is provided, use the id fetched from the name - log.Info("Setting business group id %s for the group %s ", businessGroupIDFromName, p.BusinessGroupName) - requestTemplate.BusinessGroupID = businessGroupIDFromName + // if catalog item name is provided, fetch the catalog item id + if len(providerSchema.CatalogItemName) > 0 { + id, err := vraClient.ReadCatalogItemByName(providerSchema.CatalogItemName) + if err != nil { + return &providerSchema, err + } + providerSchema.CatalogItemID = id } - return requestTemplate, nil -} -func updateRequestTemplateFromDeploymentConfiguration(deploymentConfiguration map[string]interface{}, requestTemplate *sdk.CatalogItemRequestTemplate) { - for field := range deploymentConfiguration { - value := deploymentConfiguration[field] - - requestTemplate.Data[field] = utils.UnmarshalJsonStringIfNecessary(field, value) + // get the business group id from name + if len(providerSchema.BusinessGroupName) > 0 { + id, err := vraClient.GetBusinessGroupID(providerSchema.BusinessGroupName, vraClient.Tenant) + if err != nil { + return &providerSchema, err + } + providerSchema.BusinessGroupID = id } + + log.Info("The values provided in the TF config file is: \n %v ", providerSchema) + return &providerSchema, nil } -// check the request status on apply and update +// check the request status on apply update and destroy func waitForRequestCompletion(d *schema.ResourceData, meta interface{}, requestID string) (string, error) { - + vraClient := meta.(*sdk.APIClient) waitTimeout := d.Get("wait_timeout").(int) * 60 sleepFor := 30 status := "" @@ -603,7 +616,6 @@ func waitForRequestCompletion(d *schema.ResourceData, meta interface{}, requestI log.Info("Request is SUCCESSFUL.") return sdk.Successful, nil } else if status == sdk.Failed { - log.Error("Request Failed with message %v ", reqestStatusView.RequestCompletion.CompletionDetails) return sdk.Failed, fmt.Errorf("Request failed \n %v ", reqestStatusView.RequestCompletion.CompletionDetails) } else if status == sdk.InProgress { log.Info("The request is still IN PROGRESS.") @@ -616,24 +628,45 @@ func waitForRequestCompletion(d *schema.ResourceData, meta interface{}, requestI return "", fmt.Errorf("Request has timed out with status %s. \nRun terraform refresh to get the latest state of your request", status) } -// read the config file -func readProviderConfiguration(d *schema.ResourceData) *ProviderSchema { +// GetActionTemplateDataByComponent return the map corresponding the component name in the template data +func GetActionTemplateDataByComponent(actionTemplate map[string]interface{}, componentName string) map[string]interface{} { + actionTemplateDataByComponent := make(map[string]interface{}) + for key, value := range actionTemplate { + if key == componentName && reflect.ValueOf(value).Kind() == reflect.Map { + actionTemplateDataByComponent = value.(map[string]interface{}) + break + } + } + return actionTemplateDataByComponent +} - log.Info("Reading the provider configuration data.....") - providerSchema := ProviderSchema{ - CatalogItemName: strings.TrimSpace(d.Get("catalog_item_name").(string)), - CatalogItemID: strings.TrimSpace(d.Get("catalog_item_id").(string)), - Description: strings.TrimSpace(d.Get("description").(string)), - Reasons: strings.TrimSpace(d.Get("reasons").(string)), - BusinessGroupName: strings.TrimSpace(d.Get("businessgroup_name").(string)), - BusinessGroupID: strings.TrimSpace(d.Get("businessgroup_id").(string)), - WaitTimeout: d.Get("wait_timeout").(int) * 60, - FailedMessage: strings.TrimSpace(d.Get("failed_message").(string)), - ResourceConfiguration: d.Get("resource_configuration").(map[string]interface{}), - DeploymentConfiguration: d.Get("deployment_configuration").(map[string]interface{}), - DeploymentDestroy: d.Get("deployment_destroy").(bool), +// GetResourceConfigurationByComponent returns the resource_configuration corresponding the component +func GetResourceConfigurationByComponent(resourceConfigurationList []sdk.ResourceConfigurationStruct, component string) sdk.ResourceConfigurationStruct { + var resourceConfig sdk.ResourceConfigurationStruct + for _, rConfig := range resourceConfigurationList { + if rConfig.ComponentName == component { + resourceConfig = rConfig + } } + return resourceConfig +} - log.Info("The values provided in the TF config file is: \n %v ", providerSchema) - return &providerSchema +// GetActionNameIDMap returns a map of Action name and id +func GetActionNameIDMap(resourceActions []sdk.Operation) map[string]string { + actionNameIDMap := make(map[string]string) + for _, action := range resourceActions { + actionNameIDMap[action.Name] = action.ID + } + return actionNameIDMap +} + +// GetResourceByID return the resoirce config struct object filtered by ID +func GetResourceByID(resourceConfigStructList []sdk.ResourceConfigurationStruct, resourceID string) sdk.ResourceConfigurationStruct { + var resourceConfigStruct sdk.ResourceConfigurationStruct + for _, resourceStruct := range resourceConfigStructList { + if resourceStruct.ResourceID == resourceID { + resourceConfigStruct = resourceStruct + } + } + return resourceConfigStruct } diff --git a/vra7/resource_vra7_deployment_test.go b/vra7/resource_vra7_deployment_test.go index e95bcd7e..4b25dca8 100644 --- a/vra7/resource_vra7_deployment_test.go +++ b/vra7/resource_vra7_deployment_test.go @@ -1,13 +1,13 @@ package vra7 import ( - "encoding/json" "fmt" "strconv" "strings" "testing" "github.com/hashicorp/terraform/terraform" + httpmock "gopkg.in/jarcoal/httpmock.v1" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" @@ -23,78 +23,74 @@ func init() { func TestConfigValidityFunction(t *testing.T) { - mockRequestTemplate, err := GetMockRequestTemplate() - utils.AssertNilError(t, err) + httpmock.ActivateNonDefault(client.Client) + defer httpmock.DeactivateAndReset() - // a resource_configuration map is created with valid components - // all combinations of components name and properties are created with dots - mockConfigResourceMap := make(map[string]interface{}) - mockConfigResourceMap["mock.test.machine1.cpu"] = 2 - mockConfigResourceMap["mock.test.machine1.mock.storage"] = 8 + catalogItemID := "dhbh-jhdv-ghdv-dhvdd" - resourceSchema := resourceVra7Deployment().Schema + path := fmt.Sprintf(sdk.RequestTemplateAPI, catalogItemID) + url := client.BuildEncodedURL(path, nil) - resourceDataMap := map[string]interface{}{ - "catalog_item_id": "abcdefghijklmn", - "resource_configuration": mockConfigResourceMap, - } + httpmock.RegisterResponder("GET", url, + httpmock.NewStringResponder(200, mockRequestTemplate)) - mockResourceData := schema.TestResourceDataRaw(t, resourceSchema, resourceDataMap) + resourceConfigList := make([]map[string]interface{}, 0) + resourceConfigurationObject1 := make(map[string]interface{}) - p := readProviderConfiguration(mockResourceData) + configMap := make(map[string]interface{}) + configMap["cpu"] = 2 + configMap["memory"] = 1024 - readProviderConfiguration(mockResourceData) - err = p.checkResourceConfigValidity(mockRequestTemplate) - utils.AssertNilError(t, err) + resourceConfigurationObject1["configuration"] = configMap + resourceConfigurationObject1["component_name"] = "mock.test.machine1" + + resourceConfigList = append(resourceConfigList, resourceConfigurationObject1) - mockConfigResourceMap["machine2.mock.cpu"] = 2 - mockConfigResourceMap["machine2.storage"] = 2 + resourceSchema := resourceVra7Deployment().Schema - resourceDataMap = map[string]interface{}{ - "catalog_item_id": "abcdefghijklmn", - "resource_configuration": mockConfigResourceMap, + resourceSchemaMap := map[string]interface{}{ + "catalog_item_id": catalogItemID, + "wait_timeout": 20, + "resource_configuration": resourceConfigList, } - mockResourceData = schema.TestResourceDataRaw(t, resourceSchema, resourceDataMap) - readProviderConfiguration(mockResourceData) + mockResourceData := schema.TestResourceDataRaw(t, resourceSchema, resourceSchemaMap) + + p, _ := readProviderConfiguration(mockResourceData, &client) - err = p.checkResourceConfigValidity(mockRequestTemplate) + mockRequestTemplateStruct, err := p.checkResourceConfigValidity(&client) utils.AssertNilError(t, err) + utils.AssertNotNil(t, mockRequestTemplateStruct) + utils.AssertNotNil(t, mockRequestTemplateStruct.Data["mock.test.machine1"]) + utils.AssertEqualsString(t, catalogItemID, mockRequestTemplateStruct.CatalogItemID) + + resourceConfigurationObject2 := make(map[string]interface{}) + resourceConfigurationObject2["configuration"] = configMap + resourceConfigurationObject2["component_name"] = "mock.test.machine2" - mockConfigResourceMap["mock.machine3.vSphere.mock.cpu"] = 2 - resourceDataMap = map[string]interface{}{ - "catalog_item_id": "abcdefghijklmn", - "resource_configuration": mockConfigResourceMap, + resourceConfigList = append(resourceConfigList, resourceConfigurationObject2) + + resourceSchemaMap = map[string]interface{}{ + "catalog_item_id": catalogItemID, + "resource_configuration": resourceConfigList, } - mockResourceData = schema.TestResourceDataRaw(t, resourceSchema, resourceDataMap) - p = readProviderConfiguration(mockResourceData) + mockResourceData = schema.TestResourceDataRaw(t, resourceSchema, resourceSchemaMap) + p, _ = readProviderConfiguration(mockResourceData, &client) var mockInvalidKeys []string - mockInvalidKeys = append(mockInvalidKeys, "mock.machine3.vSphere.mock.cpu") + mockInvalidKeys = append(mockInvalidKeys, "mock.test.machine2") validityErr := fmt.Sprintf(ConfigInvalidError, strings.Join(mockInvalidKeys, ", ")) - err = p.checkResourceConfigValidity(mockRequestTemplate) - // this should throw an error as none of the string combinations (mock, mock.machine3, mock.machine3.vsphere, etc) - // matches the component names(mock.test.machine1 and machine2) in the request template + mockRequestTemplateStruct, err = p.checkResourceConfigValidity(&client) + // this should throw an error mock.test.machine2 does not match + // with the component names(mock.test.machine1 and machine2) in the request template utils.AssertNotNilError(t, err) utils.AssertEqualsString(t, validityErr, err.Error()) + utils.AssertNil(t, mockRequestTemplateStruct) } -// creates a mock request template from a request template template json file -func GetMockRequestTemplate() (*sdk.CatalogItemRequestTemplate, error) { - - mockRequestTemplateStruct := sdk.CatalogItemRequestTemplate{} - err := json.Unmarshal([]byte(mockRequestTemplate), &mockRequestTemplateStruct) - if err != nil { - return nil, err - } - - return &mockRequestTemplateStruct, nil - -} - -func TestAccVra7DeploymentCreate_basic(t *testing.T) { +func TestAccVra7Deployment(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -103,95 +99,25 @@ func TestAccVra7DeploymentCreate_basic(t *testing.T) { { Config: testAccCheckVra7DeploymentConfig(), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckVra7DeploymentExists("vra7_deployment.my_vra7_deployment"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "description", "Test deployment"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "reasons", "Testing the vRA 7 Terraform plugin"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "businessgroup_name", "Development"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration.%", "2"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration._leaseDays", "15"), + testAccCheckVra7DeploymentExists("vra7_deployment.this"), resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration.deployment_property", "custom deployment property"), + "vra7_deployment.this", "description", "Terraform deployment"), resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration.businessGroups", "[\"bg1\"]"), + "vra7_deployment.this", "reasons", "Testing the vRA 7 Terraform provider"), resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.%", "3"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.vSphereVM1.cpu", "1"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.vSphereVM1.memory", "2048"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.vSphereVM1.machine_property", "machine custom property"), - ), - }, - }, - }) -} - -func TestAccVra7DeploymentUpdate_basic(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckVra7DeploymentDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCheckVra7DeploymentConfig(), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckVra7DeploymentExists("vra7_deployment.my_vra7_deployment"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "description", "Test deployment"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "reasons", "Testing the vRA 7 Terraform plugin"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "businessgroup_name", "Development"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration.%", "2"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration._leaseDays", "15"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration.deployment_property", "custom deployment property"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration.businessGroups", "[\"bg1\"]"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.%", "3"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.vSphereVM1.cpu", "1"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.vSphereVM1.memory", "2048"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.vSphereVM1.machine_property", "machine custom property"), + "vra7_deployment.this", "businessgroup_name", "Terraform-BG"), ), }, { Config: testAccCheckVra7DeploymentUpdateConfig(), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckVra7DeploymentExists("vra7_deployment.my_vra7_deployment"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "description", "Test deployment"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "reasons", "Testing the vRA 7 Terraform plugin"), + testAccCheckVra7DeploymentExists("vra7_deployment.this"), resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "businessgroup_name", "Development"), + "vra7_deployment.this", "description", "Terraform deployment"), resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration.%", "2"), + "vra7_deployment.this", "reasons", "Testing the vRA 7 Terraform provider"), resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration._leaseDays", "15"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration.deployment_property", "updated custom deployment property"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "deployment_configuration.businessGroups", "[\"bg1\"]"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.%", "3"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.vSphereVM1.cpu", "1"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.vSphereVM1.memory", "1024"), - resource.TestCheckResourceAttr( - "vra7_deployment.my_vra7_deployment", "resource_configuration.vSphereVM1.machine_property", "updated machine custom property"), + "vra7_deployment.this", "businessgroup_name", "Terraform-BG"), ), }, }, @@ -204,11 +130,9 @@ func testAccCheckVra7DeploymentExists(n string) resource.TestCheckFunc { if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { return fmt.Errorf("No resource request ID is set") } - return nil } } @@ -218,7 +142,6 @@ func testAccCheckVra7DeploymentDestroy(s *terraform.State) error { if rs.Type != "vra7_deployment" { continue } - _, err := client.GetRequestResourceView(rs.Primary.ID) if err == nil { return err @@ -227,52 +150,71 @@ func testAccCheckVra7DeploymentDestroy(s *terraform.State) error { return nil } -func testAccCheckVra7DeploymentUpdateConfig() string { - return fmt.Sprintf(` -resource "vra7_deployment" "my_vra7_deployment" { -count = 1 -catalog_item_name = "Basic Single Machine" -description = "Test deployment" -reasons = "Testing the vRA 7 Terraform plugin" - -deployment_configuration = { - _leaseDays = "15" - deployment_property = "updated custom deployment property" - businessGroups = << EOF - ["bg1"] - EOF -} -resource_configuration = { - "vSphereVM1.cpu" = 1 - "vSphereVM1.memory" = 1024 - "vSphereVM1.machine_property" = "updated machine custom property" -} -wait_timeout = 20 -businessgroup_name = "Development" +func testAccCheckVra7DeploymentConfig() string { + return fmt.Sprint(` +resource "vra7_deployment" "this" { + count = 1 + catalog_item_name = "Terraform-Simple-BP" + description = "Terraform deployment" + reasons = "Testing the vRA 7 Terraform provider" + lease_days = 20 + deployment_configuration = { + "BPCustomProperty" = "custom deployment property" + } + + resource_configuration { + component_name = "vSphere1" + cluster = 2 + configuration = { + cpu = 2 + memory = 2048 + vSphere1CustomProperty = "custom machine property" + } + } + + resource_configuration { + cluster = 2 + component_name = "vSphere2" + configuration = { + cpu = 3 + memory = 2048 + } + } + wait_timeout = 20 + businessgroup_name = "Terraform-BG" }`) } -func testAccCheckVra7DeploymentConfig() string { - return fmt.Sprintf(` -resource "vra7_deployment" "my_vra7_deployment" { -count = 1 -catalog_item_name = "Basic Single Machine" -description = "Test deployment" -reasons = "Testing the vRA 7 Terraform plugin" - -deployment_configuration = { - _leaseDays = "15" - deployment_property = "custom deployment property" - businessGroups = << EOF - ["bg1"] - EOF -} -resource_configuration = { - "vSphereVM1.cpu" = 1 - "vSphereVM1.memory" = 2048 - "vSphereVM1.machine_property" = "machine custom property" -} -wait_timeout = 20 -businessgroup_name = "Development" +func testAccCheckVra7DeploymentUpdateConfig() string { + return fmt.Sprint(` +resource "vra7_deployment" "this" { + count = 1 + catalog_item_name = "Terraform-Simple-BP" + description = "Terraform deployment" + reasons = "Testing the vRA 7 Terraform provider" + lease_days = 20 + deployment_configuration = { + "BPCustomProperty" = "custom deployment property" + } + + resource_configuration { + component_name = "vSphere1" + cluster = 1 + configuration = { + cpu = 4 + memory = 2048 + vSphere1CustomProperty = "custom machine property" + } + } + resource_configuration { + cluster = 1 + component_name = "vSphere2" + configuration = { + cpu = 2 + memory = 2048 + } + } + wait_timeout = 20 + businessgroup_name = "Terraform-BG" }`) } diff --git a/website/docs/r/deployment.html.markdown b/website/docs/r/deployment.html.markdown index e2f3f205..7aad18e6 100644 --- a/website/docs/r/deployment.html.markdown +++ b/website/docs/r/deployment.html.markdown @@ -17,24 +17,41 @@ Provides a VMware vRA7 deployment resource. This can be used to deploy vRA7 cata You can refer to the sample blueprint ([here](https://github.com/terraform-providers/terraform-provider-vra7/tree/master/website/docs/r)) to understand how it is translated to following the terraform config ```hcl -resource "vra7_deployment" "my_vra7_deployment" { - count = 1 - catalog_item_name = "Basic Single Machine" - description = "Test deployment" - reasons = "Testing the vRA 7 Terraform plugin" +resource "vra7_deployment" "this" { + count = 1 + catalog_item_name = "CentOS 7.0 x64" + description = "this description" + reason = "this reason" + lease_days = 10 deployment_configuration = { - _leaseDays = "15" - _number_of_instances = 2 - deployment_property = "custom deployment property" + "blueprint_custom_property" = "This is a blueprint custom property" + } + + resource_configuration { + component_name = "Linux 1" + cluster = 2 + configuration = { + cpu = 2 + memory = 2048 + custom_property = "VM custom property" + security_tag = <