Clean unused generators
This commit is contained in:
@@ -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
|
package versioned
|
||||||
|
|
||||||
import (
|
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"
|
"k8s.io/kubectl/pkg/generate"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,31 +27,8 @@ const (
|
|||||||
// TODO(sig-cli): Enforce consistent naming for generators here.
|
// TODO(sig-cli): Enforce consistent naming for generators here.
|
||||||
// See discussion in https://github.com/kubernetes/kubernetes/issues/46237
|
// See discussion in https://github.com/kubernetes/kubernetes/issues/46237
|
||||||
// before you add any more.
|
// before you add any more.
|
||||||
RunPodV1GeneratorName = "run-pod/v1"
|
RunPodV1GeneratorName = "run-pod/v1"
|
||||||
ServiceV1GeneratorName = "service/v1"
|
ServiceV2GeneratorName = "service/v2"
|
||||||
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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultGenerators returns the set of default generators for use in Factory instances
|
// 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 {
|
switch cmdName {
|
||||||
case "expose":
|
case "expose":
|
||||||
generator = map[string]generate.Generator{
|
generator = map[string]generate.Generator{
|
||||||
ServiceV1GeneratorName: ServiceGeneratorV1{},
|
|
||||||
ServiceV2GeneratorName: ServiceGeneratorV2{},
|
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":
|
case "run":
|
||||||
generator = map[string]generate.Generator{
|
generator = map[string]generate.Generator{
|
||||||
RunPodV1GeneratorName: BasicPod{},
|
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
|
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[@]:?}"
|
kubectl expose rs frontend --port=80 "${kube_flags[@]:?}"
|
||||||
# Post-condition: service exists and the port is unnamed
|
# Post-condition: service exists and the port is unnamed
|
||||||
kube::test::get_object_assert 'service frontend' "{{${port_name:?}}} {{${port_field:?}}}" '<no value> 80'
|
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
|
# Cleanup services
|
||||||
kubectl delete service frontend{,-2} "${kube_flags[@]:?}"
|
kubectl delete service frontend "${kube_flags[@]:?}"
|
||||||
|
|
||||||
# Test set commands
|
# Test set commands
|
||||||
# Pre-condition: frontend replica set exists at generation 1
|
# 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[@]}"
|
kubectl expose pod valid-pod --port=444 --name=frontend-3 "${kube_flags[@]}"
|
||||||
# Post-condition: service exists and the port is unnamed
|
# Post-condition: service exists and the port is unnamed
|
||||||
kube::test::get_object_assert 'service frontend-3' "{{$port_name}} {{$port_field}}" '<no value> 444'
|
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.
|
# 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.
|
# 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
|
# Cleanup services
|
||||||
kubectl delete pod valid-pod "${kube_flags[@]}"
|
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
|
### Expose negative invalid resource test
|
||||||
# Pre-condition: don't need
|
# Pre-condition: don't need
|
||||||
|
Reference in New Issue
Block a user