...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf/overrides.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package krmtotf
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  
    21  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/deepcopy"
    22  
    23  	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    24  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    25  )
    26  
    27  func withCustomFlatteners(config map[string]interface{}, kind string) (map[string]interface{}, error) {
    28  	switch kind {
    29  	case "ComputeInstance", "ComputeInstanceTemplate":
    30  		return FlattenComputeInstanceMetadata(config)
    31  	default:
    32  		return config, nil
    33  	}
    34  }
    35  
    36  func withCustomExpanders(state map[string]interface{}, prev *Resource, kind string) map[string]interface{} {
    37  	switch kind {
    38  	case "ComputeInstance", "ComputeInstanceTemplate":
    39  		return ExpandComputeInstanceMetadata(state, prev)
    40  	default:
    41  		return state
    42  	}
    43  }
    44  
    45  func withResourceCustomResolvers(config map[string]interface{}, liveState map[string]interface{}, kind string, r *tfschema.Resource) (map[string]interface{}, error) {
    46  	switch kind {
    47  	case "BigtableInstance":
    48  		return MergeClusterConfigsFromLiveStateForBigtableInstance(config, liveState, r)
    49  	default:
    50  		return config, nil
    51  	}
    52  }
    53  
    54  // MergeClusterConfigsFromLiveStateForBigtableInstance is a resource specific function to deal with the following edge case.
    55  // BigtableInstance has a `cluster` field that takes a full list of clusters associated with the instance. The list of clusters read from the API is unordered.
    56  // Due to the terraform SDK limitation, if some optional field e.g. num_nodes is omitted, terraform SDK will determine the current value of the field from the cluster on the same index
    57  // rather than from the cluster with the same cluster_id; plus the returned list is not in the same order as user specified, the partial config with optional fields omitted will result in unexpected behaviors.
    58  // As a workarounds until migrating this resource to DCL, KCC will maintain this following resource specific code to merge the cluster config for omitted fields from cluster's live state by cluster_id.
    59  // DCL is expected to have the similar logic on its side to merge the partial desired intent with the live state; once this resource is migrated to DCL, we should be able to remove the bespoke code.
    60  func MergeClusterConfigsFromLiveStateForBigtableInstance(config map[string]interface{}, liveState map[string]interface{}, r *tfschema.Resource) (map[string]interface{}, error) {
    61  	if len(liveState) == 0 || len(config) == 0 {
    62  		return config, nil
    63  	}
    64  	clustersRaw, found, err := unstructured.NestedFieldCopy(liveState, "cluster")
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	if !found || clustersRaw == nil {
    69  		return config, nil
    70  	}
    71  	clusters, ok := clustersRaw.([]interface{})
    72  	if !ok {
    73  		return nil, fmt.Errorf("expected the value of `cluster` field in config to be []interface{}, but was actually %T", clustersRaw)
    74  	}
    75  
    76  	newClustersRaw, found, err := unstructured.NestedFieldCopy(config, "cluster")
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	if !found || newClustersRaw == nil {
    81  		return config, nil
    82  	}
    83  	newClusters, ok := newClustersRaw.([]interface{})
    84  	if !ok {
    85  		return nil, fmt.Errorf("expected the value of `cluster` field in state to be []interface{}, but was actually %T", newClusters)
    86  	}
    87  	for _, item := range newClusters {
    88  		newCluster, ok := item.(map[string]interface{})
    89  		if !ok {
    90  			return nil, fmt.Errorf("expected the item value of `cluster` field to be map[string]interface{}, but was actually %T", item)
    91  		}
    92  		c, found, err := getClusterById(clusters, newCluster["cluster_id"])
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		if !found {
    97  			continue
    98  		}
    99  		clusterSchema := r.Schema["cluster"].Elem.(*tfschema.Resource)
   100  		mergeClusterConfigWithLiveState(newCluster, c, clusterSchema)
   101  	}
   102  	config["cluster"] = newClusters
   103  	return config, nil
   104  }
   105  
   106  func getClusterById(clusters []interface{}, id interface{}) (map[string]interface{}, bool, error) {
   107  	for _, item := range clusters {
   108  		c, ok := item.(map[string]interface{})
   109  		if !ok {
   110  			return nil, false, fmt.Errorf("expected the item value of `cluster` field to be map[string]interface{}, but was actually %T", item)
   111  		}
   112  		if id == c["cluster_id"] {
   113  			return c, true, nil
   114  		}
   115  	}
   116  	return nil, false, nil
   117  }
   118  
   119  func mergeClusterConfigWithLiveState(config map[string]interface{}, state map[string]interface{}, clusterSchema *tfschema.Resource) {
   120  	for f, _ := range clusterSchema.Schema {
   121  		if f == "cluster_id" {
   122  			continue
   123  		}
   124  
   125  		if _, ok := config[f]; !ok {
   126  			if v, ok := state[f]; ok {
   127  				config[f] = v
   128  			}
   129  		}
   130  	}
   131  }
   132  
   133  func FlattenComputeInstanceMetadata(config map[string]interface{}) (map[string]interface{}, error) {
   134  	metadataRaw, _ := config["metadata"]
   135  	if metadataRaw == nil {
   136  		return config, nil
   137  	}
   138  	metadataList, ok := metadataRaw.([]interface{})
   139  	if !ok {
   140  		return nil, fmt.Errorf("metadata field not in expected list format")
   141  	}
   142  	config = deepcopy.MapStringInterface(config)
   143  	metadataMap, err := convertStructuredListToMap(metadataList)
   144  	if err != nil {
   145  		return nil, fmt.Errorf("error converting structured list to map: %w", err)
   146  	}
   147  	config["metadata"] = metadataMap
   148  	return config, nil
   149  }
   150  
   151  func ExpandComputeInstanceMetadata(state map[string]interface{}, prev *Resource) map[string]interface{} {
   152  	// If previous resource already has metadata set, copy the metadata from
   153  	// the previous resource onto the state to preserve the desired ordering
   154  	// from the customer.
   155  	prevMetadata, _ := prev.Spec["metadata"]
   156  	if prevMetadata != nil {
   157  		state = deepcopy.MapStringInterface(state)
   158  		state["metadata"] = prevMetadata
   159  		return state
   160  	}
   161  	// Otherwise, if the previous resource does not specify metadata, then
   162  	// convert the metadata map in the state to a structured list.
   163  	stateMetadataMapRaw, ok := state["metadata"]
   164  	if !ok || stateMetadataMapRaw == nil {
   165  		return state
   166  	}
   167  	stateMetadataMap, ok := stateMetadataMapRaw.(map[string]interface{})
   168  	if !ok {
   169  		panic(fmt.Errorf("metadata in state unexpectedly is not of type map[string]interface{}: %v", stateMetadataMapRaw))
   170  	}
   171  	state["metadata"] = convertMapToStructuredList(stateMetadataMap)
   172  	return state
   173  }
   174  
   175  func convertStructuredListToMap(l []interface{}) (map[string]interface{}, error) {
   176  	// Converts a list of structured {"key":"foo", "value":"bar"} objects into a simple
   177  	// map[string]interface{}.
   178  	m := make(map[string]interface{})
   179  	for _, elemRaw := range l {
   180  		elem, ok := elemRaw.(map[string]interface{})
   181  		if !ok {
   182  			return nil, fmt.Errorf("could not process metadata list element as object")
   183  		}
   184  		key, ok := elem["key"].(string)
   185  		if !ok {
   186  			return nil, fmt.Errorf("metadata list element's key is not string: %+v", key)
   187  		}
   188  		val := elem["value"]
   189  		if existingVal, ok := m[key]; ok {
   190  			return nil, fmt.Errorf("duplicate values set for key '%v': [%v, %v]", key, existingVal, val)
   191  		}
   192  		m[key] = val
   193  	}
   194  	return m, nil
   195  }
   196  
   197  func convertMapToStructuredList(m map[string]interface{}) []interface{} {
   198  	// Converts a map[string]interface{} to a list of structured {"key":"foo",
   199  	// "value":"bar"} objects. Resulting list is sorted by key to ensure
   200  	// function is deterministic.
   201  	l := make([]interface{}, 0)
   202  	for k, v := range m {
   203  		l = append(l, map[string]interface{}{
   204  			"key":   k,
   205  			"value": v,
   206  		})
   207  	}
   208  	sort.Slice(l, func(i, j int) bool {
   209  		iKey, err := keyForStructuredListElement(l[i])
   210  		if err != nil {
   211  			panic(err)
   212  		}
   213  		jKey, err := keyForStructuredListElement(l[j])
   214  		if err != nil {
   215  			panic(err)
   216  		}
   217  		return iKey < jKey
   218  	})
   219  	return l
   220  }
   221  
   222  func keyForStructuredListElement(rawElem interface{}) (string, error) {
   223  	elem, ok := rawElem.(map[string]interface{})
   224  	if !ok {
   225  		return "", fmt.Errorf("structured list element unexpectedly does not have type map[string]interface{}: %v", rawElem)
   226  	}
   227  	rawKey, ok := elem["key"]
   228  	if !ok {
   229  		return "", fmt.Errorf("structured list element unexpectedly does not have a key: %v", rawElem)
   230  	}
   231  	key, ok := rawKey.(string)
   232  	if !ok {
   233  		return "", fmt.Errorf("structured list element's key unexpectedly does not have type string: %v", rawKey)
   234  	}
   235  	return key, nil
   236  }
   237  

View as plain text