Merge pull request #99733 from soltysh/drop_generators
Clean unused generators
This commit is contained in:
commit
ce1a720bed
@ -1,86 +0,0 @@
|
||||
/*
|
||||
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/kubectl/pkg/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
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
/*
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
/*
|
||||
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/kubectl/pkg/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 := &rbacv1.ClusterRoleBinding{}
|
||||
clusterRoleBinding.Name = s.Name
|
||||
clusterRoleBinding.RoleRef = rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: "ClusterRole",
|
||||
Name: s.ClusterRole,
|
||||
}
|
||||
for _, user := range sets.NewString(s.Users...).List() {
|
||||
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{
|
||||
Kind: rbacv1.UserKind,
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Name: user,
|
||||
})
|
||||
}
|
||||
for _, group := range sets.NewString(s.Groups...).List() {
|
||||
clusterRoleBinding.Subjects = append(clusterRoleBinding.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[0] == "" || tokens[1] == "" {
|
||||
return nil, fmt.Errorf("serviceaccount must be <namespace>:<name>")
|
||||
}
|
||||
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{
|
||||
Kind: rbacv1.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
|
||||
}
|
@ -1,231 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestClusterRoleBindingGenerate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
params map[string]interface{}
|
||||
expected *rbacv1.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: &rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: "ClusterRole",
|
||||
Name: "admin",
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: rbacv1.UserKind,
|
||||
Name: "user",
|
||||
},
|
||||
{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: rbacv1.GroupKind,
|
||||
Name: "group",
|
||||
},
|
||||
{
|
||||
Kind: rbacv1.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: &rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: "ClusterRole",
|
||||
Name: "admin",
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: rbacv1.UserKind,
|
||||
Name: "user1",
|
||||
},
|
||||
{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: rbacv1.UserKind,
|
||||
Name: "user2",
|
||||
},
|
||||
{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: rbacv1.GroupKind,
|
||||
Name: "group1",
|
||||
},
|
||||
{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: rbacv1.GroupKind,
|
||||
Name: "group2",
|
||||
},
|
||||
{
|
||||
Kind: rbacv1.ServiceAccountKind,
|
||||
APIGroup: "",
|
||||
Namespace: "ns1",
|
||||
Name: "name1",
|
||||
},
|
||||
{
|
||||
Kind: rbacv1.ServiceAccountKind,
|
||||
APIGroup: "",
|
||||
Namespace: "ns2",
|
||||
Name: "name2",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid case 3",
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"clusterrole": "admin",
|
||||
},
|
||||
expected: &rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.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.(*rbacv1.ClusterRoleBinding), tt.expected) {
|
||||
t.Errorf("\n[%s] want:\n%#v\ngot:\n%#v", tt.name, tt.expected, obj.(*rbacv1.ClusterRoleBinding))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,299 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/generate"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/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 cmdutil.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 %q, another key by that name already exists in Data for ConfigMap %q", keyName, configMap.Name)
|
||||
}
|
||||
if _, exists := configMap.BinaryData[keyName]; exists {
|
||||
return fmt.Errorf("cannot add key %q, another key by that name already exists in BinaryData for ConfigMap %q", keyName, configMap.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,416 +0,0 @@
|
||||
/*
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
utilrand "k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/kubectl/pkg/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]
|
||||
}
|
||||
name = sanitizeAndUniquify(name)
|
||||
podSpec.Containers = append(podSpec.Containers, v1.Container{Name: name, Image: imageString})
|
||||
}
|
||||
return podSpec
|
||||
}
|
||||
|
||||
// sanitizeAndUniquify: replaces characters like "." or "_" into "-" to follow DNS1123 rules.
|
||||
// Then add random suffix to make it uniquified.
|
||||
func sanitizeAndUniquify(name string) string {
|
||||
if strings.Contains(name, "_") || strings.Contains(name, ".") {
|
||||
name = strings.Replace(name, "_", "-", -1)
|
||||
name = strings.Replace(name, ".", "-", -1)
|
||||
name = fmt.Sprintf("%s-%s", name, utilrand.String(5))
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
/*
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -17,14 +17,6 @@ limitations under the License.
|
||||
package versioned
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
appsv1beta1 "k8s.io/api/apps/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/kubectl/pkg/generate"
|
||||
)
|
||||
|
||||
@ -35,31 +27,8 @@ const (
|
||||
// TODO(sig-cli): Enforce consistent naming for generators here.
|
||||
// See discussion in https://github.com/kubernetes/kubernetes/issues/46237
|
||||
// before you add any more.
|
||||
RunPodV1GeneratorName = "run-pod/v1"
|
||||
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"
|
||||
DeploymentBasicV1Beta1GeneratorName = "deployment-basic/v1beta1"
|
||||
DeploymentBasicAppsV1Beta1GeneratorName = "deployment-basic/apps.v1beta1"
|
||||
DeploymentBasicAppsV1GeneratorName = "deployment-basic/apps.v1"
|
||||
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"
|
||||
PriorityClassV1Beta1GeneratorName = "priorityclass/v1beta1"
|
||||
PriorityClassV1GeneratorName = "priorityclass/v1"
|
||||
RunPodV1GeneratorName = "run-pod/v1"
|
||||
ServiceV2GeneratorName = "service/v2"
|
||||
)
|
||||
|
||||
// DefaultGenerators returns the set of default generators for use in Factory instances
|
||||
@ -68,104 +37,13 @@ func DefaultGenerators(cmdName string) 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{
|
||||
RunPodV1GeneratorName: BasicPod{},
|
||||
}
|
||||
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 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
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
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/kubectl/pkg/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
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
/*
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,214 +0,0 @@
|
||||
/*
|
||||
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/kubectl/pkg/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
|
||||
}
|
@ -1,368 +0,0 @@
|
||||
/*
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
scheduling "k8s.io/api/scheduling/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubectl/pkg/generate"
|
||||
)
|
||||
|
||||
// PriorityClassV1Generator supports stable generation of a priorityClass.
|
||||
type PriorityClassV1Generator struct {
|
||||
Name string
|
||||
Value int32
|
||||
GlobalDefault bool
|
||||
Description string
|
||||
PreemptionPolicy apiv1.PreemptionPolicy
|
||||
}
|
||||
|
||||
// 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},
|
||||
{Name: "preemption-policy", 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)
|
||||
}
|
||||
|
||||
preemptionPolicy := apiv1.PreemptionPolicy(params["preemption-policy"].(string))
|
||||
|
||||
delegate := &PriorityClassV1Generator{Name: name, Value: value, GlobalDefault: globalDefault, Description: description, PreemptionPolicy: preemptionPolicy}
|
||||
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,
|
||||
PreemptionPolicy: &s.PreemptionPolicy,
|
||||
}, nil
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
scheduling "k8s.io/api/scheduling/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPriorityClassV1Generator(t *testing.T) {
|
||||
var (
|
||||
preemptLowerPriority = apiv1.PreemptLowerPriority
|
||||
preemptNever = apiv1.PreemptNever
|
||||
)
|
||||
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",
|
||||
"preemption-policy": "PreemptLowerPriority",
|
||||
},
|
||||
expected: &scheduling.PriorityClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Value: int32(1000),
|
||||
GlobalDefault: false,
|
||||
Description: "high priority class",
|
||||
PreemptionPolicy: &preemptLowerPriority,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "test valid case that field non-preempting is set",
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"value": int32(1000),
|
||||
"global-default": false,
|
||||
"description": "high priority class",
|
||||
"preemption-policy": "Never",
|
||||
},
|
||||
expected: &scheduling.PriorityClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Value: int32(1000),
|
||||
GlobalDefault: false,
|
||||
Description: "high priority class",
|
||||
PreemptionPolicy: &preemptNever,
|
||||
},
|
||||
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",
|
||||
"preemption-policy": "PreemptLowerPriority",
|
||||
},
|
||||
expected: &scheduling.PriorityClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Value: int32(1000),
|
||||
GlobalDefault: true,
|
||||
Description: "high priority class",
|
||||
PreemptionPolicy: &preemptLowerPriority,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "test missing required param",
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"global-default": true,
|
||||
"description": "high priority class",
|
||||
"preemption-policy": "PreemptLowerPriority",
|
||||
},
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
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/kubectl/pkg/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
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
/*
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
/*
|
||||
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/kubectl/pkg/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
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
/*
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,273 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/generate"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/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 cmdutil.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", keyName)
|
||||
}
|
||||
secret.Data[keyName] = data
|
||||
return nil
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
/*
|
||||
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/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubectl/pkg/generate"
|
||||
"k8s.io/kubectl/pkg/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 `datapolicy:"password"`
|
||||
// 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,
|
||||
Auth: encodeDockerConfigFieldAuth(username, password),
|
||||
}
|
||||
|
||||
dockerCfgJSON := DockerConfigJSON{
|
||||
Auths: map[string]DockerConfigEntry{server: dockercfgAuth},
|
||||
}
|
||||
|
||||
return json.Marshal(dockerCfgJSON)
|
||||
}
|
||||
|
||||
func encodeDockerConfigFieldAuth(username, password string) string {
|
||||
fieldValue := username + ":" + password
|
||||
return base64.StdEncoding.EncodeToString([]byte(fieldValue))
|
||||
}
|
||||
|
||||
// DockerConfigJSON represents a local docker auth config file
|
||||
// for pulling images.
|
||||
type DockerConfigJSON struct {
|
||||
Auths DockerConfig `json:"auths" datapolicy:"token"`
|
||||
// +optional
|
||||
HttpHeaders map[string]string `json:"HttpHeaders,omitempty" datapolicy:"token"`
|
||||
}
|
||||
|
||||
// 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 `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty" datapolicy:"password"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Auth string `json:"auth,omitempty" datapolicy:"token"`
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestSecretForDockerRegistryGenerate(t *testing.T) {
|
||||
// Fake values for testing.
|
||||
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-548cm7fgdh",
|
||||
},
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
/*
|
||||
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/kubectl/pkg/generate"
|
||||
"k8s.io/kubectl/pkg/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
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
/*
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,371 +0,0 @@
|
||||
/*
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,257 +0,0 @@
|
||||
/*
|
||||
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/kubectl/pkg/generate"
|
||||
utilsnet "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
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 := utilsnet.ParsePort(portStringSlice[0], true)
|
||||
if err != nil {
|
||||
return 0, intstr.FromInt(0), err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -1,326 +0,0 @@
|
||||
/*
|
||||
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: "parsing \"-5\": invalid syntax",
|
||||
},
|
||||
{
|
||||
portString: "0:1234",
|
||||
expectPort: 0,
|
||||
expectTargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 1234},
|
||||
},
|
||||
{
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
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/kubectl/pkg/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
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -636,12 +636,8 @@ run_rs_tests() {
|
||||
kubectl expose rs frontend --port=80 "${kube_flags[@]:?}"
|
||||
# Post-condition: service exists and the port is unnamed
|
||||
kube::test::get_object_assert 'service frontend' "{{${port_name:?}}} {{${port_field:?}}}" '<no value> 80'
|
||||
# Create a service using service/v1 generator
|
||||
kubectl expose rs frontend --port=80 --name=frontend-2 --generator=service/v1 "${kube_flags[@]:?}"
|
||||
# Post-condition: service exists and the port is named default.
|
||||
kube::test::get_object_assert 'service frontend-2' "{{${port_name:?}}} {{${port_field:?}}}" 'default 80'
|
||||
# Cleanup services
|
||||
kubectl delete service frontend{,-2} "${kube_flags[@]:?}"
|
||||
kubectl delete service frontend "${kube_flags[@]:?}"
|
||||
|
||||
# Test set commands
|
||||
# Pre-condition: frontend replica set exists at generation 1
|
||||
|
@ -1313,17 +1313,13 @@ run_rc_tests() {
|
||||
kubectl expose pod valid-pod --port=444 --name=frontend-3 "${kube_flags[@]}"
|
||||
# Post-condition: service exists and the port is unnamed
|
||||
kube::test::get_object_assert 'service frontend-3' "{{$port_name}} {{$port_field}}" '<no value> 444'
|
||||
# Create a service using service/v1 generator
|
||||
kubectl expose rc frontend --port=80 --name=frontend-4 --generator=service/v1 "${kube_flags[@]}"
|
||||
# Post-condition: service exists and the port is named default.
|
||||
kube::test::get_object_assert 'service frontend-4' "{{$port_name}} {{$port_field}}" 'default 80'
|
||||
# Verify that expose service works without specifying a port.
|
||||
kubectl expose service frontend --name=frontend-5 "${kube_flags[@]}"
|
||||
kubectl expose service frontend --name=frontend-4 "${kube_flags[@]}"
|
||||
# Post-condition: service exists with the same port as the original service.
|
||||
kube::test::get_object_assert 'service frontend-5' "{{$port_field}}" '80'
|
||||
kube::test::get_object_assert 'service frontend-4' "{{$port_field}}" '80'
|
||||
# Cleanup services
|
||||
kubectl delete pod valid-pod "${kube_flags[@]}"
|
||||
kubectl delete service frontend{,-2,-3,-4,-5} "${kube_flags[@]}"
|
||||
kubectl delete service frontend{,-2,-3,-4} "${kube_flags[@]}"
|
||||
|
||||
### Expose negative invalid resource test
|
||||
# Pre-condition: don't need
|
||||
|
Loading…
Reference in New Issue
Block a user