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 generate 18 19import ( 20 "fmt" 21 "reflect" 22 "strconv" 23 "strings" 24 25 "github.com/spf13/cobra" 26 "github.com/spf13/pflag" 27 28 "k8s.io/apimachinery/pkg/runtime" 29 utilerrors "k8s.io/apimachinery/pkg/util/errors" 30) 31 32// GeneratorFunc returns the generators for the provided command 33type GeneratorFunc func(cmdName string) map[string]Generator 34 35// GeneratorParam is a parameter for a generator 36// TODO: facilitate structured json generator input schemes 37type GeneratorParam struct { 38 Name string 39 Required bool 40} 41 42// Generator is an interface for things that can generate API objects from input 43// parameters. One example is the "expose" generator that is capable of exposing 44// new replication controllers and services, among other things. 45type Generator interface { 46 // Generate creates an API object given a set of parameters 47 Generate(params map[string]interface{}) (runtime.Object, error) 48 // ParamNames returns the list of parameters that this generator uses 49 ParamNames() []GeneratorParam 50} 51 52// StructuredGenerator is an interface for things that can generate API objects not using parameter injection 53type StructuredGenerator interface { 54 // StructuredGenerator creates an API object using pre-configured parameters 55 StructuredGenerate() (runtime.Object, error) 56} 57 58func IsZero(i interface{}) bool { 59 if i == nil { 60 return true 61 } 62 return reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) 63} 64 65// ValidateParams ensures that all required params are present in the params map 66func ValidateParams(paramSpec []GeneratorParam, params map[string]interface{}) error { 67 allErrs := []error{} 68 for ix := range paramSpec { 69 if paramSpec[ix].Required { 70 value, found := params[paramSpec[ix].Name] 71 if !found || IsZero(value) { 72 allErrs = append(allErrs, fmt.Errorf("Parameter: %s is required", paramSpec[ix].Name)) 73 } 74 } 75 } 76 return utilerrors.NewAggregate(allErrs) 77} 78 79// AnnotateFlags annotates all flags that are used by generators. 80func AnnotateFlags(cmd *cobra.Command, generators map[string]Generator) { 81 // Iterate over all generators and mark any flags used by them. 82 for name, generator := range generators { 83 generatorParams := map[string]struct{}{} 84 for _, param := range generator.ParamNames() { 85 generatorParams[param.Name] = struct{}{} 86 } 87 88 cmd.Flags().VisitAll(func(flag *pflag.Flag) { 89 if _, found := generatorParams[flag.Name]; !found { 90 // This flag is not used by the current generator 91 // so skip it. 92 return 93 } 94 if flag.Annotations == nil { 95 flag.Annotations = map[string][]string{} 96 } 97 if annotations := flag.Annotations["generator"]; annotations == nil { 98 flag.Annotations["generator"] = []string{} 99 } 100 flag.Annotations["generator"] = append(flag.Annotations["generator"], name) 101 }) 102 } 103} 104 105// EnsureFlagsValid ensures that no invalid flags are being used against a 106func EnsureFlagsValid(cmd *cobra.Command, generators map[string]Generator, generatorInUse string) error { 107 AnnotateFlags(cmd, generators) 108 109 allErrs := []error{} 110 cmd.Flags().VisitAll(func(flag *pflag.Flag) { 111 // If the flag hasn't changed, don't validate it. 112 if !flag.Changed { 113 return 114 } 115 // Look into the flag annotations for the generators that can use it. 116 if annotations := flag.Annotations["generator"]; len(annotations) > 0 { 117 annotationMap := map[string]struct{}{} 118 for _, ann := range annotations { 119 annotationMap[ann] = struct{}{} 120 } 121 // If the current generator is not annotated, then this flag shouldn't 122 // be used with it. 123 if _, found := annotationMap[generatorInUse]; !found { 124 allErrs = append(allErrs, fmt.Errorf("cannot use --%s with --generator=%s", flag.Name, generatorInUse)) 125 } 126 } 127 }) 128 return utilerrors.NewAggregate(allErrs) 129} 130 131// MakeParams is a utility that creates generator parameters from a command line 132func MakeParams(cmd *cobra.Command, params []GeneratorParam) map[string]interface{} { 133 result := map[string]interface{}{} 134 for ix := range params { 135 f := cmd.Flags().Lookup(params[ix].Name) 136 if f != nil { 137 result[params[ix].Name] = f.Value.String() 138 } 139 } 140 return result 141} 142 143func MakeProtocols(protocols map[string]string) string { 144 out := []string{} 145 for key, value := range protocols { 146 out = append(out, fmt.Sprintf("%s/%s", key, value)) 147 } 148 return strings.Join(out, ",") 149} 150 151func ParseProtocols(protocols interface{}) (map[string]string, error) { 152 protocolsString, isString := protocols.(string) 153 if !isString { 154 return nil, fmt.Errorf("expected string, found %v", protocols) 155 } 156 if len(protocolsString) == 0 { 157 return nil, fmt.Errorf("no protocols passed") 158 } 159 portProtocolMap := map[string]string{} 160 protocolsSlice := strings.Split(protocolsString, ",") 161 for ix := range protocolsSlice { 162 portProtocol := strings.Split(protocolsSlice[ix], "/") 163 if len(portProtocol) != 2 { 164 return nil, fmt.Errorf("unexpected port protocol mapping: %s", protocolsSlice[ix]) 165 } 166 if len(portProtocol[0]) == 0 { 167 return nil, fmt.Errorf("unexpected empty port") 168 } 169 if len(portProtocol[1]) == 0 { 170 return nil, fmt.Errorf("unexpected empty protocol") 171 } 172 portProtocolMap[portProtocol[0]] = portProtocol[1] 173 } 174 return portProtocolMap, nil 175} 176 177// ParseLabels turns a string representation of a label set into a map[string]string 178func ParseLabels(labelSpec interface{}) (map[string]string, error) { 179 labelString, isString := labelSpec.(string) 180 if !isString { 181 return nil, fmt.Errorf("expected string, found %v", labelSpec) 182 } 183 if len(labelString) == 0 { 184 return nil, fmt.Errorf("no label spec passed") 185 } 186 labels := map[string]string{} 187 labelSpecs := strings.Split(labelString, ",") 188 for ix := range labelSpecs { 189 labelSpec := strings.Split(labelSpecs[ix], "=") 190 if len(labelSpec) != 2 { 191 return nil, fmt.Errorf("unexpected label spec: %s", labelSpecs[ix]) 192 } 193 if len(labelSpec[0]) == 0 { 194 return nil, fmt.Errorf("unexpected empty label key") 195 } 196 labels[labelSpec[0]] = labelSpec[1] 197 } 198 return labels, nil 199} 200 201func GetBool(params map[string]string, key string, defValue bool) (bool, error) { 202 if val, found := params[key]; !found { 203 return defValue, nil 204 } else { 205 return strconv.ParseBool(val) 206 } 207} 208