diff --git a/cloudmanager/cvo_aws.go b/cloudmanager/cvo_aws.go index 3079f7e..7c7991b 100644 --- a/cloudmanager/cvo_aws.go +++ b/cloudmanager/cvo_aws.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/fatih/structs" - "github.com/hashicorp/terraform/helper/schema" ) // AWSLicenseTypes is the AWS License types @@ -40,7 +39,7 @@ type createCVOAWSDetails struct { EnableCompliance bool `structs:"enableCompliance"` EnableMonitoring bool `structs:"enableMonitoring"` AwsEncryptionParameters awsEncryptionParameters `structs:"awsEncryptionParameters,omitempty"` - AwsTags []awsTags `structs:"awsTags,omitempty"` + AwsTags []userTags `structs:"awsTags,omitempty"` IsHA bool HAParams haParamsAWS `structs:"haParams,omitempty"` } @@ -82,12 +81,6 @@ type awsEncryptionParameters struct { KmsKeyID string `structs:"kmsKeyId,omitempty"` } -// awsTags the input for requesting a CVO -type awsTags struct { - TagKey string `structs:"tagKey"` - TagValue string `structs:"tagValue,omitempty"` -} - // deleteCVODetails the users input for deleting a cvo type deleteCVODetails struct { InstanceID string @@ -347,20 +340,6 @@ func (c *Client) deleteCVO(id string, isHA bool) error { return nil } -// expandAWSTags converts set to awsEncryptionParameters struct -func expandAWSTags(set *schema.Set) []awsTags { - tags := []awsTags{} - - for _, v := range set.List() { - tag := v.(map[string]interface{}) - awsTag := awsTags{} - awsTag.TagKey = tag["tag_key"].(string) - awsTag.TagValue = tag["tag_value"].(string) - tags = append(tags, awsTag) - } - return tags -} - // validateCVOParams validates params func validateCVOParams(cvoDetails createCVOAWSDetails) error { if cvoDetails.VsaMetadata.UseLatestVersion == true && cvoDetails.VsaMetadata.OntapVersion != "latest" { diff --git a/cloudmanager/cvo_azure.go b/cloudmanager/cvo_azure.go index a6912fd..24454d7 100644 --- a/cloudmanager/cvo_azure.go +++ b/cloudmanager/cvo_azure.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/fatih/structs" - "github.com/hashicorp/terraform/helper/schema" ) // AzureLicenseTypes is the Azure License types @@ -38,9 +37,9 @@ type createCVOAzureDetails struct { EnableCompliance bool `structs:"enableCompliance"` EnableMonitoring bool `structs:"enableMonitoring"` AllowDeployInExistingRg bool `structs:"allowDeployInExistingRg,omitempty"` - AzureTags []azureTags `structs:"azureTags,omitempty"` + AzureTags []userTags `structs:"azureTags,omitempty"` IsHA bool - ResourceGroup string `structs:"resourceGroup,omitempty"` + ResourceGroup string `structs:"resourceGroup,omitempty"` VnetResourceGroup string VnetForInternal string SerialNumber string `structs:"serialNumber,omitempty"` @@ -53,12 +52,6 @@ type haParamsAzure struct { PlatformSerialNumberNode2 string `structs:"platformSerialNumberNode2,omitempty"` } -// azureTags the input for requesting a CVO -type azureTags struct { - TagKey string `structs:"tagKey"` - TagValue string `structs:"tagValue,omitempty"` -} - // cvoListAzure the users input for getting cvo type cvoListAzure struct { CVO []cvoResult `json:"azureVsaWorkingEnvironments"` @@ -318,20 +311,6 @@ func (c *Client) deleteCVOAzure(id string, isHA bool) error { return nil } -// expandAzureTags converts set to azureTags struct -func expandAzureTags(set *schema.Set) []azureTags { - tags := []azureTags{} - - for _, v := range set.List() { - tag := v.(map[string]interface{}) - azureTag := azureTags{} - azureTag.TagKey = tag["tag_key"].(string) - azureTag.TagValue = tag["tag_value"].(string) - tags = append(tags, azureTag) - } - return tags -} - // validateCVOParams validates params func validateCVOAzureParams(cvoDetails createCVOAzureDetails) error { if cvoDetails.VsaMetadata.UseLatestVersion == true && cvoDetails.VsaMetadata.OntapVersion != "latest" { diff --git a/cloudmanager/cvo_gcp.go b/cloudmanager/cvo_gcp.go index 82c1234..b3a8007 100644 --- a/cloudmanager/cvo_gcp.go +++ b/cloudmanager/cvo_gcp.go @@ -181,6 +181,20 @@ func expandGCPLabels(set *schema.Set) []gcpLabels { return labels } +// expandGCPLabelsToUseTags +func expandGCPLabelsToUserTags(set *schema.Set) []userTags { + tags := []userTags{} + + for _, v := range set.List() { + label := v.(map[string]interface{}) + userTag := userTags{} + userTag.TagKey = label["label_key"].(string) + userTag.TagValue = label["label_value"].(string) + tags = append(tags, userTag) + } + return tags +} + // validateCVOGCPParams validates params func validateCVOGCPParams(cvoDetails createCVOGCPDetails) error { if cvoDetails.VsaMetadata.UseLatestVersion == true && cvoDetails.VsaMetadata.OntapVersion != "latest" { diff --git a/cloudmanager/helper.go b/cloudmanager/helper.go index cd1c005..66b69cf 100644 --- a/cloudmanager/helper.go +++ b/cloudmanager/helper.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/fatih/structs" "github.com/hashicorp/terraform/helper/schema" ) @@ -32,6 +33,17 @@ type workingEnvironmentResult struct { GcpVsaWorkingEnvironments []workingEnvironmentInfo `json:"gcpVsaWorkingEnvironments"` } +// userTags the input for requesting a CVO +type userTags struct { + TagKey string `structs:"tagKey"` + TagValue string `structs:"tagValue,omitempty"` +} + +// ModifyUserTagsRequest the input for requesting tags modificaiton +type modifyUserTagsRequest struct { + Tags []userTags `structs:"tags"` +} + // Check HTTP response code, return error if HTTP request is not successed. func apiResponseChecker(statusCode int, response []byte, funcName string) error { @@ -413,3 +425,105 @@ func (c *Client) findWorkingEnvironmentForID(id string) (workingEnvironmentInfo, return workingEnvironmentInfo{}, err } + +// customized check diff user-tags (aws_tag, azure_tag, gcp_label) +func checkUserTagDiff(diff *schema.ResourceDiff, tagName string, keyName string) error { + if diff.HasChange(tagName) { + _, expectTag := diff.GetChange(tagName) + etags := expectTag.(*schema.Set) + if etags.Len() > 0 { + log.Println("etags len: ", etags.Len()) + // check each of the tag_key in the list is unique + respErr := checkUserTagKeyUnique(etags, keyName) + if respErr != nil { + return respErr + } + } + } + return nil +} + +// check each of the tag_key or label_key in the list is unique +func checkUserTagKeyUnique(etags *schema.Set, keyName string) error { + m := make(map[string]bool) + for _, v := range etags.List() { + tag := v.(map[string]interface{}) + tkey := tag[keyName].(string) + if _, ok := m[tkey]; !ok { + m[tkey] = true + } else { + return fmt.Errorf("Tag key %s is not unique", tkey) + } + } + return nil +} + +// expandUserTags converts set to userTags struct +func expandUserTags(set *schema.Set) []userTags { + tags := []userTags{} + + for _, v := range set.List() { + tag := v.(map[string]interface{}) + userTag := userTags{} + userTag.TagKey = tag["tag_key"].(string) + userTag.TagValue = tag["tag_value"].(string) + tags = append(tags, userTag) + } + return tags +} + +// modify the CVO user-tags +func (c *Client) callUpdateUserTags(request modifyUserTagsRequest, id string, isHA bool) error { + apiRoot, _, err := c.getAPIRoot(id) + baseURL := fmt.Sprintf("%s/working-environments/%s/user-tags", apiRoot, id) + + hostType := "CloudManagerHost" + params := structs.Map(request) + + accessTokenResult, err := c.getAccessToken() + if err != nil { + log.Print("in updateCVOUserTags request, failed to get AccessToken") + return err + } + c.Token = accessTokenResult.Token + + statusCode, response, _, err := c.CallAPIMethod("PUT", baseURL, params, c.Token, hostType) + if err != nil { + log.Print("updateCVOUserTags request failed: ", statusCode) + log.Print("call api response: ", response) + return err + } + + responseError := apiResponseChecker(statusCode, response, "updateCVOUserTags") + if responseError != nil { + return responseError + } + return nil +} + +// update CVO user-tags +func updateCVOUserTags(d *schema.ResourceData, meta interface{}, tagName string) error { + client := meta.(*Client) + client.ClientID = d.Get("client_id").(string) + var request modifyUserTagsRequest + if c, ok := d.GetOk(tagName); ok { + tags := c.(*schema.Set) + if tags.Len() > 0 { + if tagName == "gcp_label" { + request.Tags = expandGCPLabelsToUserTags(tags) + } else { + request.Tags = expandUserTags(tags) + } + log.Print("Update user-tags: ", request.Tags) + } + } + // Update tags + isHA := d.Get("is_ha").(bool) + id := d.Id() + updateErr := client.callUpdateUserTags(request, id, isHA) + if updateErr != nil { + return updateErr + } + log.Printf("Updated %s %s: %v", id, tagName, request.Tags) + return nil +} diff --git a/cloudmanager/occm_aws.go b/cloudmanager/occm_aws.go index 9338245..0e545f7 100644 --- a/cloudmanager/occm_aws.go +++ b/cloudmanager/occm_aws.go @@ -63,7 +63,7 @@ type createOCCMDetails struct { AssociatePublicIP bool FirewallTags bool EnableTerminationProtection *bool - AwsTags []awsTags + AwsTags []userTags } // deleteOCCMDetails the users input for deleting a occm diff --git a/cloudmanager/resource_netapp_cloudmanager_connector_aws.go b/cloudmanager/resource_netapp_cloudmanager_connector_aws.go index 00aff9a..8773979 100644 --- a/cloudmanager/resource_netapp_cloudmanager_connector_aws.go +++ b/cloudmanager/resource_netapp_cloudmanager_connector_aws.go @@ -206,7 +206,7 @@ func resourceOCCMAWSCreate(d *schema.ResourceData, meta interface{}) error { if o, ok := d.GetOk("aws_tag"); ok { tags := o.(*schema.Set) if tags.Len() > 0 { - occmDetails.AwsTags = expandAWSTags(tags) + occmDetails.AwsTags = expandUserTags(tags) } } diff --git a/cloudmanager/resource_netapp_cloudmanager_cvo_aws.go b/cloudmanager/resource_netapp_cloudmanager_cvo_aws.go index f77e5b0..943a3e8 100644 --- a/cloudmanager/resource_netapp_cloudmanager_cvo_aws.go +++ b/cloudmanager/resource_netapp_cloudmanager_cvo_aws.go @@ -13,11 +13,12 @@ func resourceCVOAWS() *schema.Resource { Create: resourceCVOAWSCreate, Read: resourceCVOAWSRead, Delete: resourceCVOAWSDelete, + Update: resourceCVOAWSUpdate, Exists: resourceCVOAWSExists, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - + CustomizeDiff: resourceCVOAWSCustomizeDiff, Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -195,18 +196,15 @@ func resourceCVOAWS() *schema.Resource { "aws_tag": { Type: schema.TypeSet, Optional: true, - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "tag_key": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "tag_value": { Type: schema.TypeString, Optional: true, - ForceNew: true, }, }, }, @@ -322,7 +320,7 @@ func resourceCVOAWSCreate(d *schema.ResourceData, meta interface{}) error { if c, ok := d.GetOk("aws_tag"); ok { tags := c.(*schema.Set) if tags.Len() > 0 { - cvoDetails.AwsTags = expandAWSTags(tags) + cvoDetails.AwsTags = expandUserTags(tags) } } cvoDetails.EbsVolumeSize.Size = d.Get("ebs_volume_size").(int) @@ -470,6 +468,28 @@ func resourceCVOAWSDelete(d *schema.ResourceData, meta interface{}) error { return nil } +func resourceCVOAWSUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("Updating CVO: %#v", d) + + // check if aws_tag has changes + if d.HasChange("aws_tag") { + respErr := updateCVOUserTags(d, meta, "aws_tag") + if respErr != nil { + return respErr + } + return resourceCVOAWSRead(d, meta) + } + return nil +} + +func resourceCVOAWSCustomizeDiff(diff *schema.ResourceDiff, v interface{}) error { + respErr := checkUserTagDiff(diff, "aws_tag", "tag_key") + if respErr != nil { + return respErr + } + return nil +} + func resourceCVOAWSExists(d *schema.ResourceData, meta interface{}) (bool, error) { log.Printf("Checking existence of CVO: %#v", d) client := meta.(*Client) diff --git a/cloudmanager/resource_netapp_cloudmanager_cvo_azure.go b/cloudmanager/resource_netapp_cloudmanager_cvo_azure.go index 861aaba..ab8dfc6 100644 --- a/cloudmanager/resource_netapp_cloudmanager_cvo_azure.go +++ b/cloudmanager/resource_netapp_cloudmanager_cvo_azure.go @@ -14,11 +14,12 @@ func resourceCVOAzure() *schema.Resource { Create: resourceCVOAzureCreate, Read: resourceCVOAzureRead, Delete: resourceCVOAzureDelete, + Update: resourceCVOAzureUpdate, Exists: resourceCVOAzureExists, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - + CustomizeDiff: resourceCVOAzureCustomizeDiff, Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -184,18 +185,15 @@ func resourceCVOAzure() *schema.Resource { "azure_tag": { Type: schema.TypeSet, Optional: true, - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "tag_key": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "tag_value": { Type: schema.TypeString, Optional: true, - ForceNew: true, }, }, }, @@ -256,7 +254,7 @@ func resourceCVOAzureCreate(d *schema.ResourceData, meta interface{}) error { if c, ok := d.GetOk("azure_tag"); ok { tags := c.(*schema.Set) if tags.Len() > 0 { - cvoDetails.AzureTags = expandAzureTags(tags) + cvoDetails.AzureTags = expandUserTags(tags) } } cvoDetails.DiskSize.Size = d.Get("disk_size").(int) @@ -381,6 +379,28 @@ func resourceCVOAzureDelete(d *schema.ResourceData, meta interface{}) error { return nil } +func resourceCVOAzureUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("Updating CVO: %#v", d) + + // check if aws_tag has changes + if d.HasChange("azure_tag") { + respErr := updateCVOUserTags(d, meta, "azure_tag") + if respErr != nil { + return respErr + } + return resourceCVOAzureRead(d, meta) + } + return nil +} + +func resourceCVOAzureCustomizeDiff(diff *schema.ResourceDiff, v interface{}) error { + respErr := checkUserTagDiff(diff, "azure_tag", "tag_key") + if respErr != nil { + return respErr + } + return nil +} + func resourceCVOAzureExists(d *schema.ResourceData, meta interface{}) (bool, error) { log.Printf("Checking existence of CVO: %#v", d) client := meta.(*Client) diff --git a/cloudmanager/resource_netapp_cloudmanager_cvo_gcp.go b/cloudmanager/resource_netapp_cloudmanager_cvo_gcp.go index 5f9423e..cdbd9b9 100644 --- a/cloudmanager/resource_netapp_cloudmanager_cvo_gcp.go +++ b/cloudmanager/resource_netapp_cloudmanager_cvo_gcp.go @@ -14,11 +14,12 @@ func resourceCVOGCP() *schema.Resource { Create: resourceCVOGCPCreate, Read: resourceCVOGCPRead, Delete: resourceCVOGCPDelete, + Update: resourceCVOGCPUpdate, Exists: resourceCVOGCPExists, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - + CustomizeDiff: resourceCVOGCPCustomizeDiff, Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -153,21 +154,19 @@ func resourceCVOGCP() *schema.Resource { "gcp_label": { Type: schema.TypeSet, Optional: true, - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "label_key": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "label_value": { Type: schema.TypeString, Optional: true, - ForceNew: true, }, }, }, + // ValidateFunc: func(val interface{}, ) }, "firewall_rule": { Type: schema.TypeString, @@ -497,6 +496,28 @@ func resourceCVOGCPDelete(d *schema.ResourceData, meta interface{}) error { return nil } +func resourceCVOGCPUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("Updating CVO: %#v", d) + + // check if gcp_label has changes + if d.HasChange("gcp_label") { + respErr := updateCVOUserTags(d, meta, "gcp_label") + if respErr != nil { + return respErr + } + return resourceCVOGCPRead(d, meta) + } + return nil +} + +func resourceCVOGCPCustomizeDiff(diff *schema.ResourceDiff, v interface{}) error { + respErr := checkUserTagDiff(diff, "gcp_label", "label_key") + if respErr != nil { + return respErr + } + return nil +} + func resourceCVOGCPExists(d *schema.ResourceData, meta interface{}) (bool, error) { log.Printf("Checking existence of CVO: %#v", d) client := meta.(*Client)