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