github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/schema/util/util.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package util
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"reflect"
    23  	"strings"
    24  
    25  	yamlpatch "github.com/krishicks/yaml-patch"
    26  
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
    28  )
    29  
    30  type VersionedConfig interface {
    31  	GetVersion() string
    32  	Upgrade() (VersionedConfig, error)
    33  }
    34  
    35  // HelmOverrides is a helper struct to aid with json serialization of map[string]interface{}
    36  type HelmOverrides struct {
    37  	Values map[string]interface{} `yaml:",inline"`
    38  }
    39  
    40  // FlatMap flattens deeply nested yaml into a map with corresponding dot separated keys
    41  type FlatMap map[string]string
    42  
    43  // MarshalJSON implements JSON marshalling by including the value as an inline yaml fragment.
    44  func (h *HelmOverrides) MarshalJSON() ([]byte, error) {
    45  	return marshalInlineYaml(h)
    46  }
    47  
    48  // UnmarshalYAML implements JSON unmarshalling by reading an inline yaml fragment.
    49  func (h *HelmOverrides) UnmarshalJSON(text []byte) error {
    50  	yml, err := unmarshalInlineYaml(text)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	return yaml.Unmarshal([]byte(yml), h)
    55  }
    56  
    57  // YamlpatchNode wraps a `yamlpatch.Node` and makes it serializable to JSON.
    58  // The yaml serialization needs to be implemented manually, because the node may be
    59  // an arbitrary yaml fragment so that a field tag `yaml:",inline"` does not work here.
    60  type YamlpatchNode struct {
    61  	// node is an arbitrary yaml fragment
    62  	Node yamlpatch.Node
    63  }
    64  
    65  // MarshalJSON implements JSON marshalling by including the value as an inline yaml fragment.
    66  func (n *YamlpatchNode) MarshalJSON() ([]byte, error) {
    67  	return marshalInlineYaml(n)
    68  }
    69  
    70  // UnmarshalYAML implements JSON unmarshalling by reading an inline yaml fragment.
    71  func (n *YamlpatchNode) UnmarshalJSON(text []byte) error {
    72  	yml, err := unmarshalInlineYaml(text)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	return yaml.Unmarshal([]byte(yml), n)
    77  }
    78  
    79  // MarshalYAML implements yaml.Marshaler.
    80  func (n *YamlpatchNode) MarshalYAML() (interface{}, error) {
    81  	return n.Node.MarshalYAML()
    82  }
    83  
    84  // UnmarshalYAML implements yaml.Unmarshaler
    85  func (n *YamlpatchNode) UnmarshalYAML(unmarshal func(interface{}) error) error {
    86  	return n.Node.UnmarshalYAML(unmarshal)
    87  }
    88  
    89  func (m *FlatMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
    90  	var obj map[string]interface{}
    91  	if err := unmarshal(&obj); err != nil {
    92  		return err
    93  	}
    94  	result := make(map[string]string)
    95  	if err := buildFlatMap(obj, result, ""); err != nil {
    96  		return err
    97  	}
    98  	*m = result
    99  	return nil
   100  }
   101  
   102  func buildFlatMap(obj map[string]interface{}, result map[string]string, currK string) (err error) {
   103  	var prevK string
   104  	for k, v := range obj {
   105  		prevK = currK
   106  		if currK == "" {
   107  			currK = fmt.Sprintf("%v", k)
   108  		} else {
   109  			currK = fmt.Sprintf("%v.%v", currK, k)
   110  		}
   111  
   112  		switch v := v.(type) {
   113  		case map[string]interface{}:
   114  			if err = buildFlatMap(v, result, currK); err != nil {
   115  				return
   116  			}
   117  		case []interface{}:
   118  			for idx, i := range v {
   119  				if m, ok := i.(map[string]interface{}); ok {
   120  					currIdx := fmt.Sprintf("%v[%d]", currK, idx)
   121  					if err = buildFlatMap(m, result, currIdx); err != nil {
   122  						return
   123  					}
   124  				}
   125  			}
   126  		case string:
   127  			result[currK] = v
   128  		default:
   129  			result[currK] = fmt.Sprintf("%v", v)
   130  		}
   131  		currK = prevK
   132  	}
   133  	return err
   134  }
   135  
   136  func marshalInlineYaml(in interface{}) ([]byte, error) {
   137  	yaml, err := yaml.Marshal(in)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	return json.Marshal(string(yaml))
   142  }
   143  
   144  func unmarshalInlineYaml(text []byte) (string, error) {
   145  	var in string
   146  	err := json.Unmarshal(text, &in)
   147  	return in, err
   148  }
   149  
   150  // IsOneOfField checks if a field is tagged with oneOf
   151  func IsOneOfField(field reflect.StructField) bool {
   152  	for _, tag := range strings.Split(field.Tag.Get("yamltags"), ",") {
   153  		tagParts := strings.Split(tag, "=")
   154  
   155  		if tagParts[0] == "oneOf" {
   156  			return true
   157  		}
   158  	}
   159  	return false
   160  }