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 add 18 19import ( 20 "fmt" 21 "strings" 22 23 "github.com/spf13/cobra" 24 "sigs.k8s.io/kustomize/pkg/commands/kustfile" 25 "sigs.k8s.io/kustomize/pkg/constants" 26 "sigs.k8s.io/kustomize/pkg/fs" 27 "sigs.k8s.io/kustomize/pkg/types" 28) 29 30// kindOfAdd is the kind of metadata being added: label or annotation 31type kindOfAdd int 32 33const ( 34 annotation kindOfAdd = iota 35 label 36) 37 38func (k kindOfAdd) String() string { 39 kinds := [...]string{ 40 "annotation", 41 "label", 42 } 43 if k < 0 || k > 1 { 44 return "Unknown metadatakind" 45 } 46 return kinds[k] 47} 48 49type addMetadataOptions struct { 50 metadata map[string]string 51 mapValidator func(map[string]string) error 52 kind kindOfAdd 53} 54 55// newCmdAddAnnotation adds one or more commonAnnotations to the kustomization file. 56func newCmdAddAnnotation(fSys fs.FileSystem, v func(map[string]string) error) *cobra.Command { 57 var o addMetadataOptions 58 o.kind = annotation 59 o.mapValidator = v 60 cmd := &cobra.Command{ 61 Use: "annotation", 62 Short: "Adds one or more commonAnnotations to " + constants.KustomizationFileNames[0], 63 Example: ` 64 add annotation {annotationKey1:annotationValue1},{annotationKey2:annotationValue2}`, 65 RunE: func(cmd *cobra.Command, args []string) error { 66 return o.runE(args, fSys, o.addAnnotations) 67 }, 68 } 69 return cmd 70} 71 72// newCmdAddLabel adds one or more commonLabels to the kustomization file. 73func newCmdAddLabel(fSys fs.FileSystem, v func(map[string]string) error) *cobra.Command { 74 var o addMetadataOptions 75 o.kind = label 76 o.mapValidator = v 77 cmd := &cobra.Command{ 78 Use: "label", 79 Short: "Adds one or more commonLabels to " + constants.KustomizationFileNames[0], 80 Example: ` 81 add label {labelKey1:labelValue1},{labelKey2:labelValue2}`, 82 RunE: func(cmd *cobra.Command, args []string) error { 83 return o.runE(args, fSys, o.addLabels) 84 }, 85 } 86 return cmd 87} 88 89func (o *addMetadataOptions) runE( 90 args []string, fSys fs.FileSystem, adder func(*types.Kustomization) error) error { 91 err := o.validateAndParse(args) 92 if err != nil { 93 return err 94 } 95 kf, err := kustfile.NewKustomizationFile(fSys) 96 if err != nil { 97 return err 98 } 99 m, err := kf.Read() 100 if err != nil { 101 return err 102 } 103 err = adder(m) 104 if err != nil { 105 return err 106 } 107 return kf.Write(m) 108} 109 110// validateAndParse validates `add` commands and parses them into o.metadata 111func (o *addMetadataOptions) validateAndParse(args []string) error { 112 if len(args) < 1 { 113 return fmt.Errorf("must specify %s", o.kind) 114 } 115 if len(args) > 1 { 116 return fmt.Errorf("%ss must be comma-separated, with no spaces", o.kind) 117 } 118 m, err := o.convertToMap(args[0]) 119 if err != nil { 120 return err 121 } 122 if err = o.mapValidator(m); err != nil { 123 return err 124 } 125 o.metadata = m 126 return nil 127} 128 129func (o *addMetadataOptions) convertToMap(arg string) (map[string]string, error) { 130 result := make(map[string]string) 131 inputs := strings.Split(arg, ",") 132 for _, input := range inputs { 133 c := strings.Index(input, ":") 134 if c == 0 { 135 // key is not passed 136 return nil, o.makeError(input, "need k:v pair where v may be quoted") 137 } else if c < 0 { 138 // only key passed 139 result[input] = "" 140 } else { 141 // both key and value passed 142 key := input[:c] 143 value := trimQuotes(input[c+1:]) 144 result[key] = value 145 } 146 } 147 return result, nil 148} 149 150func (o *addMetadataOptions) addAnnotations(m *types.Kustomization) error { 151 if m.CommonAnnotations == nil { 152 m.CommonAnnotations = make(map[string]string) 153 } 154 return o.writeToMap(m.CommonAnnotations, annotation) 155} 156 157func (o *addMetadataOptions) addLabels(m *types.Kustomization) error { 158 if m.CommonLabels == nil { 159 m.CommonLabels = make(map[string]string) 160 } 161 return o.writeToMap(m.CommonLabels, label) 162} 163 164func (o *addMetadataOptions) writeToMap(m map[string]string, kind kindOfAdd) error { 165 for k, v := range o.metadata { 166 if _, ok := m[k]; ok { 167 return fmt.Errorf("%s %s already in kustomization file", kind, k) 168 } 169 m[k] = v 170 } 171 return nil 172} 173 174func (o *addMetadataOptions) makeError(input string, message string) error { 175 return fmt.Errorf("invalid %s: '%s' (%s)", o.kind, input, message) 176} 177 178func trimQuotes(s string) string { 179 if len(s) >= 2 { 180 if s[0] == '"' && s[len(s)-1] == '"' { 181 return s[1 : len(s)-1] 182 } 183 } 184 return s 185} 186