Fresh dep ensure

This commit is contained in:
Mike Cronce
2018-11-26 13:23:56 -05:00
parent 93cb8a04d7
commit 407478ab9a
9016 changed files with 551394 additions and 279685 deletions

View File

@ -0,0 +1,111 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"autoscale.go",
"clusterrolebinding.go",
"configmap.go",
"deployment.go",
"env_file.go",
"generator.go",
"namespace.go",
"pdb.go",
"priorityclass.go",
"quota.go",
"rolebinding.go",
"run.go",
"secret.go",
"secret_for_docker_registry.go",
"secret_for_tls.go",
"service.go",
"service_basic.go",
"serviceaccount.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/generate/versioned",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/generate:go_default_library",
"//pkg/kubectl/util:go_default_library",
"//pkg/kubectl/util/hash:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1beta1:go_default_library",
"//staging/src/k8s.io/api/batch/v2alpha1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
"//staging/src/k8s.io/api/policy/v1beta1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1beta1:go_default_library",
"//staging/src/k8s.io/api/scheduling/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/client-go/discovery:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"autoscale_test.go",
"clusterrolebinding_test.go",
"configmap_test.go",
"deployment_test.go",
"env_file_test.go",
"namespace_test.go",
"pdb_test.go",
"priorityclass_test.go",
"quota_test.go",
"rolebinding_test.go",
"run_test.go",
"secret_for_docker_registry_test.go",
"secret_for_tls_test.go",
"secret_test.go",
"service_basic_test.go",
"service_test.go",
"serviceaccount_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/kubectl/generate:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1beta1:go_default_library",
"//staging/src/k8s.io/api/batch/v2alpha1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
"//staging/src/k8s.io/api/policy/v1beta1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1beta1:go_default_library",
"//staging/src/k8s.io/api/scheduling/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,86 @@
/*
Copyright 2015 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"
autoscalingv1 "k8s.io/api/autoscaling/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
// HorizontalPodAutoscalerGeneratorV1 supports stable generation of a horizontal pod autoscaler.
type HorizontalPodAutoscalerGeneratorV1 struct {
Name string
ScaleRefKind string
ScaleRefName string
ScaleRefAPIVersion string
MinReplicas int32
MaxReplicas int32
CPUPercent int32
}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ generate.StructuredGenerator = &HorizontalPodAutoscalerGeneratorV1{}
// StructuredGenerate outputs a horizontal pod autoscaler object using the configured fields.
func (s *HorizontalPodAutoscalerGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
scaler := autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: s.ScaleRefKind,
Name: s.ScaleRefName,
APIVersion: s.ScaleRefAPIVersion,
},
MaxReplicas: s.MaxReplicas,
},
}
if s.MinReplicas > 0 {
v := int32(s.MinReplicas)
scaler.Spec.MinReplicas = &v
}
if s.CPUPercent >= 0 {
c := int32(s.CPUPercent)
scaler.Spec.TargetCPUUtilizationPercentage = &c
}
return &scaler, nil
}
// validate check if the caller has set the right fields.
func (s HorizontalPodAutoscalerGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if s.MaxReplicas < 1 {
return fmt.Errorf("'max' is a required parameter and must be at least 1")
}
if s.MinReplicas > s.MaxReplicas {
return fmt.Errorf("'max' must be greater than or equal to 'min'")
}
return nil
}

View File

@ -0,0 +1,130 @@
/*
Copyright 2017 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"
autoscalingv1 "k8s.io/api/autoscaling/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilpointer "k8s.io/utils/pointer"
)
func TestHPAGenerate(t *testing.T) {
tests := []struct {
name string
HPAName string
scaleRefKind string
scaleRefName string
scaleRefAPIVersion string
minReplicas int32
maxReplicas int32
CPUPercent int32
expected *autoscalingv1.HorizontalPodAutoscaler
expectErr bool
}{
{
name: "valid case",
HPAName: "foo",
minReplicas: 1,
maxReplicas: 10,
CPUPercent: 80,
scaleRefKind: "kind",
scaleRefName: "name",
scaleRefAPIVersion: "apiVersion",
expected: &autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
TargetCPUUtilizationPercentage: utilpointer.Int32Ptr(80),
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "kind",
Name: "name",
APIVersion: "apiVersion",
},
MaxReplicas: int32(10),
MinReplicas: utilpointer.Int32Ptr(1),
},
},
expectErr: false,
},
{
name: "'name' is a required parameter",
scaleRefKind: "kind",
scaleRefName: "name",
scaleRefAPIVersion: "apiVersion",
expectErr: true,
},
{
name: "'max' is a required parameter",
HPAName: "foo",
scaleRefKind: "kind",
scaleRefName: "name",
scaleRefAPIVersion: "apiVersion",
expectErr: true,
},
{
name: "'max' must be greater than or equal to 'min'",
HPAName: "foo",
minReplicas: 10,
maxReplicas: 1,
scaleRefKind: "kind",
scaleRefName: "name",
scaleRefAPIVersion: "apiVersion",
expectErr: true,
},
{
name: "'max' must be at least 1",
HPAName: "foo",
minReplicas: 1,
maxReplicas: -10,
scaleRefKind: "kind",
scaleRefName: "name",
scaleRefAPIVersion: "apiVersion",
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
generator := HorizontalPodAutoscalerGeneratorV1{
Name: tt.HPAName,
ScaleRefKind: tt.scaleRefKind,
ScaleRefName: tt.scaleRefName,
ScaleRefAPIVersion: tt.scaleRefAPIVersion,
MinReplicas: tt.minReplicas,
MaxReplicas: tt.maxReplicas,
CPUPercent: tt.CPUPercent,
}
obj, err := generator.StructuredGenerate()
if tt.expectErr && err != nil {
return
}
if !tt.expectErr && err != nil {
t.Errorf("[%s] unexpected error: %v", tt.name, err)
}
if tt.expectErr && err == nil {
t.Errorf("[%s] expect error, got nil", tt.name)
}
if !reflect.DeepEqual(obj.(*autoscalingv1.HorizontalPodAutoscaler), tt.expected) {
t.Errorf("[%s] want:\n%#v\ngot:\n%#v", tt.name, tt.expected, obj.(*autoscalingv1.HorizontalPodAutoscaler))
}
})
}
}

View File

@ -0,0 +1,159 @@
/*
Copyright 2016 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"
"strings"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
// ClusterRoleBindingGeneratorV1 supports stable generation of a clusterRoleBinding.
type ClusterRoleBindingGeneratorV1 struct {
// Name of clusterRoleBinding (required)
Name string
// ClusterRole for the clusterRoleBinding (required)
ClusterRole string
// Users to derive the clusterRoleBinding from (optional)
Users []string
// Groups to derive the clusterRoleBinding from (optional)
Groups []string
// ServiceAccounts to derive the clusterRoleBinding from in namespace:name format(optional)
ServiceAccounts []string
}
// Ensure it supports the generator pattern that uses parameter injection.
var _ generate.Generator = &ClusterRoleBindingGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ generate.StructuredGenerator = &ClusterRoleBindingGeneratorV1{}
// Generate returns a clusterRoleBinding using the specified parameters.
func (s ClusterRoleBindingGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
delegate := &ClusterRoleBindingGeneratorV1{}
userStrings, found := genericParams["user"]
if found {
fromFileArray, isArray := userStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", userStrings)
}
delegate.Users = fromFileArray
delete(genericParams, "user")
}
groupStrings, found := genericParams["group"]
if found {
fromLiteralArray, isArray := groupStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", groupStrings)
}
delegate.Groups = fromLiteralArray
delete(genericParams, "group")
}
saStrings, found := genericParams["serviceaccount"]
if found {
fromLiteralArray, isArray := saStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", saStrings)
}
delegate.ServiceAccounts = fromLiteralArray
delete(genericParams, "serviceaccount")
}
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
}
delegate.Name = params["name"]
delegate.ClusterRole = params["clusterrole"]
return delegate.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern.
func (s ClusterRoleBindingGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "clusterrole", Required: false},
{Name: "user", Required: false},
{Name: "group", Required: false},
{Name: "serviceaccount", Required: false},
}
}
// StructuredGenerate outputs a clusterRoleBinding object using the configured fields.
func (s ClusterRoleBindingGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
clusterRoleBinding := &rbacv1beta1.ClusterRoleBinding{}
clusterRoleBinding.Name = s.Name
clusterRoleBinding.RoleRef = rbacv1beta1.RoleRef{
APIGroup: rbacv1beta1.GroupName,
Kind: "ClusterRole",
Name: s.ClusterRole,
}
for _, user := range sets.NewString(s.Users...).List() {
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1beta1.Subject{
Kind: rbacv1beta1.UserKind,
APIGroup: rbacv1beta1.GroupName,
Name: user,
})
}
for _, group := range sets.NewString(s.Groups...).List() {
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1beta1.Subject{
Kind: rbacv1beta1.GroupKind,
APIGroup: rbacv1beta1.GroupName,
Name: group,
})
}
for _, sa := range sets.NewString(s.ServiceAccounts...).List() {
tokens := strings.Split(sa, ":")
if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
return nil, fmt.Errorf("serviceaccount must be <namespace>:<name>")
}
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1beta1.Subject{
Kind: rbacv1beta1.ServiceAccountKind,
APIGroup: "",
Namespace: tokens[0],
Name: tokens[1],
})
}
return clusterRoleBinding, nil
}
// validate validates required fields are set to support structured generation.
func (s ClusterRoleBindingGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.ClusterRole) == 0 {
return fmt.Errorf("clusterrole must be specified")
}
return nil
}

View File

@ -0,0 +1,231 @@
/*
Copyright 2017 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"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestClusterRoleBindingGenerate(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
expected *rbacv1beta1.ClusterRoleBinding
expectErr bool
}{
{
name: "valid case 1",
params: map[string]interface{}{
"name": "foo",
"clusterrole": "admin",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": []string{"ns1:name1"},
},
expected: &rbacv1beta1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
RoleRef: rbacv1beta1.RoleRef{
APIGroup: rbacv1beta1.GroupName,
Kind: "ClusterRole",
Name: "admin",
},
Subjects: []rbacv1beta1.Subject{
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.UserKind,
Name: "user",
},
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.GroupKind,
Name: "group",
},
{
Kind: rbacv1beta1.ServiceAccountKind,
APIGroup: "",
Namespace: "ns1",
Name: "name1",
},
},
},
expectErr: false,
},
{
name: "valid case 2",
params: map[string]interface{}{
"name": "foo",
"clusterrole": "admin",
"user": []string{"user1", "user2"},
"group": []string{"group1", "group2"},
"serviceaccount": []string{"ns1:name1", "ns2:name2"},
},
expected: &rbacv1beta1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
RoleRef: rbacv1beta1.RoleRef{
APIGroup: rbacv1beta1.GroupName,
Kind: "ClusterRole",
Name: "admin",
},
Subjects: []rbacv1beta1.Subject{
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.UserKind,
Name: "user1",
},
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.UserKind,
Name: "user2",
},
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.GroupKind,
Name: "group1",
},
{
APIGroup: rbacv1beta1.GroupName,
Kind: rbacv1beta1.GroupKind,
Name: "group2",
},
{
Kind: rbacv1beta1.ServiceAccountKind,
APIGroup: "",
Namespace: "ns1",
Name: "name1",
},
{
Kind: rbacv1beta1.ServiceAccountKind,
APIGroup: "",
Namespace: "ns2",
Name: "name2",
},
},
},
expectErr: false,
},
{
name: "valid case 3",
params: map[string]interface{}{
"name": "foo",
"clusterrole": "admin",
},
expected: &rbacv1beta1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
RoleRef: rbacv1beta1.RoleRef{
APIGroup: rbacv1beta1.GroupName,
Kind: "ClusterRole",
Name: "admin",
},
},
expectErr: false,
},
{
name: "invalid serviceaccount, expected format: <namespace:name>",
params: map[string]interface{}{
"name": "role",
"clusterrole": "admin",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": []string{"ns1-name1"},
},
expectErr: true,
},
{
name: "name must be specified",
params: map[string]interface{}{
"name": "",
"clusterrole": "admin",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": []string{"ns1:name1"},
},
expectErr: true,
},
{
name: "clusterrole must be specified",
params: map[string]interface{}{
"name": "foo",
"clusterrole": "",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": []string{"ns1:name1"},
},
expectErr: true,
},
{
name: "expected user []string",
params: map[string]interface{}{
"name": "role",
"clusterrole": "admin",
"user": "user",
"group": []string{"group"},
"serviceaccount": []string{"ns1:name1"},
},
expectErr: true,
},
{
name: "expected group []string",
params: map[string]interface{}{
"name": "role",
"clusterrole": "admin",
"user": []string{"user"},
"group": "group",
"serviceaccount": []string{"ns1:name1"},
},
expectErr: true,
},
{
name: "expected serviceaccount []string",
params: map[string]interface{}{
"name": "role",
"clusterrole": "admin",
"user": []string{"user"},
"group": []string{"group"},
"serviceaccount": "ns1",
},
expectErr: true,
},
}
generator := ClusterRoleBindingGeneratorV1{}
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
obj, err := generator.Generate(tt.params)
if !tt.expectErr && err != nil {
t.Errorf("[%d] unexpected error: %v", i, err)
}
if tt.expectErr && err != nil {
return
}
if tt.expectErr && err == nil {
t.Errorf("[%s] expect error, got nil", tt.name)
}
if !reflect.DeepEqual(obj.(*rbacv1beta1.ClusterRoleBinding), tt.expected) {
t.Errorf("\n[%s] want:\n%#v\ngot:\n%#v", tt.name, tt.expected, obj.(*rbacv1beta1.ClusterRoleBinding))
}
})
}
}

View File

@ -0,0 +1,298 @@
/*
Copyright 2016 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"
"io/ioutil"
"os"
"path"
"strings"
"unicode/utf8"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/kubectl/generate"
"k8s.io/kubernetes/pkg/kubectl/util"
"k8s.io/kubernetes/pkg/kubectl/util/hash"
)
// ConfigMapGeneratorV1 supports stable generation of a configMap.
type ConfigMapGeneratorV1 struct {
// Name of configMap (required)
Name string
// Type of configMap (optional)
Type string
// FileSources to derive the configMap from (optional)
FileSources []string
// LiteralSources to derive the configMap from (optional)
LiteralSources []string
// EnvFileSource to derive the configMap from (optional)
EnvFileSource string
// AppendHash; if true, derive a hash from the ConfigMap and append it to the name
AppendHash bool
}
// Ensure it supports the generator pattern that uses parameter injection.
var _ generate.Generator = &ConfigMapGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ generate.StructuredGenerator = &ConfigMapGeneratorV1{}
// Generate returns a configMap using the specified parameters.
func (s ConfigMapGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
delegate := &ConfigMapGeneratorV1{}
fromFileStrings, found := genericParams["from-file"]
if found {
fromFileArray, isArray := fromFileStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
}
delegate.FileSources = fromFileArray
delete(genericParams, "from-file")
}
fromLiteralStrings, found := genericParams["from-literal"]
if found {
fromLiteralArray, isArray := fromLiteralStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromLiteralStrings)
}
delegate.LiteralSources = fromLiteralArray
delete(genericParams, "from-literal")
}
fromEnvFileString, found := genericParams["from-env-file"]
if found {
fromEnvFile, isString := fromEnvFileString.(string)
if !isString {
return nil, fmt.Errorf("expected string, found :%v", fromEnvFileString)
}
delegate.EnvFileSource = fromEnvFile
delete(genericParams, "from-env-file")
}
hashParam, found := genericParams["append-hash"]
if found {
hashBool, isBool := hashParam.(bool)
if !isBool {
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
}
delegate.AppendHash = hashBool
delete(genericParams, "append-hash")
}
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
}
delegate.Name = params["name"]
delegate.Type = params["type"]
return delegate.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern.
func (s ConfigMapGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "type", Required: false},
{Name: "from-file", Required: false},
{Name: "from-literal", Required: false},
{Name: "from-env-file", Required: false},
{Name: "force", Required: false},
{Name: "hash", Required: false},
}
}
// StructuredGenerate outputs a configMap object using the configured fields.
func (s ConfigMapGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
configMap := &v1.ConfigMap{}
configMap.Name = s.Name
configMap.Data = map[string]string{}
configMap.BinaryData = map[string][]byte{}
if len(s.FileSources) > 0 {
if err := handleConfigMapFromFileSources(configMap, s.FileSources); err != nil {
return nil, err
}
}
if len(s.LiteralSources) > 0 {
if err := handleConfigMapFromLiteralSources(configMap, s.LiteralSources); err != nil {
return nil, err
}
}
if len(s.EnvFileSource) > 0 {
if err := handleConfigMapFromEnvFileSource(configMap, s.EnvFileSource); err != nil {
return nil, err
}
}
if s.AppendHash {
h, err := hash.ConfigMapHash(configMap)
if err != nil {
return nil, err
}
configMap.Name = fmt.Sprintf("%s-%s", configMap.Name, h)
}
return configMap, nil
}
// validate validates required fields are set to support structured generation.
func (s ConfigMapGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.EnvFileSource) > 0 && (len(s.FileSources) > 0 || len(s.LiteralSources) > 0) {
return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
}
return nil
}
// handleConfigMapFromLiteralSources adds the specified literal source
// information into the provided configMap.
func handleConfigMapFromLiteralSources(configMap *v1.ConfigMap, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := util.ParseLiteralSource(literalSource)
if err != nil {
return err
}
err = addKeyFromLiteralToConfigMap(configMap, keyName, value)
if err != nil {
return err
}
}
return nil
}
// handleConfigMapFromFileSources adds the specified file source information
// into the provided configMap
func handleConfigMapFromFileSources(configMap *v1.ConfigMap, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := util.ParseFileSource(fileSource)
if err != nil {
return err
}
info, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default:
return fmt.Errorf("error reading %s: %v", filePath, err)
}
}
if info.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path.")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
err = addKeyFromFileToConfigMap(configMap, keyName, itemPath)
if err != nil {
return err
}
}
}
} else {
if err := addKeyFromFileToConfigMap(configMap, keyName, filePath); err != nil {
return err
}
}
}
return nil
}
// handleConfigMapFromEnvFileSource adds the specified env file source information
// into the provided configMap
func handleConfigMapFromEnvFileSource(configMap *v1.ConfigMap, envFileSource string) error {
info, err := os.Stat(envFileSource)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
default:
return fmt.Errorf("error reading %s: %v", envFileSource, err)
}
}
if info.IsDir() {
return fmt.Errorf("env config file cannot be a directory")
}
return addFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToConfigMap(configMap, key, value)
})
}
// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating
// the value with the content of the given file path, or returns an error.
func addKeyFromFileToConfigMap(configMap *v1.ConfigMap, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
if utf8.Valid(data) {
return addKeyFromLiteralToConfigMap(configMap, keyName, string(data))
}
err = validateNewConfigMap(configMap, keyName)
if err != nil {
return err
}
configMap.BinaryData[keyName] = data
return nil
}
// addKeyFromLiteralToConfigMap adds the given key and data to the given config map,
// returning an error if the key is not valid or if the key already exists.
func addKeyFromLiteralToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
err := validateNewConfigMap(configMap, keyName)
if err != nil {
return err
}
configMap.Data[keyName] = data
return nil
}
func validateNewConfigMap(configMap *v1.ConfigMap, keyName string) error {
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
}
if _, exists := configMap.Data[keyName]; exists {
return fmt.Errorf("cannot add key %s, another key by that name already exists in data: %v", keyName, configMap.Data)
}
if _, exists := configMap.BinaryData[keyName]; exists {
return fmt.Errorf("cannot add key %s, another key by that name already exists in binaryData: %v", keyName, configMap.BinaryData)
}
return nil
}

View File

@ -0,0 +1,416 @@
/*
Copyright 2016 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 (
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestConfigMapGenerate(t *testing.T) {
tests := []struct {
name string
setup func(t *testing.T, params map[string]interface{}) func()
params map[string]interface{}
expected *v1.ConfigMap
expectErr bool
}{
{
name: "test1",
params: map[string]interface{}{
"name": "foo",
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test2",
params: map[string]interface{}{
"name": "foo",
"append-hash": true,
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-867km9574f",
},
Data: map[string]string{},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test3",
params: map[string]interface{}{
"name": "foo",
"type": "my-type",
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test4",
params: map[string]interface{}{
"name": "foo",
"type": "my-type",
"append-hash": true,
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-867km9574f",
},
Data: map[string]string{},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test5",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1=value1", "key2=value2"},
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{
"key1": "value1",
"key2": "value2",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test6",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1=value1", "key2=value2"},
"append-hash": true,
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-gcb75dd9gb",
},
Data: map[string]string{
"key1": "value1",
"key2": "value2",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test7",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1value1"},
},
expectErr: true,
},
{
name: "test8",
params: map[string]interface{}{
"name": "foo",
"from-file": []string{"key1=/file=2"},
},
expectErr: true,
},
{
name: "test9",
params: map[string]interface{}{
"name": "foo",
"from-file": []string{"key1==value"},
},
expectErr: true,
},
{
name: "test10",
setup: setupBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}),
params: map[string]interface{}{
"name": "foo",
"from-file": []string{"foo1"},
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{"foo1": "hello world"},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test11",
setup: setupBinaryFile([]byte{0xff, 0xfd}),
params: map[string]interface{}{
"name": "foo",
"from-file": []string{"foo1"},
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{},
BinaryData: map[string][]byte{"foo1": {0xff, 0xfd}},
},
expectErr: false,
},
{
name: "test12",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1==value1"},
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{
"key1": "=value1",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test13",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1==value1"},
"append-hash": true,
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-bdgk9ttt7m",
},
Data: map[string]string{
"key1": "=value1",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test14",
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
params: map[string]interface{}{
"name": "valid_env",
"from-env-file": "file.env",
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "valid_env",
},
Data: map[string]string{
"key1": "value1",
"key2": "value2",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test15",
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
params: map[string]interface{}{
"name": "valid_env",
"from-env-file": "file.env",
"append-hash": true,
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "valid_env-2cgh8552ch",
},
Data: map[string]string{
"key1": "value1",
"key2": "value2",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test16",
setup: func() func(t *testing.T, params map[string]interface{}) func() {
os.Setenv("g_key1", "1")
os.Setenv("g_key2", "2")
return setupEnvFile("g_key1", "g_key2=")
}(),
params: map[string]interface{}{
"name": "getenv",
"from-env-file": "file.env",
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "getenv",
},
Data: map[string]string{
"g_key1": "1",
"g_key2": "",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test17",
setup: func() func(t *testing.T, params map[string]interface{}) func() {
os.Setenv("g_key1", "1")
os.Setenv("g_key2", "2")
return setupEnvFile("g_key1", "g_key2=")
}(),
params: map[string]interface{}{
"name": "getenv",
"from-env-file": "file.env",
"append-hash": true,
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "getenv-b4hh92hgdk",
},
Data: map[string]string{
"g_key1": "1",
"g_key2": "",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test18",
params: map[string]interface{}{
"name": "too_many_args",
"from-literal": []string{"key1=value1"},
"from-env-file": "file.env",
},
expectErr: true,
},
{name: "test19",
setup: setupEnvFile("key#1=value1"),
params: map[string]interface{}{
"name": "invalid_key",
"from-env-file": "file.env",
},
expectErr: true,
},
{
name: "test20",
setup: setupEnvFile(" key1= value1"),
params: map[string]interface{}{
"name": "with_spaces",
"from-env-file": "file.env",
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "with_spaces",
},
Data: map[string]string{
"key1": " value1",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
{
name: "test21",
setup: setupEnvFile(" key1= value1"),
params: map[string]interface{}{
"name": "with_spaces",
"from-env-file": "file.env",
"append-hash": true,
},
expected: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "with_spaces-bfc558b4ct",
},
Data: map[string]string{
"key1": " value1",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
}
generator := ConfigMapGeneratorV1{}
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setup != nil {
if teardown := tt.setup(t, tt.params); teardown != nil {
defer teardown()
}
}
obj, err := generator.Generate(tt.params)
if !tt.expectErr && err != nil {
t.Errorf("case %d, unexpected error: %v", i, err)
}
if tt.expectErr && err != nil {
return
}
if !reflect.DeepEqual(obj.(*v1.ConfigMap), tt.expected) {
t.Errorf("\ncase %d, expected:\n%#v\nsaw:\n%#v", i, tt.expected, obj.(*v1.ConfigMap))
}
})
}
}
func setupEnvFile(lines ...string) func(*testing.T, map[string]interface{}) func() {
return func(t *testing.T, params map[string]interface{}) func() {
f, err := ioutil.TempFile("", "cme")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
for _, l := range lines {
f.WriteString(l)
f.WriteString("\r\n")
}
f.Close()
params["from-env-file"] = f.Name()
return func() {
os.Remove(f.Name())
}
}
}
func setupBinaryFile(data []byte) func(*testing.T, map[string]interface{}) func() {
return func(t *testing.T, params map[string]interface{}) func() {
tmp, _ := ioutil.TempDir("", "")
f := tmp + "/foo1"
ioutil.WriteFile(f, data, 0644)
params["from-file"] = []string{f}
return func() {
os.Remove(f)
}
}
}

View File

@ -0,0 +1,180 @@
/*
Copyright 2016 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"
"strings"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
"k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
// BaseDeploymentGenerator implements the common functionality of
// DeploymentBasicGeneratorV1, DeploymentBasicAppsGeneratorV1Beta1 and DeploymentBasicAppsGeneratorV1. To reduce
// confusion, it's best to keep this struct in the same file as those
// generators.
type BaseDeploymentGenerator struct {
Name string
Images []string
}
// validate: check if the caller has forgotten to set one of our fields.
func (b BaseDeploymentGenerator) validate() error {
if len(b.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(b.Images) == 0 {
return fmt.Errorf("at least one image must be specified")
}
return nil
}
// structuredGenerate: determine the fields of a deployment. The struct that
// embeds BaseDeploymentGenerator should assemble these pieces into a
// runtime.Object.
func (b BaseDeploymentGenerator) structuredGenerate() (
podSpec v1.PodSpec,
labels map[string]string,
selector metav1.LabelSelector,
err error,
) {
err = b.validate()
if err != nil {
return
}
podSpec = buildPodSpec(b.Images)
labels = map[string]string{}
labels["app"] = b.Name
selector = metav1.LabelSelector{MatchLabels: labels}
return
}
// buildPodSpec: parse the image strings and assemble them into the Containers
// of a PodSpec. This is all you need to create the PodSpec for a deployment.
func buildPodSpec(images []string) v1.PodSpec {
podSpec := v1.PodSpec{Containers: []v1.Container{}}
for _, imageString := range images {
// Retain just the image name
imageSplit := strings.Split(imageString, "/")
name := imageSplit[len(imageSplit)-1]
// Remove any tag or hash
if strings.Contains(name, ":") {
name = strings.Split(name, ":")[0]
}
if strings.Contains(name, "@") {
name = strings.Split(name, "@")[0]
}
podSpec.Containers = append(podSpec.Containers, v1.Container{Name: name, Image: imageString})
}
return podSpec
}
// DeploymentBasicGeneratorV1 supports stable generation of a deployment
type DeploymentBasicGeneratorV1 struct {
BaseDeploymentGenerator
}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ generate.StructuredGenerator = &DeploymentBasicGeneratorV1{}
// StructuredGenerate outputs a deployment object using the configured fields
func (s *DeploymentBasicGeneratorV1) StructuredGenerate() (runtime.Object, error) {
podSpec, labels, selector, err := s.structuredGenerate()
one := int32(1)
return &extensionsv1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
Labels: labels,
},
Spec: extensionsv1beta1.DeploymentSpec{
Replicas: &one,
Selector: &selector,
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: podSpec,
},
},
}, err
}
// DeploymentBasicAppsGeneratorV1Beta1 supports stable generation of a deployment under apps/v1beta1 endpoint
type DeploymentBasicAppsGeneratorV1Beta1 struct {
BaseDeploymentGenerator
}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ generate.StructuredGenerator = &DeploymentBasicAppsGeneratorV1Beta1{}
// StructuredGenerate outputs a deployment object using the configured fields
func (s *DeploymentBasicAppsGeneratorV1Beta1) StructuredGenerate() (runtime.Object, error) {
podSpec, labels, selector, err := s.structuredGenerate()
one := int32(1)
return &appsv1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
Labels: labels,
},
Spec: appsv1beta1.DeploymentSpec{
Replicas: &one,
Selector: &selector,
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: podSpec,
},
},
}, err
}
// DeploymentBasicAppsGeneratorV1 supports stable generation of a deployment under apps/v1 endpoint
type DeploymentBasicAppsGeneratorV1 struct {
BaseDeploymentGenerator
}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ generate.StructuredGenerator = &DeploymentBasicAppsGeneratorV1{}
// StructuredGenerate outputs a deployment object using the configured fields
func (s *DeploymentBasicAppsGeneratorV1) StructuredGenerate() (runtime.Object, error) {
podSpec, labels, selector, err := s.structuredGenerate()
one := int32(1)
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
Labels: labels,
},
Spec: appsv1.DeploymentSpec{
Replicas: &one,
Selector: &selector,
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: podSpec,
},
},
}, err
}

View File

@ -0,0 +1,105 @@
/*
Copyright 2017 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"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestDeploymentBasicGenerate(t *testing.T) {
one := int32(1)
tests := []struct {
name string
deploymentName string
images []string
expected *appsv1.Deployment
expectErr bool
}{
{
name: "deployment name and images ok",
deploymentName: "images-name-ok",
images: []string{"nn/image1", "registry/nn/image2", "nn/image3:tag", "nn/image4@digest", "nn/image5@sha256:digest"},
expected: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "images-name-ok",
Labels: map[string]string{"app": "images-name-ok"},
},
Spec: appsv1.DeploymentSpec{
Replicas: &one,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "images-name-ok"},
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": "images-name-ok"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{Name: "image1", Image: "nn/image1"},
{Name: "image2", Image: "registry/nn/image2"},
{Name: "image3", Image: "nn/image3:tag"},
{Name: "image4", Image: "nn/image4@digest"},
{Name: "image5", Image: "nn/image5@sha256:digest"},
},
},
},
},
},
expectErr: false,
},
{
name: "empty images",
deploymentName: "images-empty",
images: []string{},
expectErr: true,
},
{
name: "no images",
deploymentName: "images-missing",
expectErr: true,
},
{
name: "no deployment name and images",
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
generator := &DeploymentBasicAppsGeneratorV1{
BaseDeploymentGenerator{
Name: tt.deploymentName,
Images: tt.images,
},
}
obj, err := generator.StructuredGenerate()
if !tt.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
if tt.expectErr && err != nil {
return
}
if !reflect.DeepEqual(obj.(*appsv1.Deployment), tt.expected) {
t.Errorf("test: %v\nexpected:\n%#v\nsaw:\n%#v", tt.name, tt.expected, obj.(*appsv1.Deployment))
}
})
}
}

View File

@ -0,0 +1,103 @@
/*
Copyright 2017 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 (
"bufio"
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
"k8s.io/apimachinery/pkg/util/validation"
)
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// proccessEnvFileLine returns a blank key if the line is empty or a comment.
// The value will be retrieved from the environment if necessary.
func proccessEnvFileLine(line []byte, filePath string,
currentLine int) (key, value string, err error) {
if !utf8.Valid(line) {
return ``, ``, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v",
filePath, currentLine+1, line)
}
// We trim UTF8 BOM from the first line of the file but no others
if currentLine == 0 {
line = bytes.TrimPrefix(line, utf8bom)
}
// trim the line from all leading whitespace first
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
// If the line is empty or a comment, we return a blank key/value pair.
if len(line) == 0 || line[0] == '#' {
return ``, ``, nil
}
data := strings.SplitN(string(line), "=", 2)
key = data[0]
if errs := validation.IsEnvVarName(key); len(errs) != 0 {
return ``, ``, fmt.Errorf("%q is not a valid key name: %s", key, strings.Join(errs, ";"))
}
if len(data) == 2 {
value = data[1]
} else {
// No value (no `=` in the line) is a signal to obtain the value
// from the environment.
value = os.Getenv(key)
}
return
}
// addFromEnvFile processes an env file allows a generic addTo to handle the
// collection of key value pairs or returns an error.
func addFromEnvFile(filePath string, addTo func(key, value string) error) error {
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
currentLine := 0
for scanner.Scan() {
// Process the current line, retrieving a key/value pair if
// possible.
scannedBytes := scanner.Bytes()
key, value, err := proccessEnvFileLine(scannedBytes, filePath, currentLine)
if err != nil {
return err
}
currentLine++
if len(key) == 0 {
// no key means line was empty or a comment
continue
}
if err = addTo(key, value); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,105 @@
/*
Copyright 2017 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 (
"os"
"strings"
"testing"
)
// Test the cases of proccessEnvFileLine that can be run without touching the
// environment.
func Test_processEnvFileLine(t *testing.T) {
testCases := []struct {
name string
line []byte
currentLine int
expectedKey string
expectedValue string
expectedErr string
}{
{"the utf8bom is trimmed on the first line",
append(utf8bom, 'a', '=', 'c'), 0, "a", "c", ""},
{"the utf8bom is NOT trimmed on the second line",
append(utf8bom, 'a', '=', 'c'), 1, "", "", "not a valid key name"},
{"no key is returned on a comment line",
[]byte{' ', '#', 'c'}, 0, "", "", ""},
{"no key is returned on a blank line",
[]byte{' ', ' ', '\t'}, 0, "", "", ""},
{"key is returned even with no value",
[]byte{' ', 'x', '='}, 0, "x", "", ""},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
key, value, err := proccessEnvFileLine(tt.line, `filename`, tt.currentLine)
t.Logf("Testing that %s.", tt.name)
if tt.expectedKey != key {
t.Errorf("\texpected key %q, received %q", tt.expectedKey, key)
}
if tt.expectedValue != value {
t.Errorf("\texpected value %q, received %q", tt.expectedValue, value)
}
if len(tt.expectedErr) == 0 {
if err != nil {
t.Errorf("\tunexpected err %v", err)
}
} else {
if !strings.Contains(err.Error(), tt.expectedErr) {
t.Errorf("\terr %v doesn't match expected %q", err, tt.expectedErr)
}
}
})
}
}
// proccessEnvFileLine needs to fetch the value from the environment if no
// equals sign is provided.
// For example:
//
// my_key1=alpha
// my_key2=beta
// my_key3
//
// In this file, my_key3 must be fetched from the environment.
// Test this capability.
func Test_processEnvFileLine_readEnvironment(t *testing.T) {
const realKey = "k8s_test_env_file_key"
const realValue = `my_value`
// Just in case, these two lines ensure the environment is restored to
// its original state.
original := os.Getenv(realKey)
defer func() { os.Setenv(realKey, original) }()
os.Setenv(realKey, `my_value`)
key, value, err := proccessEnvFileLine([]byte(realKey), `filename`, 3)
if err != nil {
t.Fatal(err)
}
if key != realKey {
t.Errorf(`expected key %q, received %q`, realKey, key)
}
if value != realValue {
t.Errorf(`expected value %q, received %q`, realValue, value)
}
}

View File

@ -0,0 +1,235 @@
/*
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 (
"fmt"
"io"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/kubernetes/pkg/kubectl/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.
RunV1GeneratorName = "run/v1"
RunPodV1GeneratorName = "run-pod/v1"
ServiceV1GeneratorName = "service/v1"
ServiceV2GeneratorName = "service/v2"
ServiceNodePortGeneratorV1Name = "service-nodeport/v1"
ServiceClusterIPGeneratorV1Name = "service-clusterip/v1"
ServiceLoadBalancerGeneratorV1Name = "service-loadbalancer/v1"
ServiceExternalNameGeneratorV1Name = "service-externalname/v1"
ServiceAccountV1GeneratorName = "serviceaccount/v1"
HorizontalPodAutoscalerV1GeneratorName = "horizontalpodautoscaler/v1"
DeploymentV1Beta1GeneratorName = "deployment/v1beta1"
DeploymentAppsV1Beta1GeneratorName = "deployment/apps.v1beta1"
DeploymentAppsV1GeneratorName = "deployment/apps.v1"
DeploymentBasicV1Beta1GeneratorName = "deployment-basic/v1beta1"
DeploymentBasicAppsV1Beta1GeneratorName = "deployment-basic/apps.v1beta1"
DeploymentBasicAppsV1GeneratorName = "deployment-basic/apps.v1"
JobV1GeneratorName = "job/v1"
CronJobV2Alpha1GeneratorName = "cronjob/v2alpha1"
CronJobV1Beta1GeneratorName = "cronjob/v1beta1"
NamespaceV1GeneratorName = "namespace/v1"
ResourceQuotaV1GeneratorName = "resourcequotas/v1"
SecretV1GeneratorName = "secret/v1"
SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1"
SecretForTLSV1GeneratorName = "secret-for-tls/v1"
ConfigMapV1GeneratorName = "configmap/v1"
ClusterRoleBindingV1GeneratorName = "clusterrolebinding.rbac.authorization.k8s.io/v1alpha1"
RoleBindingV1GeneratorName = "rolebinding.rbac.authorization.k8s.io/v1alpha1"
PodDisruptionBudgetV1GeneratorName = "poddisruptionbudget/v1beta1"
PodDisruptionBudgetV2GeneratorName = "poddisruptionbudget/v1beta1/v2"
PriorityClassV1Alpha1GeneratorName = "priorityclass/v1alpha1"
)
// 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{
ServiceV1GeneratorName: ServiceGeneratorV1{},
ServiceV2GeneratorName: ServiceGeneratorV2{},
}
case "service-clusterip":
generator = map[string]generate.Generator{
ServiceClusterIPGeneratorV1Name: ServiceClusterIPGeneratorV1{},
}
case "service-nodeport":
generator = map[string]generate.Generator{
ServiceNodePortGeneratorV1Name: ServiceNodePortGeneratorV1{},
}
case "service-loadbalancer":
generator = map[string]generate.Generator{
ServiceLoadBalancerGeneratorV1Name: ServiceLoadBalancerGeneratorV1{},
}
case "deployment":
// Create Deployment has only StructuredGenerators and no
// param-based Generators.
// The StructuredGenerators are as follows (as of 2018-03-16):
// DeploymentBasicV1Beta1GeneratorName -> DeploymentBasicGeneratorV1
// DeploymentBasicAppsV1Beta1GeneratorName -> DeploymentBasicAppsGeneratorV1Beta1
// DeploymentBasicAppsV1GeneratorName -> DeploymentBasicAppsGeneratorV1
generator = map[string]generate.Generator{}
case "run":
generator = map[string]generate.Generator{
RunV1GeneratorName: BasicReplicationController{},
RunPodV1GeneratorName: BasicPod{},
DeploymentV1Beta1GeneratorName: DeploymentV1Beta1{},
DeploymentAppsV1Beta1GeneratorName: DeploymentAppsV1Beta1{},
DeploymentAppsV1GeneratorName: DeploymentAppsV1{},
JobV1GeneratorName: JobV1{},
CronJobV2Alpha1GeneratorName: CronJobV2Alpha1{},
CronJobV1Beta1GeneratorName: CronJobV1Beta1{},
}
case "namespace":
generator = map[string]generate.Generator{
NamespaceV1GeneratorName: NamespaceGeneratorV1{},
}
case "quota":
generator = map[string]generate.Generator{
ResourceQuotaV1GeneratorName: ResourceQuotaGeneratorV1{},
}
case "secret":
generator = map[string]generate.Generator{
SecretV1GeneratorName: SecretGeneratorV1{},
}
case "secret-for-docker-registry":
generator = map[string]generate.Generator{
SecretForDockerRegistryV1GeneratorName: SecretForDockerRegistryGeneratorV1{},
}
case "secret-for-tls":
generator = map[string]generate.Generator{
SecretForTLSV1GeneratorName: SecretForTLSGeneratorV1{},
}
}
return generator
}
// FallbackGeneratorNameIfNecessary returns the name of the old generator
// if server does not support new generator. Otherwise, the
// generator string is returned unchanged.
//
// If the generator name is changed, print a warning message to let the user
// know.
func FallbackGeneratorNameIfNecessary(
generatorName string,
discoveryClient discovery.DiscoveryInterface,
cmdErr io.Writer,
) (string, error) {
switch generatorName {
case DeploymentAppsV1GeneratorName:
hasResource, err := HasResource(discoveryClient, appsv1.SchemeGroupVersion.WithResource("deployments"))
if err != nil {
return "", err
}
if !hasResource {
return FallbackGeneratorNameIfNecessary(DeploymentAppsV1Beta1GeneratorName, discoveryClient, cmdErr)
}
case DeploymentAppsV1Beta1GeneratorName:
hasResource, err := HasResource(discoveryClient, appsv1beta1.SchemeGroupVersion.WithResource("deployments"))
if err != nil {
return "", err
}
if !hasResource {
return FallbackGeneratorNameIfNecessary(DeploymentV1Beta1GeneratorName, discoveryClient, cmdErr)
}
case DeploymentV1Beta1GeneratorName:
hasResource, err := HasResource(discoveryClient, extensionsv1beta1.SchemeGroupVersion.WithResource("deployments"))
if err != nil {
return "", err
}
if !hasResource {
return RunV1GeneratorName, nil
}
case DeploymentBasicAppsV1GeneratorName:
hasResource, err := HasResource(discoveryClient, appsv1.SchemeGroupVersion.WithResource("deployments"))
if err != nil {
return "", err
}
if !hasResource {
return FallbackGeneratorNameIfNecessary(DeploymentBasicAppsV1Beta1GeneratorName, discoveryClient, cmdErr)
}
case DeploymentBasicAppsV1Beta1GeneratorName:
hasResource, err := HasResource(discoveryClient, appsv1beta1.SchemeGroupVersion.WithResource("deployments"))
if err != nil {
return "", err
}
if !hasResource {
return DeploymentBasicV1Beta1GeneratorName, nil
}
case JobV1GeneratorName:
hasResource, err := HasResource(discoveryClient, batchv1.SchemeGroupVersion.WithResource("jobs"))
if err != nil {
return "", err
}
if !hasResource {
return RunPodV1GeneratorName, nil
}
case CronJobV1Beta1GeneratorName:
hasResource, err := HasResource(discoveryClient, batchv1beta1.SchemeGroupVersion.WithResource("cronjobs"))
if err != nil {
return "", err
}
if !hasResource {
return FallbackGeneratorNameIfNecessary(CronJobV2Alpha1GeneratorName, discoveryClient, cmdErr)
}
case CronJobV2Alpha1GeneratorName:
hasResource, err := HasResource(discoveryClient, batchv2alpha1.SchemeGroupVersion.WithResource("cronjobs"))
if err != nil {
return "", err
}
if !hasResource {
return JobV1GeneratorName, nil
}
}
return generatorName, nil
}
func HasResource(client discovery.DiscoveryInterface, resource schema.GroupVersionResource) (bool, error) {
resources, err := client.ServerResourcesForGroupVersion(resource.GroupVersion().String())
if apierrors.IsNotFound(err) {
// entire group is missing
return false, nil
}
if err != nil {
// other errors error
return false, fmt.Errorf("failed to discover supported resources: %v", err)
}
for _, serverResource := range resources.APIResources {
if serverResource.Name == resource.Resource {
return true, nil
}
}
return false, nil
}

View File

@ -0,0 +1,80 @@
/*
Copyright 2015 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"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
// NamespaceGeneratorV1 supports stable generation of a namespace
type NamespaceGeneratorV1 struct {
// Name of namespace
Name string
}
// Ensure it supports the generator pattern that uses parameter injection
var _ generate.Generator = &NamespaceGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ generate.StructuredGenerator = &NamespaceGeneratorV1{}
// Generate returns a namespace using the specified parameters
func (g NamespaceGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(g.ParamNames(), genericParams)
if err != nil {
return nil, err
}
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
}
delegate := &NamespaceGeneratorV1{Name: params["name"]}
return delegate.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern
func (g NamespaceGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
}
}
// StructuredGenerate outputs a namespace object using the configured fields
func (g *NamespaceGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := g.validate(); err != nil {
return nil, err
}
namespace := &v1.Namespace{}
namespace.Name = g.Name
return namespace, nil
}
// validate validates required fields are set to support structured generation
func (g *NamespaceGeneratorV1) validate() error {
if len(g.Name) == 0 {
return fmt.Errorf("name must be specified")
}
return nil
}

View File

@ -0,0 +1,108 @@
/*
Copyright 2015 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"
)
func TestNamespaceGenerate(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
expected *v1.Namespace
expectErr bool
}{
{
name: "test1",
params: map[string]interface{}{
"name": "foo",
},
expected: &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
},
expectErr: false,
},
{
name: "test2",
params: map[string]interface{}{},
expectErr: true,
},
{
name: "test3",
params: map[string]interface{}{
"name": 1,
},
expectErr: true,
},
{
name: "test4",
params: map[string]interface{}{
"name": "",
},
expectErr: true,
},
{
name: "test5",
params: map[string]interface{}{
"name": nil,
},
expectErr: true,
},
{
name: "test6",
params: map[string]interface{}{
"name_wrong_key": "some_value",
},
expectErr: true,
},
{
name: "test7",
params: map[string]interface{}{
"NAME": "some_value",
},
expectErr: true,
},
}
generator := NamespaceGeneratorV1{}
for index, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
obj, err := generator.Generate(tt.params)
switch {
case tt.expectErr && err != nil:
return // loop, since there's no output to check
case tt.expectErr && err == nil:
t.Errorf("%v: expected error and didn't get one", index)
return // loop, no expected output object
case !tt.expectErr && err != nil:
t.Errorf("%v: unexpected error %v", index, err)
return // loop, no output object
case !tt.expectErr && err == nil:
// do nothing and drop through
}
if !reflect.DeepEqual(obj.(*v1.Namespace), tt.expected) {
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", tt.expected, obj.(*v1.Namespace))
}
})
}
}

View File

@ -0,0 +1,214 @@
/*
Copyright 2016 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"
policy "k8s.io/api/policy/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
// PodDisruptionBudgetV1Generator supports stable generation of a pod disruption budget.
type PodDisruptionBudgetV1Generator struct {
Name string
MinAvailable string
Selector string
}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ generate.StructuredGenerator = &PodDisruptionBudgetV1Generator{}
func (PodDisruptionBudgetV1Generator) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "min-available", Required: false},
{Name: "selector", Required: true},
}
}
func (s PodDisruptionBudgetV1Generator) Generate(params map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), params)
if err != nil {
return nil, err
}
name, isString := params["name"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %T for 'name'", params["name"])
}
minAvailable, isString := params["min-available"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %T for 'min-available'", params["min-available"])
}
selector, isString := params["selector"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %T for 'selector'", params["selector"])
}
delegate := &PodDisruptionBudgetV1Generator{Name: name, MinAvailable: minAvailable, Selector: selector}
return delegate.StructuredGenerate()
}
// StructuredGenerate outputs a pod disruption budget object using the configured fields.
func (s *PodDisruptionBudgetV1Generator) StructuredGenerate() (runtime.Object, error) {
if len(s.MinAvailable) == 0 {
// defaulting behavior seen in Kubernetes 1.6 and below.
s.MinAvailable = "1"
}
if err := s.validate(); err != nil {
return nil, err
}
selector, err := metav1.ParseToLabelSelector(s.Selector)
if err != nil {
return nil, err
}
minAvailable := intstr.Parse(s.MinAvailable)
return &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: &minAvailable,
Selector: selector,
},
}, nil
}
// validate validates required fields are set to support structured generation.
func (s *PodDisruptionBudgetV1Generator) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.Selector) == 0 {
return fmt.Errorf("a selector must be specified")
}
if len(s.MinAvailable) == 0 {
return fmt.Errorf("the minimum number of available pods required must be specified")
}
return nil
}
// PodDisruptionBudgetV2Generator supports stable generation of a pod disruption budget.
type PodDisruptionBudgetV2Generator struct {
Name string
MinAvailable string
MaxUnavailable string
Selector string
}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ generate.StructuredGenerator = &PodDisruptionBudgetV2Generator{}
func (PodDisruptionBudgetV2Generator) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "min-available", Required: false},
{Name: "max-unavailable", Required: false},
{Name: "selector", Required: true},
}
}
func (s PodDisruptionBudgetV2Generator) Generate(params map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), params)
if err != nil {
return nil, err
}
name, isString := params["name"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %T for 'name'", params["name"])
}
minAvailable, isString := params["min-available"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %T for 'min-available'", params["min-available"])
}
maxUnavailable, isString := params["max-unavailable"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %T for 'max-unavailable'", params["max-unavailable"])
}
selector, isString := params["selector"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %T for 'selector'", params["selector"])
}
delegate := &PodDisruptionBudgetV2Generator{Name: name, MinAvailable: minAvailable, MaxUnavailable: maxUnavailable, Selector: selector}
return delegate.StructuredGenerate()
}
// StructuredGenerate outputs a pod disruption budget object using the configured fields.
func (s *PodDisruptionBudgetV2Generator) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
selector, err := metav1.ParseToLabelSelector(s.Selector)
if err != nil {
return nil, err
}
if len(s.MaxUnavailable) > 0 {
maxUnavailable := intstr.Parse(s.MaxUnavailable)
return &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
Spec: policy.PodDisruptionBudgetSpec{
MaxUnavailable: &maxUnavailable,
Selector: selector,
},
}, nil
}
if len(s.MinAvailable) > 0 {
minAvailable := intstr.Parse(s.MinAvailable)
return &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: &minAvailable,
Selector: selector,
},
}, nil
}
return nil, err
}
// validate validates required fields are set to support structured generation.
func (s *PodDisruptionBudgetV2Generator) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.Selector) == 0 {
return fmt.Errorf("a selector must be specified")
}
if len(s.MaxUnavailable) == 0 && len(s.MinAvailable) == 0 {
return fmt.Errorf("one of min-available or max-unavailable must be specified")
}
if len(s.MaxUnavailable) > 0 && len(s.MinAvailable) > 0 {
return fmt.Errorf("min-available and max-unavailable cannot be both specified")
}
return nil
}

View File

@ -0,0 +1,368 @@
/*
Copyright 2017 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"
policy "k8s.io/api/policy/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func TestPodDisruptionBudgetV1Generate(t *testing.T) {
name := "foo"
minAvailable := "5"
minAvailableIS := intstr.Parse(minAvailable)
defaultMinAvailableIS := intstr.Parse("1")
selector := "app=foo"
labelSelector, err := metav1.ParseToLabelSelector(selector)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tests := []struct {
name string
params map[string]interface{}
expectErrMsg string
expectPDB *policy.PodDisruptionBudget
}{
{
name: "test-valid-use",
params: map[string]interface{}{
"name": name,
"min-available": minAvailable,
"selector": selector,
},
expectPDB: &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: &minAvailableIS,
Selector: labelSelector,
},
},
},
{
name: "test-missing-name-param",
params: map[string]interface{}{
"min-available": minAvailable,
"selector": selector,
},
expectErrMsg: "Parameter: name is required",
},
{
name: "test-blank-name-param",
params: map[string]interface{}{
"name": "",
"min-available": minAvailable,
"selector": selector,
},
expectErrMsg: "Parameter: name is required",
},
{
name: "test-invalid-name-param",
params: map[string]interface{}{
"name": 1,
"min-available": minAvailable,
"selector": selector,
},
expectErrMsg: "expected string, found int for 'name'",
},
{
name: "test-missing-min-available-param",
params: map[string]interface{}{
"name": name,
"selector": selector,
},
expectErrMsg: "expected string, found <nil> for 'min-available'",
},
{
name: "test-blank-min-available-param",
params: map[string]interface{}{
"name": name,
"min-available": "",
"selector": selector,
},
expectPDB: &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: &defaultMinAvailableIS,
Selector: labelSelector,
},
},
},
{
name: "test-invalid-min-available-param",
params: map[string]interface{}{
"name": name,
"min-available": 1,
"selector": selector,
},
expectErrMsg: "expected string, found int for 'min-available'",
},
{
name: "test-missing-selector-param",
params: map[string]interface{}{
"name": name,
"min-available": minAvailable,
},
expectErrMsg: "Parameter: selector is required",
},
{
name: "test-blank-selector-param",
params: map[string]interface{}{
"name": name,
"min-available": minAvailable,
"selector": "",
},
expectErrMsg: "Parameter: selector is required",
},
{
name: "test-invalid-selector-param",
params: map[string]interface{}{
"name": name,
"min-available": minAvailable,
"selector": 1,
},
expectErrMsg: "expected string, found int for 'selector'",
},
}
generator := PodDisruptionBudgetV1Generator{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
obj, err := generator.Generate(tt.params)
switch {
case tt.expectErrMsg != "" && err != nil:
if err.Error() != tt.expectErrMsg {
t.Errorf("test '%s': expect error '%s', but saw '%s'", tt.name, tt.expectErrMsg, err.Error())
}
return
case tt.expectErrMsg != "" && err == nil:
t.Errorf("test '%s': expected error '%s' and didn't get one", tt.name, tt.expectErrMsg)
return
case tt.expectErrMsg == "" && err != nil:
t.Errorf("test '%s': unexpected error %s", tt.name, err.Error())
return
}
if !reflect.DeepEqual(obj.(*policy.PodDisruptionBudget), tt.expectPDB) {
t.Errorf("test '%s': expected:\n%#v\nsaw:\n%#v", tt.name, tt.expectPDB, obj.(*policy.PodDisruptionBudget))
}
})
}
}
func TestPodDisruptionBudgetV2Generate(t *testing.T) {
name := "foo"
minAvailable := "1"
minAvailableIS := intstr.Parse(minAvailable)
maxUnavailable := "5%"
maxUnavailableIS := intstr.Parse(maxUnavailable)
selector := "app=foo"
labelSelector, err := metav1.ParseToLabelSelector(selector)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tests := []struct {
name string
params map[string]interface{}
expectErrMsg string
expectPDB *policy.PodDisruptionBudget
}{
{
name: "test-valid-min-available",
params: map[string]interface{}{
"name": name,
"min-available": minAvailable,
"max-unavailable": "",
"selector": selector,
},
expectPDB: &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: &minAvailableIS,
Selector: labelSelector,
},
},
},
{
name: "test-valid-max-available",
params: map[string]interface{}{
"name": name,
"min-available": "",
"max-unavailable": maxUnavailable,
"selector": selector,
},
expectPDB: &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: policy.PodDisruptionBudgetSpec{
MaxUnavailable: &maxUnavailableIS,
Selector: labelSelector,
},
},
},
{
name: "test-missing-name-param",
params: map[string]interface{}{
"min-available": "",
"max-unavailable": "",
"selector": selector,
},
expectErrMsg: "Parameter: name is required",
},
{
name: "test-blank-name-param",
params: map[string]interface{}{
"name": "",
"min-available": "",
"max-unavailable": "",
"selector": selector,
},
expectErrMsg: "Parameter: name is required",
},
{
name: "test-invalid-name-param",
params: map[string]interface{}{
"name": 1,
"min-available": "",
"max-unavailable": "",
"selector": selector,
},
expectErrMsg: "expected string, found int for 'name'",
},
{
name: "test-missing-min-available-param",
params: map[string]interface{}{
"name": name,
"max-unavailable": "",
"selector": selector,
},
expectErrMsg: "expected string, found <nil> for 'min-available'",
},
{
name: "test-invalid-min-available-param",
params: map[string]interface{}{
"name": name,
"min-available": 1,
"max-unavailable": "",
"selector": selector,
},
expectErrMsg: "expected string, found int for 'min-available'",
},
{
name: "test-missing-max-available-param",
params: map[string]interface{}{
"name": name,
"min-available": "",
"selector": selector,
},
expectErrMsg: "expected string, found <nil> for 'max-unavailable'",
},
{
name: "test-invalid-max-available-param",
params: map[string]interface{}{
"name": name,
"min-available": "",
"max-unavailable": 1,
"selector": selector,
},
expectErrMsg: "expected string, found int for 'max-unavailable'",
},
{
name: "test-blank-min-available-max-unavailable-param",
params: map[string]interface{}{
"name": name,
"min-available": "",
"max-unavailable": "",
"selector": selector,
},
expectErrMsg: "one of min-available or max-unavailable must be specified",
},
{
name: "test-min-available-max-unavailable-param",
params: map[string]interface{}{
"name": name,
"min-available": minAvailable,
"max-unavailable": maxUnavailable,
"selector": selector,
},
expectErrMsg: "min-available and max-unavailable cannot be both specified",
},
{
name: "test-missing-selector-param",
params: map[string]interface{}{
"name": name,
"min-available": "",
"max-unavailable": "",
},
expectErrMsg: "Parameter: selector is required",
},
{
name: "test-blank-selector-param",
params: map[string]interface{}{
"name": name,
"min-available": "",
"max-unavailable": "",
"selector": "",
},
expectErrMsg: "Parameter: selector is required",
},
{
name: "test-invalid-selector-param",
params: map[string]interface{}{
"name": name,
"min-available": "",
"max-unavailable": "",
"selector": 1,
},
expectErrMsg: "expected string, found int for 'selector'",
},
}
generator := PodDisruptionBudgetV2Generator{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
obj, err := generator.Generate(tt.params)
switch {
case tt.expectErrMsg != "" && err != nil:
if err.Error() != tt.expectErrMsg {
t.Errorf("test '%s': expect error '%s', but saw '%s'", tt.name, tt.expectErrMsg, err.Error())
}
return
case tt.expectErrMsg != "" && err == nil:
t.Errorf("test '%s': expected error '%s' and didn't get one", tt.name, tt.expectErrMsg)
return
case tt.expectErrMsg == "" && err != nil:
t.Errorf("test '%s': unexpected error %s", tt.name, err.Error())
return
}
if !reflect.DeepEqual(obj.(*policy.PodDisruptionBudget), tt.expectPDB) {
t.Errorf("test '%s': expected:\n%#v\nsaw:\n%#v", tt.name, tt.expectPDB, obj.(*policy.PodDisruptionBudget))
}
})
}
}

View File

@ -0,0 +1,86 @@
/*
Copyright 2017 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"
scheduling "k8s.io/api/scheduling/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
// PriorityClassV1Generator supports stable generation of a priorityClass.
type PriorityClassV1Generator struct {
Name string
Value int32
GlobalDefault bool
Description string
}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ generate.StructuredGenerator = &PriorityClassV1Generator{}
func (PriorityClassV1Generator) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "value", Required: true},
{Name: "global-default", Required: false},
{Name: "description", Required: false},
}
}
func (s PriorityClassV1Generator) Generate(params map[string]interface{}) (runtime.Object, error) {
if err := generate.ValidateParams(s.ParamNames(), params); err != nil {
return nil, err
}
name, found := params["name"].(string)
if !found {
return nil, fmt.Errorf("expected string, saw %v for 'name'", name)
}
value, found := params["value"].(int32)
if !found {
return nil, fmt.Errorf("expected int32, found %v", value)
}
globalDefault, found := params["global-default"].(bool)
if !found {
return nil, fmt.Errorf("expected bool, found %v", globalDefault)
}
description, found := params["description"].(string)
if !found {
return nil, fmt.Errorf("expected string, found %v", description)
}
delegate := &PriorityClassV1Generator{Name: name, Value: value, GlobalDefault: globalDefault, Description: description}
return delegate.StructuredGenerate()
}
// StructuredGenerate outputs a priorityClass object using the configured fields.
func (s *PriorityClassV1Generator) StructuredGenerate() (runtime.Object, error) {
return &scheduling.PriorityClass{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
Value: s.Value,
GlobalDefault: s.GlobalDefault,
Description: s.Description,
}, nil
}

View File

@ -0,0 +1,96 @@
/*
Copyright 2017 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 (
scheduling "k8s.io/api/scheduling/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"reflect"
"testing"
)
func TestPriorityClassV1Generator(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
expected *scheduling.PriorityClass
expectErr bool
}{
{
name: "test valid case",
params: map[string]interface{}{
"name": "foo",
"value": int32(1000),
"global-default": false,
"description": "high priority class",
},
expected: &scheduling.PriorityClass{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Value: int32(1000),
GlobalDefault: false,
Description: "high priority class",
},
expectErr: false,
},
{
name: "test valid case that as default priority",
params: map[string]interface{}{
"name": "foo",
"value": int32(1000),
"global-default": true,
"description": "high priority class",
},
expected: &scheduling.PriorityClass{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Value: int32(1000),
GlobalDefault: true,
Description: "high priority class",
},
expectErr: false,
},
{
name: "test missing required param",
params: map[string]interface{}{
"name": "foo",
"global-default": true,
"description": "high priority class",
},
expectErr: true,
},
}
generator := PriorityClassV1Generator{}
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("%s: unexpected error: %v", tt.name, err)
}
if tt.expectErr && err != nil {
return
}
if !reflect.DeepEqual(obj.(*scheduling.PriorityClass), tt.expected) {
t.Errorf("%s:\nexpected:\n%#v\nsaw:\n%#v", tt.name, tt.expected, obj.(*scheduling.PriorityClass))
}
})
}
}

View File

@ -0,0 +1,126 @@
/*
Copyright 2016 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"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
// ResourceQuotaGeneratorV1 supports stable generation of a resource quota
type ResourceQuotaGeneratorV1 struct {
// The name of a quota object.
Name string
// The hard resource limit string before parsing.
Hard string
// The scopes of a quota object before parsing.
Scopes string
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern
func (g ResourceQuotaGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "hard", Required: true},
{Name: "scopes", Required: false},
}
}
// Ensure it supports the generator pattern that uses parameter injection
var _ generate.Generator = &ResourceQuotaGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ generate.StructuredGenerator = &ResourceQuotaGeneratorV1{}
func (g ResourceQuotaGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(g.ParamNames(), genericParams)
if err != nil {
return nil, err
}
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
}
delegate := &ResourceQuotaGeneratorV1{}
delegate.Name = params["name"]
delegate.Hard = params["hard"]
delegate.Scopes = params["scopes"]
return delegate.StructuredGenerate()
}
// StructuredGenerate outputs a ResourceQuota object using the configured fields
func (g *ResourceQuotaGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := g.validate(); err != nil {
return nil, err
}
resourceList, err := populateResourceListV1(g.Hard)
if err != nil {
return nil, err
}
scopes, err := parseScopes(g.Scopes)
if err != nil {
return nil, err
}
resourceQuota := &v1.ResourceQuota{}
resourceQuota.Name = g.Name
resourceQuota.Spec.Hard = resourceList
resourceQuota.Spec.Scopes = scopes
return resourceQuota, nil
}
// validate validates required fields are set to support structured generation
func (r *ResourceQuotaGeneratorV1) validate() error {
if len(r.Name) == 0 {
return fmt.Errorf("name must be specified")
}
return nil
}
func parseScopes(spec string) ([]v1.ResourceQuotaScope, error) {
// empty input gets a nil response to preserve generator test expected behaviors
if spec == "" {
return nil, nil
}
scopes := strings.Split(spec, ",")
result := make([]v1.ResourceQuotaScope, 0, len(scopes))
for _, scope := range scopes {
// intentionally do not verify the scope against the valid scope list. This is done by the apiserver anyway.
if scope == "" {
return nil, fmt.Errorf("invalid resource quota scope \"\"")
}
result = append(result, v1.ResourceQuotaScope(scope))
}
return result, nil
}

View File

@ -0,0 +1,123 @@
/*
Copyright 2016 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"
)
func TestQuotaGenerate(t *testing.T) {
hard := "cpu=10,memory=5G,pods=10,services=7"
resourceQuotaSpecList, err := populateResourceListV1(hard)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tests := []struct {
name string
params map[string]interface{}
expected *v1.ResourceQuota
expectErr bool
}{
{
name: "test-valid-use",
params: map[string]interface{}{
"name": "foo",
"hard": hard,
},
expected: &v1.ResourceQuota{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.ResourceQuotaSpec{Hard: resourceQuotaSpecList},
},
expectErr: false,
},
{
name: "test-missing-required-param",
params: map[string]interface{}{
"name": "foo",
},
expectErr: true,
},
{
name: "test-valid-scopes",
params: map[string]interface{}{
"name": "foo",
"hard": hard,
"scopes": "BestEffort,NotTerminating",
},
expected: &v1.ResourceQuota{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.ResourceQuotaSpec{
Hard: resourceQuotaSpecList,
Scopes: []v1.ResourceQuotaScope{
v1.ResourceQuotaScopeBestEffort,
v1.ResourceQuotaScopeNotTerminating,
},
},
},
expectErr: false,
},
{
name: "test-empty-scopes",
params: map[string]interface{}{
"name": "foo",
"hard": hard,
"scopes": "",
},
expected: &v1.ResourceQuota{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.ResourceQuotaSpec{Hard: resourceQuotaSpecList},
},
expectErr: false,
},
{
name: "test-invalid-scopes",
params: map[string]interface{}{
"name": "foo",
"hard": hard,
"scopes": "abc,",
},
expectErr: true,
},
}
generator := ResourceQuotaGeneratorV1{}
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("%s: unexpected error: %v", tt.name, err)
}
if tt.expectErr && err != nil {
return
}
if !reflect.DeepEqual(obj.(*v1.ResourceQuota), tt.expected) {
t.Errorf("%s:\nexpected:\n%#v\nsaw:\n%#v", tt.name, tt.expected, obj.(*v1.ResourceQuota))
}
})
}
}

View File

@ -0,0 +1,174 @@
/*
Copyright 2016 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"
"strings"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
// RoleBindingGeneratorV1 supports stable generation of a roleBinding.
type RoleBindingGeneratorV1 struct {
// Name of roleBinding (required)
Name string
// ClusterRole for the roleBinding
ClusterRole string
// Role for the roleBinding
Role string
// Users to derive the roleBinding from (optional)
Users []string
// Groups to derive the roleBinding from (optional)
Groups []string
// ServiceAccounts to derive the roleBinding from in namespace:name format(optional)
ServiceAccounts []string
}
// Ensure it supports the generator pattern that uses parameter injection.
var _ generate.Generator = &RoleBindingGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ generate.StructuredGenerator = &RoleBindingGeneratorV1{}
// Generate returns a roleBinding using the specified parameters.
func (s RoleBindingGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
delegate := &RoleBindingGeneratorV1{}
userStrings, found := genericParams["user"]
if found {
fromFileArray, isArray := userStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", userStrings)
}
delegate.Users = fromFileArray
delete(genericParams, "user")
}
groupStrings, found := genericParams["group"]
if found {
fromLiteralArray, isArray := groupStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", groupStrings)
}
delegate.Groups = fromLiteralArray
delete(genericParams, "group")
}
saStrings, found := genericParams["serviceaccount"]
if found {
fromLiteralArray, isArray := saStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", saStrings)
}
delegate.ServiceAccounts = fromLiteralArray
delete(genericParams, "serviceaccount")
}
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
}
delegate.Name = params["name"]
delegate.ClusterRole = params["clusterrole"]
delegate.Role = params["role"]
return delegate.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern.
func (s RoleBindingGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "clusterrole", Required: false},
{Name: "role", Required: false},
{Name: "user", Required: false},
{Name: "group", Required: false},
{Name: "serviceaccount", Required: false},
}
}
// StructuredGenerate outputs a roleBinding object using the configured fields.
func (s RoleBindingGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
roleBinding := &rbacv1.RoleBinding{}
roleBinding.Name = s.Name
switch {
case len(s.Role) > 0:
roleBinding.RoleRef = rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "Role",
Name: s.Role,
}
case len(s.ClusterRole) > 0:
roleBinding.RoleRef = rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: s.ClusterRole,
}
}
for _, user := range sets.NewString(s.Users...).List() {
roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{
Kind: rbacv1.UserKind,
APIGroup: rbacv1.GroupName,
Name: user,
})
}
for _, group := range sets.NewString(s.Groups...).List() {
roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{
Kind: rbacv1.GroupKind,
APIGroup: rbacv1.GroupName,
Name: group,
})
}
for _, sa := range sets.NewString(s.ServiceAccounts...).List() {
tokens := strings.Split(sa, ":")
if len(tokens) != 2 || tokens[1] == "" {
return nil, fmt.Errorf("serviceaccount must be <namespace>:<name>")
}
roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{
Kind: rbacv1.ServiceAccountKind,
APIGroup: "",
Namespace: tokens[0],
Name: tokens[1],
})
}
return roleBinding, nil
}
// validate validates required fields are set to support structured generation.
func (s RoleBindingGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if (len(s.ClusterRole) == 0) == (len(s.Role) == 0) {
return fmt.Errorf("exactly one of clusterrole or role must be specified")
}
return nil
}

View File

@ -0,0 +1,144 @@
/*
Copyright 2017 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"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestRoleBindingGenerate(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
expectErrMsg string
expectBinding *rbac.RoleBinding
}{
{
name: "test-missing-name",
params: map[string]interface{}{
"role": "fake-role",
"groups": []string{"fake-group"},
"serviceaccount": []string{"fake-namespace:fake-account"},
},
expectErrMsg: "Parameter: name is required",
},
{
name: "test-missing-role-and-clusterrole",
params: map[string]interface{}{
"name": "fake-binding",
"group": []string{"fake-group"},
"serviceaccount": []string{"fake-namespace:fake-account"},
},
expectErrMsg: "exactly one of clusterrole or role must be specified",
},
{
name: "test-both-role-and-clusterrole-provided",
params: map[string]interface{}{
"name": "fake-binding",
"role": "fake-role",
"clusterrole": "fake-clusterrole",
"group": []string{"fake-group"},
"serviceaccount": []string{"fake-namespace:fake-account"},
},
expectErrMsg: "exactly one of clusterrole or role must be specified",
},
{
name: "test-invalid-parameter-type",
params: map[string]interface{}{
"name": "fake-binding",
"role": []string{"fake-role"},
"group": []string{"fake-group"},
"serviceaccount": []string{"fake-namespace:fake-account"},
},
expectErrMsg: "expected string, saw [fake-role] for 'role'",
},
{
name: "test-invalid-serviceaccount",
params: map[string]interface{}{
"name": "fake-binding",
"role": "fake-role",
"group": []string{"fake-group"},
"serviceaccount": []string{"fake-account"},
},
expectErrMsg: "serviceaccount must be <namespace>:<name>",
},
{
name: "test-valid-case",
params: map[string]interface{}{
"name": "fake-binding",
"role": "fake-role",
"user": []string{"fake-user"},
"group": []string{"fake-group"},
"serviceaccount": []string{"fake-namespace:fake-account"},
},
expectBinding: &rbac.RoleBinding{
ObjectMeta: v1.ObjectMeta{
Name: "fake-binding",
},
RoleRef: rbac.RoleRef{
APIGroup: rbac.GroupName,
Kind: "Role",
Name: "fake-role",
},
Subjects: []rbac.Subject{
{
Kind: rbac.UserKind,
APIGroup: "rbac.authorization.k8s.io",
Name: "fake-user",
},
{
Kind: rbac.GroupKind,
APIGroup: "rbac.authorization.k8s.io",
Name: "fake-group",
},
{
Kind: rbac.ServiceAccountKind,
Namespace: "fake-namespace",
Name: "fake-account",
},
},
},
},
}
generator := RoleBindingGeneratorV1{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
obj, err := generator.Generate(tt.params)
switch {
case tt.expectErrMsg != "" && err != nil:
if err.Error() != tt.expectErrMsg {
t.Errorf("test '%s': expect error '%s', but saw '%s'", tt.name, tt.expectErrMsg, err.Error())
}
return
case tt.expectErrMsg != "" && err == nil:
t.Errorf("test '%s': expected error '%s' and didn't get one", tt.name, tt.expectErrMsg)
return
case tt.expectErrMsg == "" && err != nil:
t.Errorf("test '%s': unexpected error %s", tt.name, err.Error())
return
}
if !reflect.DeepEqual(obj.(*rbac.RoleBinding), tt.expectBinding) {
t.Errorf("test '%s': expected:\n%#v\nsaw:\n%#v", tt.name, tt.expectBinding, obj.(*rbac.RoleBinding))
}
})
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,272 @@
/*
Copyright 2015 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"
"io/ioutil"
"os"
"path"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/kubectl/generate"
"k8s.io/kubernetes/pkg/kubectl/util"
"k8s.io/kubernetes/pkg/kubectl/util/hash"
)
// SecretGeneratorV1 supports stable generation of an opaque secret
type SecretGeneratorV1 struct {
// Name of secret (required)
Name string
// Type of secret (optional)
Type string
// FileSources to derive the secret from (optional)
FileSources []string
// LiteralSources to derive the secret from (optional)
LiteralSources []string
// EnvFileSource to derive the secret from (optional)
EnvFileSource string
// AppendHash; if true, derive a hash from the Secret data and type and append it to the name
AppendHash bool
}
// Ensure it supports the generator pattern that uses parameter injection
var _ generate.Generator = &SecretGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ generate.StructuredGenerator = &SecretGeneratorV1{}
// Generate returns a secret using the specified parameters
func (s SecretGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
delegate := &SecretGeneratorV1{}
fromFileStrings, found := genericParams["from-file"]
if found {
fromFileArray, isArray := fromFileStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
}
delegate.FileSources = fromFileArray
delete(genericParams, "from-file")
}
fromLiteralStrings, found := genericParams["from-literal"]
if found {
fromLiteralArray, isArray := fromLiteralStrings.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", fromLiteralStrings)
}
delegate.LiteralSources = fromLiteralArray
delete(genericParams, "from-literal")
}
fromEnvFileString, found := genericParams["from-env-file"]
if found {
fromEnvFile, isString := fromEnvFileString.(string)
if !isString {
return nil, fmt.Errorf("expected string, found :%v", fromEnvFileString)
}
delegate.EnvFileSource = fromEnvFile
delete(genericParams, "from-env-file")
}
hashParam, found := genericParams["append-hash"]
if found {
hashBool, isBool := hashParam.(bool)
if !isBool {
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
}
delegate.AppendHash = hashBool
delete(genericParams, "append-hash")
}
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
}
delegate.Name = params["name"]
delegate.Type = params["type"]
return delegate.StructuredGenerate()
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern
func (s SecretGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "type", Required: false},
{Name: "from-file", Required: false},
{Name: "from-literal", Required: false},
{Name: "from-env-file", Required: false},
{Name: "force", Required: false},
{Name: "append-hash", Required: false},
}
}
// StructuredGenerate outputs a secret object using the configured fields
func (s SecretGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
secret := &v1.Secret{}
secret.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Secret"))
secret.Name = s.Name
secret.Data = map[string][]byte{}
if len(s.Type) > 0 {
secret.Type = v1.SecretType(s.Type)
}
if len(s.FileSources) > 0 {
if err := handleFromFileSources(secret, s.FileSources); err != nil {
return nil, err
}
}
if len(s.LiteralSources) > 0 {
if err := handleFromLiteralSources(secret, s.LiteralSources); err != nil {
return nil, err
}
}
if len(s.EnvFileSource) > 0 {
if err := handleFromEnvFileSource(secret, s.EnvFileSource); err != nil {
return nil, err
}
}
if s.AppendHash {
h, err := hash.SecretHash(secret)
if err != nil {
return nil, err
}
secret.Name = fmt.Sprintf("%s-%s", secret.Name, h)
}
return secret, nil
}
// validate validates required fields are set to support structured generation
func (s SecretGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.EnvFileSource) > 0 && (len(s.FileSources) > 0 || len(s.LiteralSources) > 0) {
return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
}
return nil
}
// handleFromLiteralSources adds the specified literal source information into the provided secret
func handleFromLiteralSources(secret *v1.Secret, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := util.ParseLiteralSource(literalSource)
if err != nil {
return err
}
if err = addKeyFromLiteralToSecret(secret, keyName, []byte(value)); err != nil {
return err
}
}
return nil
}
// handleFromFileSources adds the specified file source information into the provided secret
func handleFromFileSources(secret *v1.Secret, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := util.ParseFileSource(fileSource)
if err != nil {
return err
}
info, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default:
return fmt.Errorf("error reading %s: %v", filePath, err)
}
}
if info.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
if err = addKeyFromFileToSecret(secret, keyName, itemPath); err != nil {
return err
}
}
}
} else {
if err := addKeyFromFileToSecret(secret, keyName, filePath); err != nil {
return err
}
}
}
return nil
}
// handleFromEnvFileSource adds the specified env file source information
// into the provided secret
func handleFromEnvFileSource(secret *v1.Secret, envFileSource string) error {
info, err := os.Stat(envFileSource)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
default:
return fmt.Errorf("error reading %s: %v", envFileSource, err)
}
}
if info.IsDir() {
return fmt.Errorf("env secret file cannot be a directory")
}
return addFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToSecret(secret, key, []byte(value))
})
}
func addKeyFromFileToSecret(secret *v1.Secret, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
return addKeyFromLiteralToSecret(secret, keyName, data)
}
func addKeyFromLiteralToSecret(secret *v1.Secret, keyName string, data []byte) error {
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := secret.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v", keyName, secret.Data)
}
secret.Data[keyName] = data
return nil
}

View File

@ -0,0 +1,181 @@
/*
Copyright 2015 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 (
"encoding/json"
"fmt"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/generate"
"k8s.io/kubernetes/pkg/kubectl/util/hash"
)
// SecretForDockerRegistryGeneratorV1 supports stable generation of a docker registry secret
type SecretForDockerRegistryGeneratorV1 struct {
// Name of secret (required)
Name string
// FileSources to derive the secret from (optional)
FileSources []string
// Username for registry (required)
Username string
// Email for registry (optional)
Email string
// Password for registry (required)
Password string
// Server for registry (required)
Server string
// AppendHash; if true, derive a hash from the Secret and append it to the name
AppendHash bool
}
// Ensure it supports the generator pattern that uses parameter injection
var _ generate.Generator = &SecretForDockerRegistryGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ generate.StructuredGenerator = &SecretForDockerRegistryGeneratorV1{}
// Generate returns a secret using the specified parameters
func (s SecretForDockerRegistryGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
delegate := &SecretForDockerRegistryGeneratorV1{}
hashParam, found := genericParams["append-hash"]
if found {
hashBool, isBool := hashParam.(bool)
if !isBool {
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
}
delegate.AppendHash = hashBool
delete(genericParams, "append-hash")
}
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
}
delegate.Name = params["name"]
delegate.Username = params["docker-username"]
delegate.Email = params["docker-email"]
delegate.Password = params["docker-password"]
delegate.Server = params["docker-server"]
return delegate.StructuredGenerate()
}
// StructuredGenerate outputs a secret object using the configured fields
func (s SecretForDockerRegistryGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
secret := &v1.Secret{}
secret.Name = s.Name
secret.Type = v1.SecretTypeDockerConfigJson
secret.Data = map[string][]byte{}
if len(s.FileSources) > 0 {
if err := handleFromFileSources(secret, s.FileSources); err != nil {
return nil, err
}
}
if len(s.FileSources) == 0 {
dockercfgJSONContent, err := handleDockerCfgJSONContent(s.Username, s.Password, s.Email, s.Server)
if err != nil {
return nil, err
}
secret.Data[v1.DockerConfigJsonKey] = dockercfgJSONContent
}
if s.AppendHash {
h, err := hash.SecretHash(secret)
if err != nil {
return nil, err
}
secret.Name = fmt.Sprintf("%s-%s", secret.Name, h)
}
return secret, nil
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern
func (s SecretForDockerRegistryGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "from-file", Required: false},
{Name: "docker-username", Required: true},
{Name: "docker-email", Required: false},
{Name: "docker-password", Required: true},
{Name: "docker-server", Required: true},
{Name: "append-hash", Required: false},
}
}
// validate validates required fields are set to support structured generation
func (s SecretForDockerRegistryGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.FileSources) == 0 {
if len(s.Username) == 0 {
return fmt.Errorf("username must be specified")
}
if len(s.Password) == 0 {
return fmt.Errorf("password must be specified")
}
if len(s.Server) == 0 {
return fmt.Errorf("server must be specified")
}
}
return nil
}
// handleDockerCfgJSONContent serializes a ~/.docker/config.json file
func handleDockerCfgJSONContent(username, password, email, server string) ([]byte, error) {
dockercfgAuth := DockerConfigEntry{
Username: username,
Password: password,
Email: email,
}
dockerCfgJSON := DockerConfigJSON{
Auths: map[string]DockerConfigEntry{server: dockercfgAuth},
}
return json.Marshal(dockerCfgJSON)
}
// DockerConfigJSON represents a local docker auth config file
// for pulling images.
type DockerConfigJSON struct {
Auths DockerConfig `json:"auths"`
// +optional
HttpHeaders map[string]string `json:"HttpHeaders,omitempty"`
}
// DockerConfig represents the config file used by the docker CLI.
// This config that represents the credentials that should be used
// when pulling images from specific image repositories.
type DockerConfig map[string]DockerConfigEntry
type DockerConfigEntry struct {
Username string
Password string
Email string
}

View File

@ -0,0 +1,131 @@
/*
Copyright 2015 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"
)
func TestSecretForDockerRegistryGenerate(t *testing.T) {
username, password, email, server := "test-user", "test-password", "test-user@example.org", "https://index.docker.io/v1/"
secretData, err := handleDockerCfgJSONContent(username, password, email, server)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
secretDataNoEmail, err := handleDockerCfgJSONContent(username, password, "", server)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tests := []struct {
name string
params map[string]interface{}
expected *v1.Secret
expectErr bool
}{
{
name: "test-valid-use",
params: map[string]interface{}{
"name": "foo",
"docker-server": server,
"docker-username": username,
"docker-password": password,
"docker-email": email,
},
expected: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
v1.DockerConfigJsonKey: secretData,
},
Type: v1.SecretTypeDockerConfigJson,
},
expectErr: false,
},
{
name: "test-valid-use-append-hash",
params: map[string]interface{}{
"name": "foo",
"docker-server": server,
"docker-username": username,
"docker-password": password,
"docker-email": email,
"append-hash": true,
},
expected: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-7566tc6mgc",
},
Data: map[string][]byte{
v1.DockerConfigJsonKey: secretData,
},
Type: v1.SecretTypeDockerConfigJson,
},
expectErr: false,
},
{
name: "test-valid-use-no-email",
params: map[string]interface{}{
"name": "foo",
"docker-server": server,
"docker-username": username,
"docker-password": password,
},
expected: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
v1.DockerConfigJsonKey: secretDataNoEmail,
},
Type: v1.SecretTypeDockerConfigJson,
},
expectErr: false,
},
{
name: "test-missing-required-param",
params: map[string]interface{}{
"name": "foo",
"docker-server": server,
"docker-password": password,
"docker-email": email,
},
expectErr: true,
},
}
generator := SecretForDockerRegistryGeneratorV1{}
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.Secret), tt.expected) {
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", tt.expected, obj.(*v1.Secret))
}
})
}
}

View File

@ -0,0 +1,146 @@
/*
Copyright 2015 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 (
"crypto/tls"
"fmt"
"io/ioutil"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/generate"
"k8s.io/kubernetes/pkg/kubectl/util/hash"
)
// SecretForTLSGeneratorV1 supports stable generation of a TLS secret.
type SecretForTLSGeneratorV1 struct {
// Name is the name of this TLS secret.
Name string
// Key is the path to the user's private key.
Key string
// Cert is the path to the user's public key certificate.
Cert string
// AppendHash; if true, derive a hash from the Secret and append it to the name
AppendHash bool
}
// Ensure it supports the generator pattern that uses parameter injection
var _ generate.Generator = &SecretForTLSGeneratorV1{}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ generate.StructuredGenerator = &SecretForTLSGeneratorV1{}
// Generate returns a secret using the specified parameters
func (s SecretForTLSGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), genericParams)
if err != nil {
return nil, err
}
delegate := &SecretForTLSGeneratorV1{}
hashParam, found := genericParams["append-hash"]
if found {
hashBool, isBool := hashParam.(bool)
if !isBool {
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
}
delegate.AppendHash = hashBool
delete(genericParams, "append-hash")
}
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
}
delegate.Name = params["name"]
delegate.Key = params["key"]
delegate.Cert = params["cert"]
return delegate.StructuredGenerate()
}
// StructuredGenerate outputs a secret object using the configured fields
func (s SecretForTLSGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
tlsCrt, err := readFile(s.Cert)
if err != nil {
return nil, err
}
tlsKey, err := readFile(s.Key)
if err != nil {
return nil, err
}
if _, err := tls.X509KeyPair(tlsCrt, tlsKey); err != nil {
return nil, fmt.Errorf("failed to load key pair %v", err)
}
// TODO: Add more validation.
// 1. If the certificate contains intermediates, it is a valid chain.
// 2. Format etc.
secret := &v1.Secret{}
secret.Name = s.Name
secret.Type = v1.SecretTypeTLS
secret.Data = map[string][]byte{}
secret.Data[v1.TLSCertKey] = []byte(tlsCrt)
secret.Data[v1.TLSPrivateKeyKey] = []byte(tlsKey)
if s.AppendHash {
h, err := hash.SecretHash(secret)
if err != nil {
return nil, err
}
secret.Name = fmt.Sprintf("%s-%s", secret.Name, h)
}
return secret, nil
}
// readFile just reads a file into a byte array.
func readFile(file string) ([]byte, error) {
b, err := ioutil.ReadFile(file)
if err != nil {
return []byte{}, fmt.Errorf("Cannot read file %v, %v", file, err)
}
return b, nil
}
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern
func (s SecretForTLSGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "key", Required: true},
{Name: "cert", Required: true},
{Name: "append-hash", Required: false},
}
}
// validate validates required fields are set to support structured generation
func (s SecretForTLSGeneratorV1) validate() error {
// TODO: This is not strictly necessary. We can generate a self signed cert
// if no key/cert is given. The only requirement is that we either get both
// or none. See test/e2e/ingress_utils for self signed cert generation.
if len(s.Key) == 0 {
return fmt.Errorf("key must be specified")
}
if len(s.Cert) == 0 {
return fmt.Errorf("certificate must be specified")
}
return nil
}

View File

@ -0,0 +1,233 @@
/*
Copyright 2015 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"
"os"
"path"
"reflect"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utiltesting "k8s.io/client-go/util/testing"
)
var rsaCertPEM = `-----BEGIN CERTIFICATE-----
MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
-----END CERTIFICATE-----
`
var rsaKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
-----END RSA PRIVATE KEY-----
`
const mismatchRSAKeyPEM = `-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/665h55hWD4V2
kiQ+B/G9NNfBw69eBibEhI9vWkPUyn36GO2r3HPtRE63wBfFpV486ns9DoZnnAYE
JaGjVNCCqS5tQyMBWp843o66KBrEgBpuddChigvyul33FhD1ImFnN+Vy0ajOJ+1/
Zai28zBXWbxCWEbqz7s8e2UsPlBd0Caj4gcd32yD2BwiHqzB8odToWRUT7l+pS8R
qA1BruQvtjEIrcoWVlE170ZYe7+Apm96A+WvtVRkozPynxHF8SuEiw4hAh0lXR6b
4zZz4tZVV8ev2HpffveV/68GiCyeFDbglqd4sZ/Iga/rwu7bVY/BzFApHwu2hmmV
XLnaa3uVAgMBAAECggEAG+kvnCdtPR7Wvw6z3J2VJ3oW4qQNzfPBEZVhssUC1mB4
f7W+Yt8VsOzdMdXq3yCUmvFS6OdC3rCPI21Bm5pLFKV8DgHUhm7idwfO4/3PHsKu
lV/m7odAA5Xc8oEwCCZu2e8EHHWnQgwGex+SsMCfSCTRvyhNb/qz9TDQ3uVVFL9e
9a4OKqZl/GlRspJSuXhy+RSVulw9NjeX1VRjIbhqpdXAmQNXgShA+gZSQh8T/tgv
XQYsMtg+FUDvcunJQf4OW5BY7IenYBV/GvsnJU8L7oD0wjNSAwe/iLKqV/NpYhre
QR4DsGnmoRYlUlHdHFTTJpReDjWm+vH3T756yDdFAQKBgQD2/sP5dM/aEW7Z1TgS
TG4ts1t8Rhe9escHxKZQR81dfOxBeCJMBDm6ySfR8rvyUM4VsogxBL/RhRQXsjJM
7wN08MhdiXG0J5yy/oNo8W6euD8m8Mk1UmqcZjSgV4vA7zQkvkr6DRJdybKsT9mE
jouEwev8sceS6iBpPw/+Ws8z1QKBgQDG6uYHMfMcS844xKQQWhargdN2XBzeG6TV
YXfNFstNpD84d9zIbpG/AKJF8fKrseUhXkJhkDjFGJTriD3QQsntOFaDOrHMnveV
zGzvC4OTFUUFHe0SVJ0HuLf8YCHoZ+DXEeCKCN6zBXnUue+bt3NvLOf2yN5o9kYx
SIa8O1vIwQKBgEdONXWG65qg/ceVbqKZvhUjen3eHmxtTZhIhVsX34nlzq73567a
aXArMnvB/9Bs05IgAIFmRZpPOQW+RBdByVWxTabzTwgbh3mFUJqzWKQpvNGZIf1q
1axhNUA1BfulEwCojyyxKWQ6HoLwanOCU3T4JxDEokEfpku8EPn1bWwhAoGAAN8A
eOGYHfSbB5ac3VF3rfKYmXkXy0U1uJV/r888vq9Mc5PazKnnS33WOBYyKNxTk4zV
H5ZBGWPdKxbipmnUdox7nIGCS9IaZXaKt5VGUzuRnM8fvafPNDxz2dAV9e2Wh3qV
kCUvzHrmqK7TxMvN3pvEvEju6GjDr+2QYXylD0ECgYAGK5r+y+EhtKkYFLeYReUt
znvSsWq+JCQH/cmtZLaVOldCaMRL625hSl3XPPcMIHE14xi3d4njoXWzvzPcg8L6
vNXk3GiNldACS+vwk4CwEqe5YlZRm5doD07wIdsg2zRlnKsnXNM152OwgmcchDul
rLTt0TTazzwBCgCD0Jkoqg==
-----END PRIVATE KEY-----`
func tearDown(tmpDir string) {
err := os.RemoveAll(tmpDir)
if err != nil {
fmt.Printf("Error in cleaning up test: %v", err)
}
}
func write(path, contents string, t *testing.T) {
f, err := os.Create(path)
if err != nil {
t.Fatalf("Failed to create %v.", path)
}
defer f.Close()
_, err = f.WriteString(contents)
if err != nil {
t.Fatalf("Failed to write to %v.", path)
}
}
func writeKeyPair(tmpDirPath, key, cert string, t *testing.T) (keyPath, certPath string) {
keyPath = path.Join(tmpDirPath, "tls.key")
certPath = path.Join(tmpDirPath, "tls.cert")
write(keyPath, key, t)
write(certPath, cert, t)
return
}
func TestSecretForTLSGenerate(t *testing.T) {
invalidCertTmpDir := utiltesting.MkTmpdirOrDie("tls-test")
defer tearDown(invalidCertTmpDir)
invalidKeyPath, invalidCertPath := writeKeyPair(invalidCertTmpDir, "test", "test", t)
validCertTmpDir := utiltesting.MkTmpdirOrDie("tls-test")
defer tearDown(validCertTmpDir)
validKeyPath, validCertPath := writeKeyPair(validCertTmpDir, rsaKeyPEM, rsaCertPEM, t)
mismatchCertTmpDir := utiltesting.MkTmpdirOrDie("tls-mismatch-test")
defer tearDown(mismatchCertTmpDir)
mismatchKeyPath, mismatchCertPath := writeKeyPair(mismatchCertTmpDir, mismatchRSAKeyPEM, rsaCertPEM, t)
tests := []struct {
name string
params map[string]interface{}
expected *v1.Secret
expectErr bool
}{
{
name: "test-valid-tls-secret",
params: map[string]interface{}{
"name": "foo",
"key": validKeyPath,
"cert": validCertPath,
},
expected: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
v1.TLSCertKey: []byte(rsaCertPEM),
v1.TLSPrivateKeyKey: []byte(rsaKeyPEM),
},
Type: v1.SecretTypeTLS,
},
expectErr: false,
},
{
name: "test-valid-tls-secret-append-hash",
params: map[string]interface{}{
"name": "foo",
"key": validKeyPath,
"cert": validCertPath,
"append-hash": true,
},
expected: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-272h6tt825",
},
Data: map[string][]byte{
v1.TLSCertKey: []byte(rsaCertPEM),
v1.TLSPrivateKeyKey: []byte(rsaKeyPEM),
},
Type: v1.SecretTypeTLS,
},
expectErr: false,
},
{
name: "test-invalid-key-pair",
params: map[string]interface{}{
"name": "foo",
"key": invalidKeyPath,
"cert": invalidCertPath,
},
expected: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
v1.TLSCertKey: []byte("test"),
v1.TLSPrivateKeyKey: []byte("test"),
},
Type: v1.SecretTypeTLS,
},
expectErr: true,
},
{
name: "test-mismatched-key-pair",
params: map[string]interface{}{
"name": "foo",
"key": mismatchKeyPath,
"cert": mismatchCertPath,
},
expected: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
v1.TLSCertKey: []byte(rsaCertPEM),
v1.TLSPrivateKeyKey: []byte(mismatchRSAKeyPEM),
},
Type: v1.SecretTypeTLS,
},
expectErr: true,
},
{
name: "test-missing-required-param",
params: map[string]interface{}{
"name": "foo",
"key": "/tmp/foo.key",
},
expectErr: true,
},
}
generator := SecretForTLSGeneratorV1{}
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.Secret), tt.expected) {
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", tt.expected, obj.(*v1.Secret))
}
})
}
}

View File

@ -0,0 +1,371 @@
/*
Copyright 2015 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 (
"os"
"reflect"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestSecretGenerate(t *testing.T) {
tests := []struct {
name string
setup func(t *testing.T, params map[string]interface{}) func()
params map[string]interface{}
expected *v1.Secret
expectErr bool
}{
{
name: "test1",
params: map[string]interface{}{
"name": "foo",
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{},
},
expectErr: false,
},
{
name: "test2",
params: map[string]interface{}{
"name": "foo",
"append-hash": true,
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-949tdgdkgg",
},
Data: map[string][]byte{},
},
expectErr: false,
},
{
name: "test3",
params: map[string]interface{}{
"name": "foo",
"type": "my-type",
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{},
Type: "my-type",
},
expectErr: false,
},
{
name: "test4",
params: map[string]interface{}{
"name": "foo",
"type": "my-type",
"append-hash": true,
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-dg474f9t76",
},
Data: map[string][]byte{},
Type: "my-type",
},
expectErr: false,
},
{
name: "test5",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1=value1", "key2=value2"},
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
},
},
expectErr: false,
},
{
name: "test6",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1=value1", "key2=value2"},
"append-hash": true,
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-tf72c228m4",
},
Data: map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
},
},
expectErr: false,
},
{
name: "test7",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1value1"},
},
expectErr: true,
},
{
name: "test8",
params: map[string]interface{}{
"name": "foo",
"from-file": []string{"key1=/file=2"},
},
expectErr: true,
},
{
name: "test9",
params: map[string]interface{}{
"name": "foo",
"from-file": []string{"key1==value"},
},
expectErr: true,
},
{
name: "test10",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1==value1"},
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
"key1": []byte("=value1"),
},
},
expectErr: false,
},
{
name: "test11",
params: map[string]interface{}{
"name": "foo",
"from-literal": []string{"key1==value1"},
"append-hash": true,
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-fdcc8tkhh5",
},
Data: map[string][]byte{
"key1": []byte("=value1"),
},
},
expectErr: false,
},
{
name: "test12",
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
params: map[string]interface{}{
"name": "valid_env",
"from-env-file": "file.env",
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "valid_env",
},
Data: map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
},
},
expectErr: false,
},
{
name: "test13",
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
params: map[string]interface{}{
"name": "valid_env",
"from-env-file": "file.env",
"append-hash": true,
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "valid_env-bkb2m2965h",
},
Data: map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
},
},
expectErr: false,
},
{
name: "test14",
setup: func() func(t *testing.T, params map[string]interface{}) func() {
os.Setenv("g_key1", "1")
os.Setenv("g_key2", "2")
return setupEnvFile("g_key1", "g_key2=")
}(),
params: map[string]interface{}{
"name": "getenv",
"from-env-file": "file.env",
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "getenv",
},
Data: map[string][]byte{
"g_key1": []byte("1"),
"g_key2": []byte(""),
},
},
expectErr: false,
},
{
name: "test15",
setup: func() func(t *testing.T, params map[string]interface{}) func() {
os.Setenv("g_key1", "1")
os.Setenv("g_key2", "2")
return setupEnvFile("g_key1", "g_key2=")
}(),
params: map[string]interface{}{
"name": "getenv",
"from-env-file": "file.env",
"append-hash": true,
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "getenv-m7kg2khdb4",
},
Data: map[string][]byte{
"g_key1": []byte("1"),
"g_key2": []byte(""),
},
},
expectErr: false,
},
{
name: "test16",
params: map[string]interface{}{
"name": "too_many_args",
"from-literal": []string{"key1=value1"},
"from-env-file": "file.env",
},
expectErr: true,
},
{
name: "test17",
setup: setupEnvFile("key#1=value1"),
params: map[string]interface{}{
"name": "invalid_key",
"from-env-file": "file.env",
},
expectErr: true,
},
{
name: "test18",
setup: setupEnvFile(" key1= value1"),
params: map[string]interface{}{
"name": "with_spaces",
"from-env-file": "file.env",
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "with_spaces",
},
Data: map[string][]byte{
"key1": []byte(" value1"),
},
},
expectErr: false,
},
{
name: "test19",
setup: setupEnvFile(" key1= value1"),
params: map[string]interface{}{
"name": "with_spaces",
"from-env-file": "file.env",
"append-hash": true,
},
expected: &v1.Secret{
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Secret"},
ObjectMeta: metav1.ObjectMeta{
Name: "with_spaces-4488d5b57d",
},
Data: map[string][]byte{
"key1": []byte(" value1"),
},
},
expectErr: false,
},
}
generator := SecretGeneratorV1{}
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setup != nil {
if teardown := tt.setup(t, tt.params); teardown != nil {
defer teardown()
}
}
obj, err := generator.Generate(tt.params)
if !tt.expectErr && err != nil {
t.Errorf("case %d, unexpected error: %v", i, err)
return
}
if tt.expectErr && err != nil {
return
}
if !reflect.DeepEqual(obj.(*v1.Secret), tt.expected) {
t.Errorf("\ncase %d, expected:\n%#v\nsaw:\n%#v", i, tt.expected, obj.(*v1.Secret))
}
})
}
}

View File

@ -0,0 +1,240 @@
/*
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/kubernetes/pkg/kubectl/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.FromInt(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.FromInt(int(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

@ -0,0 +1,260 @@
/*
Copyright 2016 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/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
type ServiceCommonGeneratorV1 struct {
Name string
TCP []string
Type v1.ServiceType
ClusterIP string
NodePort int
ExternalName string
}
type ServiceClusterIPGeneratorV1 struct {
ServiceCommonGeneratorV1
}
type ServiceNodePortGeneratorV1 struct {
ServiceCommonGeneratorV1
}
type ServiceLoadBalancerGeneratorV1 struct {
ServiceCommonGeneratorV1
}
// TODO: is this really necessary?
type ServiceExternalNameGeneratorV1 struct {
ServiceCommonGeneratorV1
}
func (ServiceClusterIPGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "tcp", Required: true},
{Name: "clusterip", Required: false},
}
}
func (ServiceNodePortGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "tcp", Required: true},
{Name: "nodeport", Required: true},
}
}
func (ServiceLoadBalancerGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "tcp", Required: true},
}
}
func (ServiceExternalNameGeneratorV1) ParamNames() []generate.GeneratorParam {
return []generate.GeneratorParam{
{Name: "name", Required: true},
{Name: "externalname", Required: true},
}
}
func parsePorts(portString string) (int32, intstr.IntOrString, error) {
portStringSlice := strings.Split(portString, ":")
port, err := strconv.Atoi(portStringSlice[0])
if err != nil {
return 0, intstr.FromInt(0), err
}
if errs := validation.IsValidPortNum(port); len(errs) != 0 {
return 0, intstr.FromInt(0), fmt.Errorf(strings.Join(errs, ","))
}
if len(portStringSlice) == 1 {
return int32(port), intstr.FromInt(int(port)), nil
}
var targetPort intstr.IntOrString
if portNum, err := strconv.Atoi(portStringSlice[1]); err != nil {
if errs := validation.IsValidPortName(portStringSlice[1]); len(errs) != 0 {
return 0, intstr.FromInt(0), fmt.Errorf(strings.Join(errs, ","))
}
targetPort = intstr.FromString(portStringSlice[1])
} else {
if errs := validation.IsValidPortNum(portNum); len(errs) != 0 {
return 0, intstr.FromInt(0), fmt.Errorf(strings.Join(errs, ","))
}
targetPort = intstr.FromInt(portNum)
}
return int32(port), targetPort, nil
}
func (s ServiceCommonGeneratorV1) GenerateCommon(params map[string]interface{}) error {
name, isString := params["name"].(string)
if !isString {
return fmt.Errorf("expected string, saw %v for 'name'", name)
}
tcpStrings, isArray := params["tcp"].([]string)
if !isArray {
return fmt.Errorf("expected []string, found :%v", tcpStrings)
}
clusterip, isString := params["clusterip"].(string)
if !isString {
return fmt.Errorf("expected string, saw %v for 'clusterip'", clusterip)
}
externalname, isString := params["externalname"].(string)
if !isString {
return fmt.Errorf("expected string, saw %v for 'externalname'", externalname)
}
s.Name = name
s.TCP = tcpStrings
s.ClusterIP = clusterip
s.ExternalName = externalname
return nil
}
func (s ServiceLoadBalancerGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), params)
if err != nil {
return nil, err
}
delegate := &ServiceCommonGeneratorV1{Type: v1.ServiceTypeLoadBalancer, ClusterIP: ""}
err = delegate.GenerateCommon(params)
if err != nil {
return nil, err
}
return delegate.StructuredGenerate()
}
func (s ServiceNodePortGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), params)
if err != nil {
return nil, err
}
delegate := &ServiceCommonGeneratorV1{Type: v1.ServiceTypeNodePort, ClusterIP: ""}
err = delegate.GenerateCommon(params)
if err != nil {
return nil, err
}
return delegate.StructuredGenerate()
}
func (s ServiceClusterIPGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), params)
if err != nil {
return nil, err
}
delegate := &ServiceCommonGeneratorV1{Type: v1.ServiceTypeClusterIP, ClusterIP: ""}
err = delegate.GenerateCommon(params)
if err != nil {
return nil, err
}
return delegate.StructuredGenerate()
}
func (s ServiceExternalNameGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
err := generate.ValidateParams(s.ParamNames(), params)
if err != nil {
return nil, err
}
delegate := &ServiceCommonGeneratorV1{Type: v1.ServiceTypeExternalName, ClusterIP: ""}
err = delegate.GenerateCommon(params)
if err != nil {
return nil, err
}
return delegate.StructuredGenerate()
}
// validate validates required fields are set to support structured generation
// TODO(xiangpengzhao): validate ports are identity mapped for headless service when we enforce that in validation.validateServicePort.
func (s ServiceCommonGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.Type) == 0 {
return fmt.Errorf("type must be specified")
}
if s.ClusterIP == v1.ClusterIPNone && s.Type != v1.ServiceTypeClusterIP {
return fmt.Errorf("ClusterIP=None can only be used with ClusterIP service type")
}
if s.ClusterIP != v1.ClusterIPNone && len(s.TCP) == 0 && s.Type != v1.ServiceTypeExternalName {
return fmt.Errorf("at least one tcp port specifier must be provided")
}
if s.Type == v1.ServiceTypeExternalName {
if errs := validation.IsDNS1123Subdomain(s.ExternalName); len(errs) != 0 {
return fmt.Errorf("invalid service external name %s", s.ExternalName)
}
}
return nil
}
func (s ServiceCommonGeneratorV1) StructuredGenerate() (runtime.Object, error) {
err := s.validate()
if err != nil {
return nil, err
}
ports := []v1.ServicePort{}
for _, tcpString := range s.TCP {
port, targetPort, err := parsePorts(tcpString)
if err != nil {
return nil, err
}
portName := strings.Replace(tcpString, ":", "-", -1)
ports = append(ports, v1.ServicePort{
Name: portName,
Port: port,
TargetPort: targetPort,
Protocol: v1.Protocol("TCP"),
NodePort: int32(s.NodePort),
})
}
// setup default label and selector
labels := map[string]string{}
labels["app"] = s.Name
selector := map[string]string{}
selector["app"] = s.Name
service := v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
Labels: labels,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceType(s.Type),
Selector: selector,
Ports: ports,
ExternalName: s.ExternalName,
},
}
if len(s.ClusterIP) > 0 {
service.Spec.ClusterIP = s.ClusterIP
}
return &service, nil
}

View File

@ -0,0 +1,321 @@
/*
Copyright 2016 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"
"strings"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func TestServiceBasicGenerate(t *testing.T) {
tests := []struct {
name string
serviceType v1.ServiceType
tcp []string
clusterip string
expected *v1.Service
expectErr bool
}{
{
name: "clusterip-ok",
tcp: []string{"456", "321:908"},
clusterip: "",
serviceType: v1.ServiceTypeClusterIP,
expected: &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "clusterip-ok",
Labels: map[string]string{"app": "clusterip-ok"},
},
Spec: v1.ServiceSpec{Type: "ClusterIP",
Ports: []v1.ServicePort{{Name: "456", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 456, StrVal: ""}, NodePort: 0},
{Name: "321-908", Protocol: "TCP", Port: 321, TargetPort: intstr.IntOrString{Type: 0, IntVal: 908, StrVal: ""}, NodePort: 0}},
Selector: map[string]string{"app": "clusterip-ok"},
ClusterIP: "", ExternalIPs: []string(nil), LoadBalancerIP: ""},
},
expectErr: false,
},
{
name: "clusterip-missing",
serviceType: v1.ServiceTypeClusterIP,
expectErr: true,
},
{
name: "clusterip-none-wrong-type",
tcp: []string{},
clusterip: "None",
serviceType: v1.ServiceTypeNodePort,
expectErr: true,
},
{
name: "clusterip-none-ok",
tcp: []string{},
clusterip: "None",
serviceType: v1.ServiceTypeClusterIP,
expected: &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "clusterip-none-ok",
Labels: map[string]string{"app": "clusterip-none-ok"},
},
Spec: v1.ServiceSpec{Type: "ClusterIP",
Ports: []v1.ServicePort{},
Selector: map[string]string{"app": "clusterip-none-ok"},
ClusterIP: "None", ExternalIPs: []string(nil), LoadBalancerIP: ""},
},
expectErr: false,
},
{
name: "clusterip-none-and-port-mapping",
tcp: []string{"456:9898"},
clusterip: "None",
serviceType: v1.ServiceTypeClusterIP,
expected: &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "clusterip-none-and-port-mapping",
Labels: map[string]string{"app": "clusterip-none-and-port-mapping"},
},
Spec: v1.ServiceSpec{Type: "ClusterIP",
Ports: []v1.ServicePort{{Name: "456-9898", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 9898, StrVal: ""}, NodePort: 0}},
Selector: map[string]string{"app": "clusterip-none-and-port-mapping"},
ClusterIP: "None", ExternalIPs: []string(nil), LoadBalancerIP: ""},
},
expectErr: false,
},
{
name: "loadbalancer-ok",
tcp: []string{"456:9898"},
clusterip: "",
serviceType: v1.ServiceTypeLoadBalancer,
expected: &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "loadbalancer-ok",
Labels: map[string]string{"app": "loadbalancer-ok"},
},
Spec: v1.ServiceSpec{Type: "LoadBalancer",
Ports: []v1.ServicePort{{Name: "456-9898", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 9898, StrVal: ""}, NodePort: 0}},
Selector: map[string]string{"app": "loadbalancer-ok"},
ClusterIP: "", ExternalIPs: []string(nil), LoadBalancerIP: ""},
},
expectErr: false,
},
{
name: "invalid-port",
tcp: []string{"65536"},
clusterip: "None",
serviceType: v1.ServiceTypeClusterIP,
expectErr: true,
},
{
name: "invalid-port-mapping",
tcp: []string{"8080:-abc"},
clusterip: "None",
serviceType: v1.ServiceTypeClusterIP,
expectErr: true,
},
{
expectErr: true,
},
}
for _, test := range tests {
generator := ServiceCommonGeneratorV1{
Name: test.name,
TCP: test.tcp,
Type: test.serviceType,
ClusterIP: test.clusterip,
}
obj, err := generator.StructuredGenerate()
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
if test.expectErr && err != nil {
continue
}
if !reflect.DeepEqual(obj.(*v1.Service), test.expected) {
t.Errorf("test: %v\nexpected:\n%#v\nsaw:\n%#v", test.name, test.expected, obj.(*v1.Service))
}
}
}
func TestParsePorts(t *testing.T) {
tests := []struct {
portString string
expectPort int32
expectTargetPort intstr.IntOrString
expectErr string
}{
{
portString: "3232",
expectPort: 3232,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 3232},
},
{
portString: "1:65535",
expectPort: 1,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 65535},
},
{
portString: "-5:1234",
expectPort: 0,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 0},
expectErr: "must be between 1 and 65535, inclusive",
},
{
portString: "5:65536",
expectPort: 0,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 0},
expectErr: "must be between 1 and 65535, inclusive",
},
{
portString: "test-5:443",
expectPort: 0,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 0},
expectErr: "invalid syntax",
},
{
portString: "5:test-443",
expectPort: 5,
expectTargetPort: intstr.IntOrString{Type: intstr.String, StrVal: "test-443"},
},
{
portString: "5:test*443",
expectPort: 0,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 0},
expectErr: "must contain only alpha-numeric characters (a-z, 0-9), and hyphens (-)",
},
{
portString: "5:",
expectPort: 0,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 0},
expectErr: "must contain at least one letter or number (a-z, 0-9)",
},
{
portString: "5:test--443",
expectPort: 0,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 0},
expectErr: "must not contain consecutive hyphens",
},
{
portString: "5:test443-",
expectPort: 0,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 0},
expectErr: "must not begin or end with a hyphen",
},
{
portString: "3232:1234:4567",
expectPort: 3232,
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 1234},
},
}
for _, test := range tests {
t.Run(test.portString, func(t *testing.T) {
port, targetPort, err := parsePorts(test.portString)
if len(test.expectErr) != 0 {
if !strings.Contains(err.Error(), test.expectErr) {
t.Errorf("parse ports string: %s. Expected err: %s, Got err: %v.", test.portString, test.expectErr, err)
}
}
if !reflect.DeepEqual(targetPort, test.expectTargetPort) || port != test.expectPort {
t.Errorf("parse ports string: %s. Expected port:%d, targetPort:%v, Got port:%d, targetPort:%v.", test.portString, test.expectPort, test.expectTargetPort, port, targetPort)
}
})
}
}
func TestValidateServiceCommonGeneratorV1(t *testing.T) {
tests := []struct {
name string
s ServiceCommonGeneratorV1
expectErr string
}{
{
name: "validate-ok",
s: ServiceCommonGeneratorV1{
Name: "validate-ok",
Type: v1.ServiceTypeClusterIP,
TCP: []string{"123", "234:1234"},
ClusterIP: "",
},
},
{
name: "Name-none",
s: ServiceCommonGeneratorV1{
Type: v1.ServiceTypeClusterIP,
TCP: []string{"123", "234:1234"},
ClusterIP: "",
},
expectErr: "name must be specified",
},
{
name: "Type-none",
s: ServiceCommonGeneratorV1{
Name: "validate-ok",
TCP: []string{"123", "234:1234"},
ClusterIP: "",
},
expectErr: "type must be specified",
},
{
name: "invalid-ClusterIPNone",
s: ServiceCommonGeneratorV1{
Name: "validate-ok",
Type: v1.ServiceTypeNodePort,
TCP: []string{"123", "234:1234"},
ClusterIP: v1.ClusterIPNone,
},
expectErr: "ClusterIP=None can only be used with ClusterIP service type",
},
{
name: "TCP-none",
s: ServiceCommonGeneratorV1{
Name: "validate-ok",
Type: v1.ServiceTypeClusterIP,
ClusterIP: "",
},
expectErr: "at least one tcp port specifier must be provided",
},
{
name: "invalid-ExternalName",
s: ServiceCommonGeneratorV1{
Name: "validate-ok",
Type: v1.ServiceTypeExternalName,
TCP: []string{"123", "234:1234"},
ClusterIP: "",
ExternalName: "@oi:test",
},
expectErr: "invalid service external name",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.s.validate()
if err != nil {
if !strings.Contains(err.Error(), test.expectErr) {
t.Errorf("validate:%s Expected err: %s, Got err: %v", test.name, test.expectErr, err)
}
}
if err == nil && len(test.expectErr) != 0 {
t.Errorf("validate:%s Expected success, Got err: %v", test.name, err)
}
})
}
}

View File

@ -0,0 +1,963 @@
/*
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/kubernetes/pkg/kubectl/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.FromInt(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.FromInt(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.FromInt(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.FromInt(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.FromInt(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.FromInt(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.FromInt(1234),
},
{
Name: "port-2",
Port: 443,
Protocol: v1.ProtocolUDP,
TargetPort: intstr.FromInt(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.FromInt(80),
},
{
Name: "port-2",
Port: 443,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt(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.FromInt(80),
},
{
Name: "port-2",
Port: 8080,
Protocol: v1.ProtocolUDP,
TargetPort: intstr.FromInt(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.FromInt(80),
},
{
Name: "port-2",
Port: 8080,
Protocol: v1.ProtocolUDP,
TargetPort: intstr.FromInt(8080),
},
{
Name: "port-3",
Port: 8081,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt(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.FromInt(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.FromInt(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.FromInt(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.FromInt(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.FromInt(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.FromInt(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.FromInt(80),
},
{
Name: "port-2",
Port: 443,
Protocol: v1.ProtocolSCTP,
TargetPort: intstr.FromInt(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.FromInt(80),
},
{
Name: "port-2",
Port: 8080,
Protocol: v1.ProtocolSCTP,
TargetPort: intstr.FromInt(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.FromInt(80),
},
{
Name: "port-2",
Port: 8080,
Protocol: v1.ProtocolUDP,
TargetPort: intstr.FromInt(8080),
},
{
Name: "port-3",
Port: 8081,
Protocol: v1.ProtocolTCP,
TargetPort: intstr.FromInt(8081),
},
{
Name: "port-4",
Port: 8082,
Protocol: v1.ProtocolSCTP,
TargetPort: intstr.FromInt(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)
}
})
}
}

View File

@ -0,0 +1,52 @@
/*
Copyright 2016 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"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/kubectl/generate"
)
// ServiceAccountGeneratorV1 supports stable generation of a service account
type ServiceAccountGeneratorV1 struct {
// Name of service account
Name string
}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ generate.StructuredGenerator = &ServiceAccountGeneratorV1{}
// StructuredGenerate outputs a service account object using the configured fields
func (g *ServiceAccountGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := g.validate(); err != nil {
return nil, err
}
serviceAccount := &v1.ServiceAccount{}
serviceAccount.Name = g.Name
return serviceAccount, nil
}
// validate validates required fields are set to support structured generation
func (g *ServiceAccountGeneratorV1) validate() error {
if len(g.Name) == 0 {
return fmt.Errorf("name must be specified")
}
return nil
}

View File

@ -0,0 +1,63 @@
/*
Copyright 2016 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"
)
func TestServiceAccountGenerate(t *testing.T) {
tests := []struct {
name string
expected *v1.ServiceAccount
expectErr bool
}{
{
name: "foo",
expected: &v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
},
expectErr: false,
},
{
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
generator := ServiceAccountGeneratorV1{
Name: tt.name,
}
obj, err := generator.StructuredGenerate()
if !tt.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
if tt.expectErr && err != nil {
return
}
if !reflect.DeepEqual(obj.(*v1.ServiceAccount), tt.expected) {
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", tt.expected, obj.(*v1.ServiceAccount))
}
})
}
}