Drop generators entirely from kubectl

Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 2e7089b46420f6d98cc67086ba6b0c1f364798d1
This commit is contained in:
Maciej Szulik 2026-04-08 17:14:35 +02:00 committed by Kubernetes Publisher
parent a174022b42
commit 3fe5e3bbf1
10 changed files with 35 additions and 2716 deletions

View file

@ -337,7 +337,7 @@ func TestRunExposeService(t *testing.T) {
input: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
},
flags: map[string]string{"selector": "svc=frompod", "port": "90", "labels": "svc=frompod", "generator": "service/v2"},
flags: map[string]string{"selector": "svc=frompod", "port": "90", "labels": "svc=frompod"},
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters"[:63], Namespace: "", Labels: map[string]string{"svc": "frompod"}},
Spec: corev1.ServiceSpec{
@ -379,7 +379,7 @@ func TestRunExposeService(t *testing.T) {
},
},
},
flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "client"},
flags: map[string]string{"selector": "svc=fromfoo", "name": "fromfoo", "dry-run": "client"},
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: corev1.ServiceSpec{
@ -438,7 +438,7 @@ func TestRunExposeService(t *testing.T) {
},
},
},
flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "client"},
flags: map[string]string{"selector": "svc=fromfoo", "name": "fromfoo", "dry-run": "client"},
output: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
Spec: corev1.ServiceSpec{

View file

@ -18,11 +18,12 @@ package set
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"k8s.io/klog/v2"
v1 "k8s.io/api/core/v1"
apiresource "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
@ -31,8 +32,8 @@ import (
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
generateversioned "k8s.io/kubectl/pkg/generate/versioned"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
@ -211,10 +212,17 @@ func (o *SetResourcesOptions) Validate() error {
return fmt.Errorf("you must specify an update to requests or limits (in the form of --requests/--limits)")
}
o.ResourceRequirements, err = generateversioned.HandleResourceRequirementsV1(map[string]string{"limits": o.Limits, "requests": o.Requests})
o.ResourceRequirements = v1.ResourceRequirements{}
limits, err := parseResourceList(o.Limits)
if err != nil {
return err
}
o.ResourceRequirements.Limits = limits
requests, err := parseResourceList(o.Requests)
if err != nil {
return err
}
o.ResourceRequirements.Requests = requests
return nil
}
@ -300,3 +308,24 @@ func (o *SetResourcesOptions) Run() error {
}
return utilerrors.NewAggregate(allErrs)
}
func parseResourceList(spec string) (v1.ResourceList, error) {
if spec == "" {
return nil, nil
}
result := v1.ResourceList{}
for resourceStatement := range strings.SplitSeq(spec, ",") {
parts := strings.Split(resourceStatement, "=")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid argument syntax %v, expected <resource>=<value>", resourceStatement)
}
resourceName := v1.ResourceName(parts[0])
resourceQuantity, err := apiresource.ParseQuantity(parts[1])
if err != nil {
return nil, err
}
result[resourceName] = resourceQuantity
}
return result, nil
}

View file

@ -906,15 +906,6 @@ func scaleClient(restClientGetter genericclioptions.RESTClientGetter) (scale.Sca
return scale.New(restClient, mapper, dynamic.LegacyAPIPathResolverFunc, resolver), nil
}
func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) {
fmt.Fprintf(cmdErr, "WARNING: New generator %q specified, "+
"but it isn't available. "+
"Falling back to %q.\n",
newGeneratorName,
oldGeneratorName,
)
}
// Difference removes any elements of subArray from fullArray and returns the result
func Difference(fullArray []string, subArray []string) []string {
exclude := make(map[string]bool, len(subArray))

View file

@ -1,207 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generate
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
// GeneratorFunc returns the generators for the provided command
type GeneratorFunc func(cmdName string) map[string]Generator
// GeneratorParam is a parameter for a generator
// TODO: facilitate structured json generator input schemes
type GeneratorParam struct {
Name string
Required bool
}
// Generator is an interface for things that can generate API objects from input
// parameters. One example is the "expose" generator that is capable of exposing
// new replication controllers and services, among other things.
type Generator interface {
// Generate creates an API object given a set of parameters
Generate(params map[string]interface{}) (runtime.Object, error)
// ParamNames returns the list of parameters that this generator uses
ParamNames() []GeneratorParam
}
// StructuredGenerator is an interface for things that can generate API objects not using parameter injection
type StructuredGenerator interface {
// StructuredGenerator creates an API object using pre-configured parameters
StructuredGenerate() (runtime.Object, error)
}
func IsZero(i interface{}) bool {
if i == nil {
return true
}
return reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface())
}
// ValidateParams ensures that all required params are present in the params map
func ValidateParams(paramSpec []GeneratorParam, params map[string]interface{}) error {
allErrs := []error{}
for ix := range paramSpec {
if paramSpec[ix].Required {
value, found := params[paramSpec[ix].Name]
if !found || IsZero(value) {
allErrs = append(allErrs, fmt.Errorf("Parameter: %s is required", paramSpec[ix].Name))
}
}
}
return utilerrors.NewAggregate(allErrs)
}
// AnnotateFlags annotates all flags that are used by generators.
func AnnotateFlags(cmd *cobra.Command, generators map[string]Generator) {
// Iterate over all generators and mark any flags used by them.
for name, generator := range generators {
generatorParams := map[string]struct{}{}
for _, param := range generator.ParamNames() {
generatorParams[param.Name] = struct{}{}
}
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if _, found := generatorParams[flag.Name]; !found {
// This flag is not used by the current generator
// so skip it.
return
}
if flag.Annotations == nil {
flag.Annotations = map[string][]string{}
}
if annotations := flag.Annotations["generator"]; annotations == nil {
flag.Annotations["generator"] = []string{}
}
flag.Annotations["generator"] = append(flag.Annotations["generator"], name)
})
}
}
// EnsureFlagsValid ensures that no invalid flags are being used against a
func EnsureFlagsValid(cmd *cobra.Command, generators map[string]Generator, generatorInUse string) error {
AnnotateFlags(cmd, generators)
allErrs := []error{}
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
// If the flag hasn't changed, don't validate it.
if !flag.Changed {
return
}
// Look into the flag annotations for the generators that can use it.
if annotations := flag.Annotations["generator"]; len(annotations) > 0 {
annotationMap := map[string]struct{}{}
for _, ann := range annotations {
annotationMap[ann] = struct{}{}
}
// If the current generator is not annotated, then this flag shouldn't
// be used with it.
if _, found := annotationMap[generatorInUse]; !found {
allErrs = append(allErrs, fmt.Errorf("cannot use --%s with --generator=%s", flag.Name, generatorInUse))
}
}
})
return utilerrors.NewAggregate(allErrs)
}
// MakeParams is a utility that creates generator parameters from a command line
func MakeParams(cmd *cobra.Command, params []GeneratorParam) map[string]interface{} {
result := map[string]interface{}{}
for ix := range params {
f := cmd.Flags().Lookup(params[ix].Name)
if f != nil {
result[params[ix].Name] = f.Value.String()
}
}
return result
}
func MakeProtocols(protocols map[string]string) string {
out := []string{}
for key, value := range protocols {
out = append(out, fmt.Sprintf("%s/%s", key, value))
}
return strings.Join(out, ",")
}
func ParseProtocols(protocols interface{}) (map[string]string, error) {
protocolsString, isString := protocols.(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", protocols)
}
if len(protocolsString) == 0 {
return nil, fmt.Errorf("no protocols passed")
}
portProtocolMap := map[string]string{}
protocolsSlice := strings.Split(protocolsString, ",")
for ix := range protocolsSlice {
portProtocol := strings.Split(protocolsSlice[ix], "/")
if len(portProtocol) != 2 {
return nil, fmt.Errorf("unexpected port protocol mapping: %s", protocolsSlice[ix])
}
if len(portProtocol[0]) == 0 {
return nil, fmt.Errorf("unexpected empty port")
}
if len(portProtocol[1]) == 0 {
return nil, fmt.Errorf("unexpected empty protocol")
}
portProtocolMap[portProtocol[0]] = portProtocol[1]
}
return portProtocolMap, nil
}
// ParseLabels turns a string representation of a label set into a map[string]string
func ParseLabels(labelSpec interface{}) (map[string]string, error) {
labelString, isString := labelSpec.(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", labelSpec)
}
if len(labelString) == 0 {
return nil, fmt.Errorf("no label spec passed")
}
labels := map[string]string{}
labelSpecs := strings.Split(labelString, ",")
for ix := range labelSpecs {
labelSpec := strings.Split(labelSpecs[ix], "=")
if len(labelSpec) != 2 {
return nil, fmt.Errorf("unexpected label spec: %s", labelSpecs[ix])
}
if len(labelSpec[0]) == 0 {
return nil, fmt.Errorf("unexpected empty label key")
}
labels[labelSpec[0]] = labelSpec[1]
}
return labels, nil
}
func GetBool(params map[string]string, key string, defValue bool) (bool, error) {
if val, found := params[key]; !found {
return defValue, nil
} else {
return strconv.ParseBool(val)
}
}

View file

@ -1,444 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generate
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/spf13/cobra"
)
type TestStruct struct {
val int
}
func TestIsZero(t *testing.T) {
tests := []struct {
name string
val interface{}
expectZero bool
}{
{
name: "test1",
val: "",
expectZero: true,
},
{
name: "test2",
val: nil,
expectZero: true,
},
{
name: "test3",
val: 0,
expectZero: true,
},
{
name: "test4",
val: TestStruct{},
expectZero: true,
},
{
name: "test5",
val: "foo",
expectZero: false,
},
{
name: "test6",
val: 1,
expectZero: false,
},
{
name: "test7",
val: TestStruct{val: 2},
expectZero: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output := IsZero(tt.val)
if output != tt.expectZero {
t.Errorf("expected: %v, saw %v", tt.expectZero, output)
}
})
}
}
func TestValidateParams(t *testing.T) {
tests := []struct {
name string
paramSpec []GeneratorParam
params map[string]interface{}
valid bool
}{
{
name: "test1",
paramSpec: []GeneratorParam{},
params: map[string]interface{}{},
valid: true,
},
{
name: "test2",
paramSpec: []GeneratorParam{
{Name: "foo"},
},
params: map[string]interface{}{},
valid: true,
},
{
name: "test3",
paramSpec: []GeneratorParam{
{Name: "foo", Required: true},
},
params: map[string]interface{}{
"foo": "bar",
},
valid: true,
},
{
name: "test4",
paramSpec: []GeneratorParam{
{Name: "foo", Required: true},
},
params: map[string]interface{}{
"baz": "blah",
"foo": "bar",
},
valid: true,
},
{
name: "test5",
paramSpec: []GeneratorParam{
{Name: "foo", Required: true},
{Name: "baz", Required: true},
},
params: map[string]interface{}{
"baz": "blah",
"foo": "bar",
},
valid: true,
},
{
name: "test6",
paramSpec: []GeneratorParam{
{Name: "foo", Required: true},
{Name: "baz", Required: true},
},
params: map[string]interface{}{
"foo": "bar",
},
valid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateParams(tt.paramSpec, tt.params)
if tt.valid && err != nil {
t.Errorf("unexpected error: %v", err)
}
if !tt.valid && err == nil {
t.Errorf("unexpected non-error")
}
})
}
}
func TestMakeParams(t *testing.T) {
cmd := &cobra.Command{}
cmd.Flags().String("foo", "bar", "")
cmd.Flags().String("baz", "", "")
cmd.Flags().Set("baz", "blah")
paramSpec := []GeneratorParam{
{Name: "foo", Required: true},
{Name: "baz", Required: true},
}
expected := map[string]interface{}{
"foo": "bar",
"baz": "blah",
}
params := MakeParams(cmd, paramSpec)
if !reflect.DeepEqual(params, expected) {
t.Errorf("\nexpected:\n%v\nsaw:\n%v", expected, params)
}
}
func TestGetBool(t *testing.T) {
testCases := []struct {
name string
parameters map[string]string
key string
defaultValue bool
expected bool
expectError bool
}{
{
name: "found key in parameters, default value is different from key value",
parameters: map[string]string{
"foo": "false",
},
key: "foo",
defaultValue: false,
expected: false,
expectError: false,
},
{
name: "found key in parameters, default value is same with key value",
parameters: map[string]string{
"foo": "true",
},
key: "foo",
defaultValue: true,
expected: true,
expectError: false,
},
{
name: "key not found in parameters, default value is true",
parameters: map[string]string{
"foo": "true",
"far": "false",
},
key: "bar",
defaultValue: true,
expected: true,
expectError: false,
},
{
name: "key not found in parameters, default value is false",
parameters: map[string]string{
"foo": "true",
"far": "false",
},
key: "bar",
defaultValue: false,
expected: false,
expectError: false,
},
{
name: "parameters is empty",
parameters: map[string]string{},
key: "foo",
defaultValue: true,
expected: true,
expectError: false,
},
{
name: "parameters key is not a valid bool value",
parameters: map[string]string{
"foo": "error",
},
key: "foo",
defaultValue: true,
expected: false,
expectError: true,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
got, err := GetBool(tt.parameters, tt.key, tt.defaultValue)
if err != nil && !tt.expectError {
t.Errorf("%s: unexpected error: %v", tt.name, err)
}
if err == nil && tt.expectError {
t.Errorf("%s: expect error, got nil", tt.name)
}
if got != tt.expected {
t.Errorf("%s: expect %v, got %v", tt.name, tt.expected, got)
}
})
}
}
func makeLabels(labels map[string]string) string {
out := []string{}
for key, value := range labels {
out = append(out, fmt.Sprintf("%s=%s", key, value))
}
return strings.Join(out, ",")
}
func TestMakeParseLabels(t *testing.T) {
successCases := []struct {
name string
labels map[string]string
expected map[string]string
}{
{
name: "test1",
labels: map[string]string{
"foo": "false",
},
expected: map[string]string{
"foo": "false",
},
},
{
name: "test2",
labels: map[string]string{
"foo": "true",
"bar": "123",
},
expected: map[string]string{
"foo": "true",
"bar": "123",
},
},
}
for _, tt := range successCases {
t.Run(tt.name, func(t *testing.T) {
labelString := makeLabels(tt.labels)
got, err := ParseLabels(labelString)
if err != nil {
t.Errorf("unexpected error :%v", err)
}
if !reflect.DeepEqual(tt.expected, got) {
t.Errorf("\nexpected:\n%v\ngot:\n%v", tt.expected, got)
}
})
}
errorCases := []struct {
name string
labels interface{}
}{
{
name: "non-string",
labels: 123,
},
{
name: "empty string",
labels: "",
},
{
name: "error format",
labels: "abc=456;bcd=789",
},
{
name: "error format",
labels: "abc=456.bcd=789",
},
{
name: "error format",
labels: "abc,789",
},
{
name: "error format",
labels: "abc",
},
{
name: "error format",
labels: "=abc",
},
}
for _, test := range errorCases {
_, err := ParseLabels(test.labels)
if err == nil {
t.Errorf("labels %s expect error, reason: %s, got nil", test.labels, test.name)
}
}
}
func TestMakeParseProtocols(t *testing.T) {
successCases := []struct {
name string
protocols map[string]string
expected map[string]string
}{
{
name: "test1",
protocols: map[string]string{
"101": "TCP",
},
expected: map[string]string{
"101": "TCP",
},
},
{
name: "test2",
protocols: map[string]string{
"102": "UDP",
"101": "TCP",
"103": "SCTP",
},
expected: map[string]string{
"102": "UDP",
"101": "TCP",
"103": "SCTP",
},
},
}
for _, tt := range successCases {
t.Run(tt.name, func(t *testing.T) {
protocolString := MakeProtocols(tt.protocols)
got, err := ParseProtocols(protocolString)
if err != nil {
t.Errorf("unexpected error :%v", err)
}
if !reflect.DeepEqual(tt.expected, got) {
t.Errorf("\nexpected:\n%v\ngot:\n%v", tt.expected, got)
}
})
}
errorCases := []struct {
name string
protocols interface{}
}{
{
name: "non-string",
protocols: 123,
},
{
name: "empty string",
protocols: "",
},
{
name: "error format",
protocols: "123/TCP;456/UDP",
},
{
name: "error format",
protocols: "123/TCP.456/UDP",
},
{
name: "error format",
protocols: "123=456",
},
{
name: "error format",
protocols: "123",
},
{
name: "error format",
protocols: "123=",
},
{
name: "error format",
protocols: "=TCP",
},
}
for _, test := range errorCases {
_, err := ParseProtocols(test.protocols)
if err == nil {
t.Errorf("protocols %s expect error, reason: %s, got nil", test.protocols, test.name)
}
}
}

View file

@ -1,49 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package versioned
import (
"k8s.io/kubectl/pkg/generate"
)
// GeneratorFn gives a way to easily override the function for unit testing if needed
var GeneratorFn generate.GeneratorFunc = DefaultGenerators
const (
// TODO(sig-cli): Enforce consistent naming for generators here.
// See discussion in https://github.com/kubernetes/kubernetes/issues/46237
// before you add any more.
RunPodV1GeneratorName = "run-pod/v1"
ServiceV2GeneratorName = "service/v2"
)
// DefaultGenerators returns the set of default generators for use in Factory instances
func DefaultGenerators(cmdName string) map[string]generate.Generator {
var generator map[string]generate.Generator
switch cmdName {
case "expose":
generator = map[string]generate.Generator{
ServiceV2GeneratorName: ServiceGeneratorV2{},
}
case "run":
generator = map[string]generate.Generator{
RunPodV1GeneratorName: BasicPod{},
}
}
return generator
}

View file

@ -1,379 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package versioned
import (
"fmt"
"strconv"
"strings"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/generate"
)
// getLabels returns map of labels.
func getLabels(params map[string]string, name string) (map[string]string, error) {
labelString, found := params["labels"]
var labels map[string]string
var err error
if found && len(labelString) > 0 {
labels, err = generate.ParseLabels(labelString)
if err != nil {
return nil, err
}
} else {
labels = map[string]string{
"run": name,
}
}
return labels, nil
}
// getName returns the name of newly created resource.
func getName(params map[string]string) (string, error) {
name, found := params["name"]
if !found || len(name) == 0 {
name, found = params["default-name"]
if !found || len(name) == 0 {
return "", fmt.Errorf("'name' is a required parameter")
}
}
return name, nil
}
// getParams returns map of generic parameters.
func getParams(genericParams map[string]interface{}) (map[string]string, error) {
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
return params, nil
}
// getArgs returns arguments for the container command.
func getArgs(genericParams map[string]interface{}) ([]string, error) {
args := []string{}
val, found := genericParams["args"]
if found {
var isArray bool
args, isArray = val.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found: %v", val)
}
delete(genericParams, "args")
}
return args, nil
}
// getAnnotations returns map of annotations.
func getAnnotations(genericParams map[string]interface{}) (map[string]string, error) {
annotationStrings, ok := genericParams["annotations"]
if !ok {
return nil, nil
}
annotationStringArray, ok := annotationStrings.([]string)
if !ok {
return nil, fmt.Errorf("expected []string, found: %v", annotationStrings)
}
annotations, _, err := cmdutil.ParsePairs(annotationStringArray, "annotations", false)
if err != nil {
return nil, err
}
delete(genericParams, "annotations")
return annotations, nil
}
// getEnvs returns environment variables.
func getEnvs(genericParams map[string]interface{}) ([]v1.EnvVar, error) {
var envs []v1.EnvVar
envStrings, found := genericParams["env"]
if found {
if envStringArray, isArray := envStrings.([]string); isArray {
var err error
envs, err = parseEnvs(envStringArray)
if err != nil {
return nil, err
}
delete(genericParams, "env")
} else {
return nil, fmt.Errorf("expected []string, found: %v", envStrings)
}
}
return envs, nil
}
// populateResourceListV1 takes strings of form <resourceName1>=<value1>,<resourceName1>=<value2>
// and returns ResourceList.
func populateResourceListV1(spec string) (v1.ResourceList, error) {
// empty input gets a nil response to preserve generator test expected behaviors
if spec == "" {
return nil, nil
}
result := v1.ResourceList{}
resourceStatements := strings.Split(spec, ",")
for _, resourceStatement := range resourceStatements {
parts := strings.Split(resourceStatement, "=")
if len(parts) != 2 {
return nil, fmt.Errorf("Invalid argument syntax %v, expected <resource>=<value>", resourceStatement)
}
resourceName := v1.ResourceName(parts[0])
resourceQuantity, err := resource.ParseQuantity(parts[1])
if err != nil {
return nil, err
}
result[resourceName] = resourceQuantity
}
return result, nil
}
// HandleResourceRequirementsV1 parses the limits and requests parameters if specified
// and returns ResourceRequirements.
func HandleResourceRequirementsV1(params map[string]string) (v1.ResourceRequirements, error) {
result := v1.ResourceRequirements{}
limits, err := populateResourceListV1(params["limits"])
if err != nil {
return result, err
}
result.Limits = limits
requests, err := populateResourceListV1(params["requests"])
if err != nil {
return result, err
}
result.Requests = requests
return result, nil
}
// updatePodContainers updates PodSpec.Containers with passed parameters.
func updatePodContainers(params map[string]string, args []string, envs []v1.EnvVar, imagePullPolicy v1.PullPolicy, podSpec *v1.PodSpec) error {
if len(args) > 0 {
command, err := generate.GetBool(params, "command", false)
if err != nil {
return err
}
if command {
podSpec.Containers[0].Command = args
} else {
podSpec.Containers[0].Args = args
}
}
if len(envs) > 0 {
podSpec.Containers[0].Env = envs
}
if len(imagePullPolicy) > 0 {
// imagePullPolicy should be valid here since we have verified it before.
podSpec.Containers[0].ImagePullPolicy = imagePullPolicy
}
return nil
}
// updatePodContainers updates PodSpec.Containers.Ports with passed parameters.
func updatePodPorts(params map[string]string, podSpec *v1.PodSpec) (err error) {
port := -1
hostPort := -1
if len(params["port"]) > 0 {
port, err = strconv.Atoi(params["port"])
if err != nil {
return err
}
}
if len(params["hostport"]) > 0 {
hostPort, err = strconv.Atoi(params["hostport"])
if err != nil {
return err
}
if hostPort > 0 && port < 0 {
return fmt.Errorf("--hostport requires --port to be specified")
}
}
// Don't include the port if it was not specified.
if len(params["port"]) > 0 {
podSpec.Containers[0].Ports = []v1.ContainerPort{
{
ContainerPort: int32(port),
},
}
if hostPort > 0 {
podSpec.Containers[0].Ports[0].HostPort = int32(hostPort)
}
}
return nil
}
type BasicPod struct{}
func (BasicPod) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "labels", Required: false},
{Name: "annotations", Required: false},
{Name: "default-name", Required: false},
{Name: "name", Required: true},
{Name: "image", Required: true},
{Name: "image-pull-policy", Required: false},
{Name: "port", Required: false},
{Name: "hostport", Required: false},
{Name: "stdin", Required: false},
{Name: "leave-stdin-open", Required: false},
{Name: "tty", Required: false},
{Name: "restart", Required: false},
{Name: "command", Required: false},
{Name: "args", Required: false},
{Name: "env", Required: false},
{Name: "requests", Required: false},
{Name: "limits", Required: false},
{Name: "serviceaccount", Required: false},
{Name: "privileged", Required: false},
}
}
func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
envs, err := getEnvs(genericParams)
if err != nil {
return nil, err
}
annotations, err := getAnnotations(genericParams)
if err != nil {
return nil, err
}
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
name, err := getName(params)
if err != nil {
return nil, err
}
labels, err := getLabels(params, name)
if err != nil {
return nil, err
}
stdin, err := generate.GetBool(params, "stdin", false)
if err != nil {
return nil, err
}
leaveStdinOpen, err := generate.GetBool(params, "leave-stdin-open", false)
if err != nil {
return nil, err
}
tty, err := generate.GetBool(params, "tty", false)
if err != nil {
return nil, err
}
resourceRequirements, err := HandleResourceRequirementsV1(params)
if err != nil {
return nil, err
}
restartPolicy := v1.RestartPolicy(params["restart"])
if len(restartPolicy) == 0 {
restartPolicy = v1.RestartPolicyAlways
}
privileged, err := generate.GetBool(params, "privileged", false)
if err != nil {
return nil, err
}
var securityContext *v1.SecurityContext
if privileged {
securityContext = &v1.SecurityContext{
Privileged: &privileged,
}
}
pod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
Annotations: annotations,
},
Spec: v1.PodSpec{
ServiceAccountName: params["serviceaccount"],
Containers: []v1.Container{
{
Name: name,
Image: params["image"],
Stdin: stdin,
StdinOnce: !leaveStdinOpen && stdin,
TTY: tty,
Resources: resourceRequirements,
SecurityContext: securityContext,
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: restartPolicy,
},
}
imagePullPolicy := v1.PullPolicy(params["image-pull-policy"])
if err = updatePodContainers(params, args, envs, imagePullPolicy, &pod.Spec); err != nil {
return nil, err
}
if err := updatePodPorts(params, &pod.Spec); err != nil {
return nil, err
}
return &pod, nil
}
// parseEnvs converts string into EnvVar objects.
func parseEnvs(envArray []string) ([]v1.EnvVar, error) {
envs := make([]v1.EnvVar, 0, len(envArray))
for _, env := range envArray {
pos := strings.Index(env, "=")
if pos == -1 {
return nil, fmt.Errorf("invalid env: %v", env)
}
name := env[:pos]
value := env[pos+1:]
if len(name) == 0 {
return nil, fmt.Errorf("invalid env: %v", env)
}
if len(validation.IsEnvVarName(name)) != 0 {
return nil, fmt.Errorf("invalid env: %v", env)
}
envVar := v1.EnvVar{Name: name, Value: value}
envs = append(envs, envVar)
}
return envs, nil
}

View file

@ -1,419 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package versioned
import (
"reflect"
"testing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestGeneratePod(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
expected *v1.Pod
expectErr bool
}{
{
name: "test1",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"port": "",
},
expected: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"run": "foo"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "someimage",
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: v1.RestartPolicyAlways,
},
},
},
{
name: "test2",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"env": []string{"a", "c"},
},
expected: nil,
expectErr: true,
},
{
name: "test3",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"image-pull-policy": "Always",
"env": []string{"a=b", "c=d"},
},
expected: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"run": "foo"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "someimage",
ImagePullPolicy: v1.PullAlways,
Env: []v1.EnvVar{
{
Name: "a",
Value: "b",
},
{
Name: "c",
Value: "d",
},
},
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: v1.RestartPolicyAlways,
},
},
},
{
name: "test4",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"port": "80",
},
expected: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"run": "foo"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "someimage",
Ports: []v1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: v1.RestartPolicyAlways,
},
},
},
{
name: "test5",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"port": "80",
"hostport": "80",
},
expected: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"run": "foo"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "someimage",
Ports: []v1.ContainerPort{
{
ContainerPort: 80,
HostPort: 80,
},
},
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: v1.RestartPolicyAlways,
},
},
},
{
name: "test6",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"hostport": "80",
},
expected: nil,
expectErr: true,
},
{
name: "test7",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"replicas": "1",
"labels": "foo=bar,baz=blah",
},
expected: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar", "baz": "blah"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "someimage",
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: v1.RestartPolicyAlways,
},
},
},
{
name: "test8",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"replicas": "1",
"labels": "foo=bar,baz=blah",
"stdin": "true",
},
expected: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar", "baz": "blah"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "someimage",
Stdin: true,
StdinOnce: true,
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: v1.RestartPolicyAlways,
},
},
},
{
name: "test9",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"replicas": "1",
"labels": "foo=bar,baz=blah",
"stdin": "true",
"leave-stdin-open": "true",
},
expected: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar", "baz": "blah"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "someimage",
Stdin: true,
StdinOnce: false,
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: v1.RestartPolicyAlways,
},
},
},
{
name: "test10: privileged mode",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"replicas": "1",
"privileged": "true",
},
expected: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"run": "foo"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "someimage",
SecurityContext: securityContextWithPrivilege(true),
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: v1.RestartPolicyAlways,
},
},
},
{
name: "test11: check annotations",
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"replicas": "1",
"labels": "foo=bar,baz=blah",
"annotations": []string{"foo=bar1", "baz=blah1"},
},
expected: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar", "baz": "blah"},
Annotations: map[string]string{"foo": "bar1", "baz": "blah1"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "someimage",
},
},
DNSPolicy: v1.DNSClusterFirst,
RestartPolicy: v1.RestartPolicyAlways,
},
},
},
}
generator := BasicPod{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
obj, err := generator.Generate(tt.params)
if !tt.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
if tt.expectErr && err != nil {
return
}
if !reflect.DeepEqual(obj.(*v1.Pod), tt.expected) {
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", tt.expected, obj.(*v1.Pod))
}
})
}
}
func TestParseEnv(t *testing.T) {
tests := []struct {
name string
envArray []string
expected []v1.EnvVar
expectErr bool
test string
}{
{
name: "test1",
envArray: []string{
"THIS_ENV=isOK",
"this.dotted.env=isOKToo",
"HAS_COMMAS=foo,bar",
"HAS_EQUALS=jJnro54iUu75xNy==",
},
expected: []v1.EnvVar{
{
Name: "THIS_ENV",
Value: "isOK",
},
{
Name: "this.dotted.env",
Value: "isOKToo",
},
{
Name: "HAS_COMMAS",
Value: "foo,bar",
},
{
Name: "HAS_EQUALS",
Value: "jJnro54iUu75xNy==",
},
},
expectErr: false,
test: "test case 1",
},
{
name: "test2",
envArray: []string{
"WITH_OUT_EQUALS",
},
expected: []v1.EnvVar{},
expectErr: true,
test: "test case 2",
},
{
name: "test3",
envArray: []string{
"WITH_OUT_VALUES=",
},
expected: []v1.EnvVar{
{
Name: "WITH_OUT_VALUES",
Value: "",
},
},
expectErr: false,
test: "test case 3",
},
{
name: "test4",
envArray: []string{
"=WITH_OUT_NAME",
},
expected: []v1.EnvVar{},
expectErr: true,
test: "test case 4",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
envs, err := parseEnvs(tt.envArray)
if !tt.expectErr && err != nil {
t.Errorf("unexpected error: %v (%s)", err, tt.test)
}
if tt.expectErr && err != nil {
return
}
if !reflect.DeepEqual(envs, tt.expected) {
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v (%s)", tt.expected, envs, tt.test)
}
})
}
}
func securityContextWithPrivilege(privileged bool) *v1.SecurityContext {
return &v1.SecurityContext{
Privileged: &privileged,
}
}

View file

@ -1,240 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package versioned
import (
"fmt"
"strconv"
"strings"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubectl/pkg/generate"
)
// The only difference between ServiceGeneratorV1 and V2 is that the service port is named "default" in V1, while it is left unnamed in V2.
type ServiceGeneratorV1 struct{}
func (ServiceGeneratorV1) ParamNames() []generate.GeneratorParam {
return paramNames()
}
func (ServiceGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
params["port-name"] = "default"
return generateService(params)
}
type ServiceGeneratorV2 struct{}
func (ServiceGeneratorV2) ParamNames() []generate.GeneratorParam {
return paramNames()
}
func (ServiceGeneratorV2) Generate(params map[string]interface{}) (runtime.Object, error) {
return generateService(params)
}
func paramNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "default-name", Required: true},
{Name: "name", Required: false},
{Name: "selector", Required: true},
// port will be used if a user specifies --port OR the exposed object
// has one port
{Name: "port", Required: false},
// ports will be used iff a user doesn't specify --port AND the
// exposed object has multiple ports
{Name: "ports", Required: false},
{Name: "labels", Required: false},
{Name: "external-ip", Required: false},
{Name: "load-balancer-ip", Required: false},
{Name: "type", Required: false},
{Name: "protocol", Required: false},
// protocols will be used to keep port-protocol mapping derived from
// exposed object
{Name: "protocols", Required: false},
{Name: "container-port", Required: false}, // alias of target-port
{Name: "target-port", Required: false},
{Name: "port-name", Required: false},
{Name: "session-affinity", Required: false},
{Name: "cluster-ip", Required: false},
}
}
func generateService(genericParams map[string]interface{}) (runtime.Object, error) {
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
selectorString, found := params["selector"]
if !found || len(selectorString) == 0 {
return nil, fmt.Errorf("'selector' is a required parameter")
}
selector, err := generate.ParseLabels(selectorString)
if err != nil {
return nil, err
}
labelsString, found := params["labels"]
var labels map[string]string
if found && len(labelsString) > 0 {
labels, err = generate.ParseLabels(labelsString)
if err != nil {
return nil, err
}
}
name, found := params["name"]
if !found || len(name) == 0 {
name, found = params["default-name"]
if !found || len(name) == 0 {
return nil, fmt.Errorf("'name' is a required parameter")
}
}
isHeadlessService := params["cluster-ip"] == "None"
ports := []v1.ServicePort{}
servicePortName, found := params["port-name"]
if !found {
// Leave the port unnamed.
servicePortName = ""
}
protocolsString, found := params["protocols"]
var portProtocolMap map[string]string
if found && len(protocolsString) > 0 {
portProtocolMap, err = generate.ParseProtocols(protocolsString)
if err != nil {
return nil, err
}
}
// ports takes precedence over port since it will be
// specified only when the user hasn't specified a port
// via --port and the exposed object has multiple ports.
var portString string
if portString, found = params["ports"]; !found {
portString, found = params["port"]
if !found && !isHeadlessService {
return nil, fmt.Errorf("'ports' or 'port' is a required parameter")
}
}
if portString != "" {
portStringSlice := strings.Split(portString, ",")
for i, stillPortString := range portStringSlice {
port, err := strconv.Atoi(stillPortString)
if err != nil {
return nil, err
}
name := servicePortName
// If we are going to assign multiple ports to a service, we need to
// generate a different name for each one.
if len(portStringSlice) > 1 {
name = fmt.Sprintf("port-%d", i+1)
}
protocol := params["protocol"]
switch {
case len(protocol) == 0 && len(portProtocolMap) == 0:
// Default to TCP, what the flag was doing previously.
protocol = "TCP"
case len(protocol) > 0 && len(portProtocolMap) > 0:
// User has specified the --protocol while exposing a multiprotocol resource
// We should stomp multiple protocols with the one specified ie. do nothing
case len(protocol) == 0 && len(portProtocolMap) > 0:
// no --protocol and we expose a multiprotocol resource
protocol = "TCP" // have the default so we can stay sane
if exposeProtocol, found := portProtocolMap[stillPortString]; found {
protocol = exposeProtocol
}
}
ports = append(ports, v1.ServicePort{
Name: name,
Port: int32(port),
Protocol: v1.Protocol(protocol),
})
}
}
service := v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: v1.ServiceSpec{
Selector: selector,
Ports: ports,
},
}
targetPortString := params["target-port"]
if len(targetPortString) == 0 {
targetPortString = params["container-port"]
}
if len(targetPortString) > 0 {
var targetPort intstr.IntOrString
if portNum, err := strconv.Atoi(targetPortString); err != nil {
targetPort = intstr.FromString(targetPortString)
} else {
targetPort = intstr.FromInt32(int32(portNum))
}
// Use the same target-port for every port
for i := range service.Spec.Ports {
service.Spec.Ports[i].TargetPort = targetPort
}
} else {
// If --target-port or --container-port haven't been specified, this
// should be the same as Port
for i := range service.Spec.Ports {
port := service.Spec.Ports[i].Port
service.Spec.Ports[i].TargetPort = intstr.FromInt32(port)
}
}
if len(params["external-ip"]) > 0 {
service.Spec.ExternalIPs = []string{params["external-ip"]}
}
if len(params["type"]) != 0 {
service.Spec.Type = v1.ServiceType(params["type"])
}
if service.Spec.Type == v1.ServiceTypeLoadBalancer {
service.Spec.LoadBalancerIP = params["load-balancer-ip"]
}
if len(params["session-affinity"]) != 0 {
switch v1.ServiceAffinity(params["session-affinity"]) {
case v1.ServiceAffinityNone:
service.Spec.SessionAffinity = v1.ServiceAffinityNone
case v1.ServiceAffinityClientIP:
service.Spec.SessionAffinity = v1.ServiceAffinityClientIP
default:
return nil, fmt.Errorf("unknown session affinity: %s", params["session-affinity"])
}
}
if len(params["cluster-ip"]) != 0 {
if params["cluster-ip"] == "None" {
service.Spec.ClusterIP = v1.ClusterIPNone
} else {
service.Spec.ClusterIP = params["cluster-ip"]
}
}
return &service, nil
}

View file

@ -1,963 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package versioned
import (
"reflect"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubectl/pkg/generate"
)
func TestGenerateService(t *testing.T) {
tests := []struct {
name string
generator generate.Generator
params map[string]interface{}
expected v1.Service
}{
{
name: "test1",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "TCP",
"container-port": "1234",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "TCP",
TargetPort: intstr.FromInt32(1234),
},
},
},
},
},
{
name: "test2",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "UDP",
"container-port": "foobar",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "UDP",
TargetPort: intstr.FromString("foobar"),
},
},
},
},
},
{
name: "test3",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"labels": "key1=value1,key2=value2",
"name": "test",
"port": "80",
"protocol": "TCP",
"container-port": "1234",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Labels: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "TCP",
TargetPort: intstr.FromInt32(1234),
},
},
},
},
},
{
name: "test4",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "UDP",
"container-port": "foobar",
"external-ip": "1.2.3.4",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "UDP",
TargetPort: intstr.FromString("foobar"),
},
},
ExternalIPs: []string{"1.2.3.4"},
},
},
},
{
name: "test5",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "UDP",
"container-port": "foobar",
"external-ip": "1.2.3.4",
"type": "LoadBalancer",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "UDP",
TargetPort: intstr.FromString("foobar"),
},
},
Type: v1.ServiceTypeLoadBalancer,
ExternalIPs: []string{"1.2.3.4"},
},
},
},
{
name: "test6",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "UDP",
"container-port": "foobar",
"type": string(v1.ServiceTypeNodePort),
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "UDP",
TargetPort: intstr.FromString("foobar"),
},
},
Type: v1.ServiceTypeNodePort,
},
},
},
{
name: "test7",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "UDP",
"container-port": "foobar",
"create-external-load-balancer": "true", // ignored when type is present
"type": string(v1.ServiceTypeNodePort),
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "UDP",
TargetPort: intstr.FromString("foobar"),
},
},
Type: v1.ServiceTypeNodePort,
},
},
},
{
name: "test8",
generator: ServiceGeneratorV1{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "TCP",
"container-port": "1234",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Name: "default",
Port: 80,
Protocol: "TCP",
TargetPort: intstr.FromInt32(1234),
},
},
},
},
},
{
name: "test9",
generator: ServiceGeneratorV1{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "TCP",
"container-port": "1234",
"session-affinity": "ClientIP",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Name: "default",
Port: 80,
Protocol: "TCP",
TargetPort: intstr.FromInt32(1234),
},
},
SessionAffinity: v1.ServiceAffinityClientIP,
},
},
},
{
name: "test10",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "TCP",
"container-port": "1234",
"cluster-ip": "10.10.10.10",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "TCP",
TargetPort: intstr.FromInt32(1234),
},
},
ClusterIP: "10.10.10.10",
},
},
},
{
name: "test11",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "TCP",
"container-port": "1234",
"cluster-ip": "None",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "TCP",
TargetPort: intstr.FromInt32(1234),
},
},
ClusterIP: v1.ClusterIPNone,
},
},
},
{
name: "test12",
generator: ServiceGeneratorV1{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"ports": "80,443",
"protocol": "TCP",
"container-port": "foobar",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{
{
Name: "port-1",
Port: 80,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromString("foobar"),
},
{
Name: "port-2",
Port: 443,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromString("foobar"),
},
},
},
},
},
{
name: "test13",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"ports": "80,443",
"protocol": "UDP",
"target-port": "1234",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{
{
Name: "port-1",
Port: 80,
Protocol: v1.ProtocolUDP,
TargetPort: intstr.FromInt32(1234),
},
{
Name: "port-2",
Port: 443,
Protocol: v1.ProtocolUDP,
TargetPort: intstr.FromInt32(1234),
},
},
},
},
},
{
name: "test14",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"ports": "80,443",
"protocol": "TCP",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{
{
Name: "port-1",
Port: 80,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt32(80),
},
{
Name: "port-2",
Port: 443,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt32(443),
},
},
},
},
},
{
name: "test15",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"ports": "80,8080",
"protocols": "8080/UDP",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{
{
Name: "port-1",
Port: 80,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt32(80),
},
{
Name: "port-2",
Port: 8080,
Protocol: v1.ProtocolUDP,
TargetPort: intstr.FromInt32(8080),
},
},
},
},
},
{
name: "test16",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"ports": "80,8080,8081",
"protocols": "8080/UDP,8081/TCP",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{
{
Name: "port-1",
Port: 80,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt32(80),
},
{
Name: "port-2",
Port: 8080,
Protocol: v1.ProtocolUDP,
TargetPort: intstr.FromInt32(8080),
},
{
Name: "port-3",
Port: 8081,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt32(8081),
},
},
},
},
},
{
name: "test17",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"protocol": "TCP",
"container-port": "1234",
"cluster-ip": "None",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{},
ClusterIP: v1.ClusterIPNone,
},
},
},
{
name: "test18",
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"cluster-ip": "None",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{},
ClusterIP: v1.ClusterIPNone,
},
},
},
{
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "SCTP",
"container-port": "1234",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "SCTP",
TargetPort: intstr.FromInt32(1234),
},
},
},
},
},
{
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"labels": "key1=value1,key2=value2",
"name": "test",
"port": "80",
"protocol": "SCTP",
"container-port": "1234",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Labels: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "SCTP",
TargetPort: intstr.FromInt32(1234),
},
},
},
},
},
{
generator: ServiceGeneratorV1{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "SCTP",
"container-port": "1234",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Name: "default",
Port: 80,
Protocol: "SCTP",
TargetPort: intstr.FromInt32(1234),
},
},
},
},
},
{
generator: ServiceGeneratorV1{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "SCTP",
"container-port": "1234",
"session-affinity": "ClientIP",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Name: "default",
Port: 80,
Protocol: "SCTP",
TargetPort: intstr.FromInt32(1234),
},
},
SessionAffinity: v1.ServiceAffinityClientIP,
},
},
},
{
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "SCTP",
"container-port": "1234",
"cluster-ip": "10.10.10.10",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "SCTP",
TargetPort: intstr.FromInt32(1234),
},
},
ClusterIP: "10.10.10.10",
},
},
},
{
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"port": "80",
"protocol": "SCTP",
"container-port": "1234",
"cluster-ip": "None",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{
{
Port: 80,
Protocol: "SCTP",
TargetPort: intstr.FromInt32(1234),
},
},
ClusterIP: v1.ClusterIPNone,
},
},
},
{
generator: ServiceGeneratorV1{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"ports": "80,443",
"protocol": "SCTP",
"container-port": "foobar",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{
{
Name: "port-1",
Port: 80,
Protocol: v1.ProtocolSCTP,
TargetPort: intstr.FromString("foobar"),
},
{
Name: "port-2",
Port: 443,
Protocol: v1.ProtocolSCTP,
TargetPort: intstr.FromString("foobar"),
},
},
},
},
},
{
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"ports": "80,443",
"protocol": "SCTP",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{
{
Name: "port-1",
Port: 80,
Protocol: v1.ProtocolSCTP,
TargetPort: intstr.FromInt32(80),
},
{
Name: "port-2",
Port: 443,
Protocol: v1.ProtocolSCTP,
TargetPort: intstr.FromInt32(443),
},
},
},
},
},
{
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"ports": "80,8080",
"protocols": "8080/SCTP",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{
{
Name: "port-1",
Port: 80,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt32(80),
},
{
Name: "port-2",
Port: 8080,
Protocol: v1.ProtocolSCTP,
TargetPort: intstr.FromInt32(8080),
},
},
},
},
},
{
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar",
"name": "test",
"ports": "80,8080,8081,8082",
"protocols": "8080/UDP,8081/TCP,8082/SCTP",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
Ports: []v1.ServicePort{
{
Name: "port-1",
Port: 80,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt32(80),
},
{
Name: "port-2",
Port: 8080,
Protocol: v1.ProtocolUDP,
TargetPort: intstr.FromInt32(8080),
},
{
Name: "port-3",
Port: 8081,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt32(8081),
},
{
Name: "port-4",
Port: 8082,
Protocol: v1.ProtocolSCTP,
TargetPort: intstr.FromInt32(8082),
},
},
},
},
},
{
generator: ServiceGeneratorV2{},
params: map[string]interface{}{
"selector": "foo=bar,baz=blah",
"name": "test",
"protocol": "SCTP",
"container-port": "1234",
"cluster-ip": "None",
},
expected: v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
"baz": "blah",
},
Ports: []v1.ServicePort{},
ClusterIP: v1.ClusterIPNone,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
obj, err := tt.generator.Generate(tt.params)
if !reflect.DeepEqual(obj, &tt.expected) {
t.Errorf("expected:\n%#v\ngot\n%#v\n", &tt.expected, obj)
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}