blob: ffb1515cf100660612b17c7e17e942087fab5e58 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tpgresource
import (
"context"
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
)
// SetLabels is called in the READ method of the resources to set
// the field "labels" and "terraform_labels" in the state based on the labels field in the configuration.
// So the field "labels" and "terraform_labels" in the state will only have the user defined labels.
// param "labels" is all of labels returned from API read reqeust.
// param "lineage" is the terraform lineage of the field and could be "labels" or "terraform_labels".
func SetLabels(labels map[string]string, d *schema.ResourceData, lineage string) error {
transformed := make(map[string]interface{})
if v, ok := d.GetOk(lineage); ok {
if labels != nil {
for k := range v.(map[string]interface{}) {
transformed[k] = labels[k]
}
}
}
return d.Set(lineage, transformed)
}
// Sets the "labels" field and "terraform_labels" with the value of the field "effective_labels" for data sources.
// When reading data source, as the labels field is unavailable in the configuration of the data source,
// the "labels" field will be empty. With this funciton, the labels "field" will have all of labels in the resource.
func SetDataSourceLabels(d *schema.ResourceData) error {
effectiveLabels := d.Get("effective_labels")
if effectiveLabels == nil {
return nil
}
if d.Get("labels") == nil {
return fmt.Errorf("`labels` field is not present in the resource schema.")
}
if err := d.Set("labels", effectiveLabels); err != nil {
return fmt.Errorf("Error setting labels in data source: %s", err)
}
if d.Get("terraform_labels") == nil {
return fmt.Errorf("`terraform_labels` field is not present in the resource schema.")
}
if err := d.Set("terraform_labels", effectiveLabels); err != nil {
return fmt.Errorf("Error setting terraform_labels in data source: %s", err)
}
return nil
}
// Sets the values of terraform_labels and effective_labels fields when labels field is in root level
func setLabelsFields(labelsField string, d *schema.ResourceDiff, meta interface{}, skipAttribution bool) error {
raw := d.Get(labelsField)
if raw == nil {
return nil
}
if d.Get("terraform_labels") == nil {
return fmt.Errorf("`terraform_labels` field is not present in the resource schema.")
}
if d.Get("effective_labels") == nil {
return fmt.Errorf("`effective_labels` field is not present in the resource schema.")
}
// If "labels" field is computed, set "terraform_labels" and "effective_labels" to computed.
// https://github.com/hashicorp/terraform-provider-google/issues/16217
if !d.GetRawPlan().GetAttr(labelsField).IsWhollyKnown() {
if err := d.SetNewComputed("terraform_labels"); err != nil {
return fmt.Errorf("error setting terraform_labels to computed: %w", err)
}
if err := d.SetNewComputed("effective_labels"); err != nil {
return fmt.Errorf("error setting effective_labels to computed: %w", err)
}
return nil
}
config := meta.(*transport_tpg.Config)
// Merge provider default labels with the user defined labels in the resource to get terraform managed labels
terraformLabels := make(map[string]string)
for k, v := range config.DefaultLabels {
terraformLabels[k] = v
}
// Append optional label indicating the resource was provisioned using Terraform
if !skipAttribution && config.AddTerraformAttributionLabel {
if el, ok := d.Get("effective_labels").(map[string]any); ok {
_, hasExistingLabel := el[transport_tpg.AttributionKey]
if hasExistingLabel ||
config.TerraformAttributionLabelAdditionStrategy == transport_tpg.ProactiveAttributionStrategy ||
(config.TerraformAttributionLabelAdditionStrategy == transport_tpg.CreateOnlyAttributionStrategy && d.Id() == "") {
terraformLabels[transport_tpg.AttributionKey] = transport_tpg.AttributionValue
}
}
}
labels := raw.(map[string]interface{})
for k, v := range labels {
terraformLabels[k] = v.(string)
}
if err := d.SetNew("terraform_labels", terraformLabels); err != nil {
return fmt.Errorf("error setting new terraform_labels diff: %w", err)
}
o, n := d.GetChange("terraform_labels")
effectiveLabels := d.Get("effective_labels").(map[string]interface{})
for k, v := range n.(map[string]interface{}) {
effectiveLabels[k] = v.(string)
}
for k := range o.(map[string]interface{}) {
if _, ok := n.(map[string]interface{})[k]; !ok {
delete(effectiveLabels, k)
}
}
if err := d.SetNew("effective_labels", effectiveLabels); err != nil {
return fmt.Errorf("error setting new effective_labels diff: %w", err)
}
return nil
}
func SetLabelsDiffWithoutAttributionLabel(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
return setLabelsFields("labels", d, meta, true)
}
// The CustomizeDiff func to set the values of terraform_labels and effective_labels fields
// when labels field is at the root level and named "labels".
func SetLabelsDiff(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
return setLabelsFields("labels", d, meta, false)
}
// The CustomizeDiff func to set the values of terraform_labels and effective_labels fields
// when labels field is at the root level and has a diffent name (e.g. resource_labels) than "labels"
func SetDiffForLabelsWithCustomizedName(labelsField string) func(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
return func(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
return setLabelsFields(labelsField, d, meta, false)
}
}
func SetMetadataLabelsDiff(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
l := d.Get("metadata").([]interface{})
if len(l) == 0 || l[0] == nil {
return nil
}
// Fix the bug that the computed and nested "labels" field disappears from the terraform plan.
// https://github.com/hashicorp/terraform-provider-google/issues/17756
// The bug is introduced by SetNew on "metadata" field with the object including terraform_labels and effective_labels.
// "terraform_labels" and "effective_labels" cannot be set directly due to a bug that SetNew doesn't work on nested fields
// in terraform sdk.
// https://github.com/hashicorp/terraform-plugin-sdk/issues/459
values := d.GetRawPlan().GetAttr("metadata").AsValueSlice()
if len(values) > 0 && !values[0].GetAttr("labels").IsWhollyKnown() {
return nil
}
raw := d.Get("metadata.0.labels")
if raw == nil {
return nil
}
if d.Get("metadata.0.terraform_labels") == nil {
return fmt.Errorf("`metadata.0.terraform_labels` field is not present in the resource schema.")
}
if d.Get("metadata.0.effective_labels") == nil {
return fmt.Errorf("`metadata.0.effective_labels` field is not present in the resource schema.")
}
config := meta.(*transport_tpg.Config)
// Merge provider default labels with the user defined labels in the resource to get terraform managed labels
terraformLabels := make(map[string]string)
for k, v := range config.DefaultLabels {
terraformLabels[k] = v
}
// Append optional label indicating the resource was provisioned using Terraform
if config.AddTerraformAttributionLabel {
if el, ok := d.Get("metadata.0.effective_labels").(map[string]any); ok {
_, hasExistingLabel := el[transport_tpg.AttributionKey]
if hasExistingLabel ||
config.TerraformAttributionLabelAdditionStrategy == transport_tpg.ProactiveAttributionStrategy ||
(config.TerraformAttributionLabelAdditionStrategy == transport_tpg.CreateOnlyAttributionStrategy && d.Id() == "") {
terraformLabels[transport_tpg.AttributionKey] = transport_tpg.AttributionValue
}
}
}
labels := raw.(map[string]interface{})
for k, v := range labels {
terraformLabels[k] = v.(string)
}
original := l[0].(map[string]interface{})
original["terraform_labels"] = terraformLabels
if err := d.SetNew("metadata", []interface{}{original}); err != nil {
return fmt.Errorf("error setting new metadata diff: %w", err)
}
o, n := d.GetChange("metadata.0.terraform_labels")
effectiveLabels := d.Get("metadata.0.effective_labels").(map[string]interface{})
for k, v := range n.(map[string]interface{}) {
effectiveLabels[k] = v.(string)
}
for k := range o.(map[string]interface{}) {
if _, ok := n.(map[string]interface{})[k]; !ok {
delete(effectiveLabels, k)
}
}
original["effective_labels"] = effectiveLabels
if err := d.SetNew("metadata", []interface{}{original}); err != nil {
return fmt.Errorf("error setting new metadata diff: %w", err)
}
return nil
}
// Upgrade the field "labels" in the state to exclude the labels with the labels prefix
// and the field "effective_labels" to have all of labels, including the labels with the labels prefix
func LabelsStateUpgrade(rawState map[string]interface{}, labesPrefix string) (map[string]interface{}, error) {
log.Printf("[DEBUG] Attributes before migration: %#v", rawState)
log.Printf("[DEBUG] Attributes before migration labels: %#v", rawState["labels"])
log.Printf("[DEBUG] Attributes before migration effective_labels: %#v", rawState["effective_labels"])
if rawState["labels"] != nil {
rawLabels := rawState["labels"].(map[string]interface{})
labels := make(map[string]interface{})
effectiveLabels := make(map[string]interface{})
for k, v := range rawLabels {
effectiveLabels[k] = v
if !strings.HasPrefix(k, labesPrefix) {
labels[k] = v
}
}
rawState["labels"] = labels
rawState["effective_labels"] = effectiveLabels
}
log.Printf("[DEBUG] Attributes after migration: %#v", rawState)
log.Printf("[DEBUG] Attributes after migration labels: %#v", rawState["labels"])
log.Printf("[DEBUG] Attributes after migration effective_labels: %#v", rawState["effective_labels"])
return rawState, nil
}
// Upgrade the field "terraform_labels" in the state to have the value of filed "labels"
// when it is not set but "labels" field is set in the state
func TerraformLabelsStateUpgrade(rawState map[string]interface{}) (map[string]interface{}, error) {
log.Printf("[DEBUG] Attributes before migration: %#v", rawState)
log.Printf("[DEBUG] Attributes before migration terraform_labels: %#v", rawState["terraform_labels"])
if rawState["terraform_labels"] == nil && rawState["labels"] != nil {
rawState["terraform_labels"] = rawState["labels"]
}
log.Printf("[DEBUG] Attributes after migration: %#v", rawState)
log.Printf("[DEBUG] Attributes after migration terraform_labels: %#v", rawState["terraform_labels"])
return rawState, nil
}