1/* 2Copyright 2018 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package config 18 19import ( 20 "fmt" 21 "strings" 22 23 "sigs.k8s.io/kustomize/pkg/gvk" 24) 25 26// FieldSpec completely specifies a kustomizable field in 27// an unstructured representation of a k8s API object. 28// It helps define the operands of transformations. 29// 30// For example, a directive to add a common label to objects 31// will need to know that a 'Deployment' object (in API group 32// 'apps', any version) can have labels at field path 33// 'spec/template/metadata/labels', and further that it is OK 34// (or not OK) to add that field path to the object if the 35// field path doesn't exist already. 36// 37// This would look like 38// { 39// group: apps 40// kind: Deployment 41// path: spec/template/metadata/labels 42// create: true 43// } 44type FieldSpec struct { 45 gvk.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"` 46 Path string `json:"path,omitempty" yaml:"path,omitempty"` 47 CreateIfNotPresent bool `json:"create,omitempty" yaml:"create,omitempty"` 48} 49 50const ( 51 escapedForwardSlash = "\\/" 52 tempSlashReplacement = "???" 53) 54 55func (fs FieldSpec) String() string { 56 return fmt.Sprintf( 57 "%s:%v:%s", fs.Gvk.String(), fs.CreateIfNotPresent, fs.Path) 58} 59 60// If true, the primary key is the same, but other fields might not be. 61func (fs FieldSpec) effectivelyEquals(other FieldSpec) bool { 62 return fs.IsSelected(&other.Gvk) && fs.Path == other.Path 63} 64 65// PathSlice converts the path string to a slice of strings, 66// separated by a '/'. Forward slash can be contained in a 67// fieldname. such as ingress.kubernetes.io/auth-secret in 68// Ingress annotations. To deal with this special case, the 69// path to this field should be formatted as 70// 71// metadata/annotations/ingress.kubernetes.io\/auth-secret 72// 73// Then PathSlice will return 74// 75// []string{ 76// "metadata", 77// "annotations", 78// "ingress.auth-secretkubernetes.io/auth-secret" 79// } 80func (fs FieldSpec) PathSlice() []string { 81 if !strings.Contains(fs.Path, escapedForwardSlash) { 82 return strings.Split(fs.Path, "/") 83 } 84 s := strings.Replace(fs.Path, escapedForwardSlash, tempSlashReplacement, -1) 85 paths := strings.Split(s, "/") 86 var result []string 87 for _, path := range paths { 88 result = append(result, strings.Replace(path, tempSlashReplacement, "/", -1)) 89 } 90 return result 91} 92 93type fsSlice []FieldSpec 94 95func (s fsSlice) Len() int { return len(s) } 96func (s fsSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 97func (s fsSlice) Less(i, j int) bool { 98 return s[i].Gvk.IsLessThan(s[j].Gvk) 99} 100 101// mergeAll merges the argument into this, returning the result. 102// Items already present are ignored. 103// Items that conflict (primary key matches, but remain data differs) 104// result in an error. 105func (s fsSlice) mergeAll(incoming fsSlice) (result fsSlice, err error) { 106 result = s 107 for _, x := range incoming { 108 result, err = result.mergeOne(x) 109 if err != nil { 110 return nil, err 111 } 112 } 113 return result, nil 114} 115 116// mergeOne merges the argument into this, returning the result. 117// If the item's primary key is already present, and there are no 118// conflicts, it is ignored (we don't want duplicates). 119// If there is a conflict, the merge fails. 120func (s fsSlice) mergeOne(x FieldSpec) (fsSlice, error) { 121 i := s.index(x) 122 if i > -1 { 123 // It's already there. 124 if s[i].CreateIfNotPresent != x.CreateIfNotPresent { 125 return nil, fmt.Errorf("conflicting fieldspecs") 126 } 127 return s, nil 128 } 129 return append(s, x), nil 130} 131 132func (s fsSlice) index(fs FieldSpec) int { 133 for i, x := range s { 134 if x.effectivelyEquals(fs) { 135 return i 136 } 137 } 138 return -1 139} 140