Skip to content

Commit

Permalink
Allow Change Owner action on vRA deployment resource
Browse files Browse the repository at this point in the history
This change introduces the ability to change the owner of a deployment
after the deployment is requested and successful.

Partially fixes #290.

Signed-off-by: Deepak Mettem <[email protected]>
  • Loading branch information
dmettem committed Jan 25, 2021
1 parent ed1e4cd commit fd70483
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 46 deletions.
2 changes: 2 additions & 0 deletions examples/deployment/blueprint/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ resource "vra_deployment" "this" {
name = var.deployment_name
description = "Deployed from vRA provider for Terraform."

owner = var.owner

blueprint_id = data.vra_blueprint.this.id
blueprint_version = var.blueprint_version
project_id = data.vra_project.this.id
Expand Down
1 change: 1 addition & 0 deletions examples/deployment/blueprint/terraform.tfvars.sample
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ project_name = ""
blueprint_name = ""
blueprint_version = ""
deployment_name = ""
owner = ""
2 changes: 2 additions & 0 deletions examples/deployment/blueprint/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ variable "blueprint_version" {

variable "deployment_name" {
}

variable "owner" {}
7 changes: 6 additions & 1 deletion examples/deployment/blueprint/versions.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@

terraform {
required_version = ">= 0.12"
required_version = ">= 0.13"
required_providers {
vra = {
source = "vmware/vra"
}
}
}
10 changes: 10 additions & 0 deletions vra/data_source_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ func dataSourceDeployment() *schema.Resource {
Optional: true,
Computed: true,
},
"org_id": {
Type: schema.TypeString,
Computed: true,
},
"owner": {
Type: schema.TypeString,
Optional: true,
},
"project": resourceReferenceSchema(),
"project_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -147,6 +155,8 @@ func dataSourceDeploymentRead(d *schema.ResourceData, m interface{}) error {
d.Set("last_updated_by", deployment.LastUpdatedBy)
d.Set("lease_expire_at", deployment.LeaseExpireAt)
d.Set("name", deployment.Name)
d.Set("org_id", deployment.OrgID)
d.Set("owner", deployment.OwnedBy)
d.Set("project_id", deployment.ProjectID)
d.Set("status", deployment.Status)

Expand Down
217 changes: 173 additions & 44 deletions vra/resource_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ import (
"time"
)

const (
ChangeOwnerDeploymentActionName = "ChangeOwner"
ChangeLeaseDeploymentActionName = "ChangeLease"
EditTagsDeploymentActionName = "EditTags"
PowerOffDeploymentActionName = "PowerOff"
PowerOnDeploymentActionName = "PowerOn"
UpdateDeploymentActionName = "update"
)

func resourceDeployment() *schema.Resource {
return &schema.Resource{
Create: resourceDeploymentCreate,
Expand Down Expand Up @@ -113,12 +122,20 @@ func resourceDeployment() *schema.Resource {
},
"lease_expire_at": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"name": {
Type: schema.TypeString,
Required: true,
},
"org_id": {
Type: schema.TypeString,
Computed: true,
},
"owner": {
Type: schema.TypeString,
Optional: true,
},
"project": resourceReferenceSchema(),
"project_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -369,6 +386,8 @@ func resourceDeploymentRead(d *schema.ResourceData, m interface{}) error {
d.Set("last_updated_by", deployment.LastUpdatedBy)
d.Set("lease_expire_at", deployment.LeaseExpireAt)
d.Set("name", deployment.Name)
d.Set("org_id", deployment.OrgID)
d.Set("owner", deployment.OwnedBy)

if err := d.Set("project", flattenResourceReference(deployment.Project)); err != nil {
return fmt.Errorf("error setting project in deployment - error: %#v", err)
Expand All @@ -386,46 +405,6 @@ func resourceDeploymentRead(d *schema.ResourceData, m interface{}) error {
return nil
}

// Gets the inputs and their types as map[string]string
func getInputTypesMap(d *schema.ResourceData, apiClient *client.MulticloudIaaS) map[string]string {
inputTypesMap := make(map[string]string)

if _, ok := d.GetOk("inputs"); !ok {
return inputTypesMap
}

// Get blueprint_id and catalog_item_id
blueprintID, catalogItemID := "", ""
if v, ok := d.GetOk("blueprint_id"); ok {
blueprintID = v.(string)
}

if v, ok := d.GetOk("catalog_item_id"); ok {
catalogItemID = v.(string)
}

if catalogItemID != "" {
// Get the catalog item inputs and their types
catalogItemVersion := ""
if v, ok := d.GetOk("catalog_item_version"); ok {
catalogItemVersion = v.(string)
}

inputTypesMap, _ = getCatalogItemInputTypesMap(apiClient, catalogItemID, catalogItemVersion)
} else if blueprintID != "" {
// Get the blueprint inputs and their types
blueprintVersion := ""
if v, ok := d.GetOk("blueprint_version"); ok {
blueprintVersion = v.(string)
}

inputTypesMap, _ = getBlueprintInputTypesMap(apiClient, blueprintID, blueprintVersion)
}

log.Printf("InputTypesMap: %v", inputTypesMap)
return inputTypesMap
}

func resourceDeploymentUpdate(d *schema.ResourceData, m interface{}) error {
log.Printf("Starting to update the vra_deployment resource with name %s", d.Get("name"))
apiClient := m.(*Client).apiClient
Expand Down Expand Up @@ -471,6 +450,14 @@ func resourceDeploymentUpdate(d *schema.ResourceData, m interface{}) error {
}
}

if d.HasChange("owner") {
deploymentUUID := strfmt.UUID(d.Id())
err := runChangeOwnerDeploymentAction(d, apiClient, deploymentUUID)
if err != nil {
return err
}
}

log.Printf("Finished updating the vra_deployment resource with name %s", d.Get("name"))
return resourceDeploymentRead(d, m)
}
Expand Down Expand Up @@ -506,6 +493,46 @@ func resourceDeploymentDelete(d *schema.ResourceData, m interface{}) error {
return nil
}

// Gets the inputs and their types as map[string]string
func getInputTypesMap(d *schema.ResourceData, apiClient *client.MulticloudIaaS) map[string]string {
inputTypesMap := make(map[string]string)

if _, ok := d.GetOk("inputs"); !ok {
return inputTypesMap
}

// Get blueprint_id and catalog_item_id
blueprintID, catalogItemID := "", ""
if v, ok := d.GetOk("blueprint_id"); ok {
blueprintID = v.(string)
}

if v, ok := d.GetOk("catalog_item_id"); ok {
catalogItemID = v.(string)
}

if catalogItemID != "" {
// Get the catalog item inputs and their types
catalogItemVersion := ""
if v, ok := d.GetOk("catalog_item_version"); ok {
catalogItemVersion = v.(string)
}

inputTypesMap, _ = getCatalogItemInputTypesMap(apiClient, catalogItemID, catalogItemVersion)
} else if blueprintID != "" {
// Get the blueprint inputs and their types
blueprintVersion := ""
if v, ok := d.GetOk("blueprint_version"); ok {
blueprintVersion = v.(string)
}

inputTypesMap, _ = getBlueprintInputTypesMap(apiClient, blueprintID, blueprintVersion)
}

log.Printf("InputTypesMap: %v", inputTypesMap)
return inputTypesMap
}

func getCatalogItemInputsByType(apiClient *client.MulticloudIaaS, catalogItemID string, catalogItemVersion string, inputValues interface{}) (map[string]interface{}, error) {
inputTypesMap, err := getCatalogItemInputTypesMap(apiClient, catalogItemID, catalogItemVersion)
if err != nil {
Expand All @@ -529,7 +556,7 @@ func getCatalogItemInputTypesMap(apiClient *client.MulticloudIaaS, catalogItemID
}

log.Printf("Inputs Schema: %v", inputsSchemaMap)
inputTypesMap, err := getInputTypesMapFromCatalogItemSchema(inputsSchemaMap)
inputTypesMap, err := getInputTypesMapFromSchema(inputsSchemaMap)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -890,8 +917,9 @@ func getInputsByType(inputs map[string]interface{}, inputTypesMap map[string]str
return inputsByType, nil
}

// Returns a map of string, string with input variables and their types defined in the vRA catalog item
func getInputTypesMapFromCatalogItemSchema(schema map[string]interface{}) (map[string]string, error) {
// Returns a map of string, string with input variables and their types defined in the given schema map.
//Used for getting map of inputs and their types for Catalog item and Deployment actions
func getInputTypesMapFromSchema(schema map[string]interface{}) (map[string]string, error) {
log.Printf("Getting the map of inputs and their types")
inputTypesMap := make(map[string]string, len(schema))
for k, v := range schema {
Expand All @@ -910,6 +938,107 @@ func getInputTypesMapFromBlueprintInputsSchema(schema map[string]models.Property
return inputTypesMap, nil
}

// Returns whether the day2 action is valid currently, exact action ID for a given action string
func getDeploymentDay2ActionID(apiClient *client.MulticloudIaaS, deploymentUUID strfmt.UUID, actionName string) (bool, string, error) {
// Get the deployment actions
deploymentActions, err := apiClient.DeploymentActions.GetDeploymentActionsUsingGET(deployment_actions.
NewGetDeploymentActionsUsingGETParams().WithDepID(deploymentUUID))
if err != nil {
return false, "", err
}

actionAvailable := false
actionID := ""

for _, action := range deploymentActions.Payload {
if strings.Contains(strings.ToLower(action.ID), strings.ToLower(actionName)) {
actionID = action.ID
if action.Valid {
log.Printf("[DEBUG] %s action is available on the deployment", actionName)
actionAvailable = true
return actionAvailable, actionID, nil
}

// Day-2 action id is not valid
log.Printf("[DEBUG] %s action is not valid based on current state of the deployment", actionID)
return actionAvailable, actionID, fmt.Errorf("%s action is not valid based on current state of the deployment", actionID)
}
}
return actionAvailable, actionID, fmt.Errorf("%s action is not found in the list of day2 actions allowed on the deployment", actionName)
}

// Gets the schema for a given deployment action id
func getDeploymentActionSchema(apiClient *client.MulticloudIaaS, deploymentUUID strfmt.UUID, actionID string) (map[string]interface{}, error) {
// Getting the catalog item schema
log.Printf("Getting the schema for deploymentID: %v, actionID: %v", deploymentUUID, actionID)
var actionSchema interface{}

deploymentAction, err := apiClient.DeploymentActions.GetDeploymentActionUsingGET(deployment_actions.
NewGetDeploymentActionUsingGETParams().WithDepID(deploymentUUID).WithActionID(actionID))
if err != nil {
return nil, err
}

actionSchema = deploymentAction.GetPayload().Schema

if actionSchema != nil && (actionSchema.(map[string]interface{}))["properties"] != nil {
actionInputsSchemaMap := (actionSchema.(map[string]interface{}))["properties"].(map[string]interface{})
return actionInputsSchemaMap, nil
}
return make(map[string]interface{}), nil
}

func getDeploymentActionInputTypesMap(apiClient *client.MulticloudIaaS, deploymentUUID strfmt.UUID, actionID string) (map[string]string, error) {
inputsSchemaMap, err := getDeploymentActionSchema(apiClient, deploymentUUID, actionID)
if err != nil {
return nil, err
}

inputTypesMap, err := getInputTypesMapFromSchema(inputsSchemaMap)
if err != nil {
return nil, err
}
return inputTypesMap, nil
}

func runChangeOwnerDeploymentAction(d *schema.ResourceData, apiClient *client.MulticloudIaaS, deploymentUUID strfmt.UUID) error {
oldOwner, newOwner := d.GetChange("owner")
log.Printf("Noticed changes to owner. Starting to change deployment owner from %s to %s", oldOwner.(string), newOwner.(string))

// Get the deployment actionID for Change Owner
isActionValid, actionID, err := getDeploymentDay2ActionID(apiClient, deploymentUUID, ChangeOwnerDeploymentActionName)
if err != nil {
return fmt.Errorf("noticed changes to owner. But, %s", err.Error())
}

if !isActionValid {
return fmt.Errorf("noticed changes to owner, but 'Change Owner' action is not found or supported")
}

// Continue if 'Change Owner' action is available. Get action inputs for the 'ChangeOwner' action
actionInputs := make(map[string]interface{})
actionInputs["New Owner"] = newOwner

actionInputTypesMap, err := getDeploymentActionInputTypesMap(apiClient, deploymentUUID, actionID)
if err != nil {
return err
}

inputs, err := getInputsByType(actionInputs, actionInputTypesMap)
if err != nil {
return fmt.Errorf("unable to create action inputs for %v. %v", actionID, err.Error())
}

reason := "Updated deployment owner from vRA provider for Terraform."
err = runAction(d, apiClient, deploymentUUID, actionID, inputs, reason)
if err != nil {
return err
}

log.Printf("Finished changing owner for vra_deployment %s with new owner %v", d.Get("name").(string), newOwner)
return nil
}

func runAction(d *schema.ResourceData, apiClient *client.MulticloudIaaS, deploymentUUID strfmt.UUID, actionID string, inputs map[string]interface{}, reason string) error {
resourceActionRequest := models.ResourceActionRequest{
ActionID: actionID,
Expand Down
6 changes: 5 additions & 1 deletion website/docs/d/vra_deployment.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ data "vra_deployment" "this" {

* `lease_expire_at` - Time at which the deployment lease expires.

* `org_id` - The ID of the organization this deployment belongs to.

* `owner` - The user this deployment belongs to.

* `project` - The project this entity belongs to.

* `description` - A human friendly description.
Expand All @@ -148,7 +152,7 @@ data "vra_deployment" "this" {

* `description` - A description of the resource.

`expense` - Expense incurred for this resource.
* `expense` - Expense incurred for this resource.

* `additional_expense` - Additional expense incurred for the resource.

Expand Down
4 changes: 4 additions & 0 deletions website/docs/r/vra_deployment.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ resource "vra_deployment" "this" {

* `name` - (Required) A human-friendly name used as an identifier in APIs that support this option.

* `org_id` - (Optional) The ID of the organization this deployment belongs to.

* `owner` - (Optional) The user this deployment belongs to.

* `project_id` - (Required) The id of the project this entity belongs to.


Expand Down

0 comments on commit fd70483

Please sign in to comment.