Merge pull request #119156 from neolit123/1.28-add-support-for-dup-extraargs

kubeadm add support for structured ExtraArgs
This commit is contained in:
Kubernetes Prow Robot 2023-08-21 02:49:22 -07:00 committed by GitHub
commit e043bc08d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 903 additions and 414 deletions

View File

@ -0,0 +1,63 @@
/*
Copyright 2023 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 kubeadm
// GetArgValue traverses an argument slice backwards and returns the value
// of the given argument name and the index where it was found.
// If the argument does not exist an empty string and -1 are returned.
// startIdx defines where the iteration starts. If startIdx is a negative
// value or larger than the size of the argument slice the iteration
// will start from the last element.
func GetArgValue(args []Arg, name string, startIdx int) (string, int) {
if startIdx < 0 || startIdx > len(args)-1 {
startIdx = len(args) - 1
}
for i := startIdx; i >= 0; i-- {
arg := args[i]
if arg.Name == name {
return arg.Value, i
}
}
return "", -1
}
// SetArgValues updates the value of one or more arguments or adds a new
// one if missing. The function works backwards in the argument list.
// nArgs holds how many existing arguments with this name should be set.
// If nArgs is less than 1, all of them will be updated.
func SetArgValues(args []Arg, name, value string, nArgs int) []Arg {
var count int
var found bool
for i := len(args) - 1; i >= 0; i-- {
if args[i].Name == name {
found = true
args[i].Value = value
if nArgs < 1 {
continue
}
count++
if count >= nArgs {
return args
}
}
}
if found {
return args
}
args = append(args, Arg{Name: name, Value: value})
return args
}

View File

@ -0,0 +1,123 @@
/*
Copyright 2023 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 kubeadm
import (
"reflect"
"testing"
)
func TestGetArgValue(t *testing.T) {
var tests = []struct {
testName string
args []Arg
name string
expectedValue string
startIdx int
expectedIdx int
}{
{
testName: "argument exists with non-empty value",
args: []Arg{{Name: "a", Value: "a1"}, {Name: "b", Value: "b1"}, {Name: "c", Value: "c1"}},
name: "b",
expectedValue: "b1",
expectedIdx: 1,
startIdx: -1,
},
{
testName: "argument exists with non-empty value (offset index)",
args: []Arg{{Name: "a", Value: "a1"}, {Name: "b", Value: "b1"}, {Name: "c", Value: "c1"}},
name: "a",
expectedValue: "a1",
expectedIdx: 0,
startIdx: 0,
},
{
testName: "argument exists with empty value",
args: []Arg{{Name: "foo1", Value: ""}, {Name: "foo2", Value: ""}},
name: "foo2",
expectedValue: "",
expectedIdx: 1,
startIdx: -1,
},
{
testName: "argument does not exists",
args: []Arg{{Name: "foo", Value: "bar"}},
name: "z",
expectedValue: "",
expectedIdx: -1,
startIdx: -1,
},
}
for _, rt := range tests {
t.Run(rt.testName, func(t *testing.T) {
value, idx := GetArgValue(rt.args, rt.name, rt.startIdx)
if idx != rt.expectedIdx {
t.Errorf("expected index: %v, got: %v", rt.expectedIdx, idx)
}
if value != rt.expectedValue {
t.Errorf("expected value: %s, got: %s", rt.expectedValue, value)
}
})
}
}
func TestSetArgValues(t *testing.T) {
var tests = []struct {
testName string
args []Arg
name string
value string
nArgs int
expectedArgs []Arg
}{
{
testName: "update 1 argument",
args: []Arg{{Name: "foo", Value: "bar1"}, {Name: "foo", Value: "bar2"}},
name: "foo",
value: "zz",
nArgs: 1,
expectedArgs: []Arg{{Name: "foo", Value: "bar1"}, {Name: "foo", Value: "zz"}},
},
{
testName: "update all arguments",
args: []Arg{{Name: "foo", Value: "bar1"}, {Name: "foo", Value: "bar2"}},
name: "foo",
value: "zz",
nArgs: -1,
expectedArgs: []Arg{{Name: "foo", Value: "zz"}, {Name: "foo", Value: "zz"}},
},
{
testName: "add new argument",
args: []Arg{{Name: "foo", Value: "bar1"}, {Name: "foo", Value: "bar2"}},
name: "z",
value: "zz",
nArgs: -1,
expectedArgs: []Arg{{Name: "foo", Value: "bar1"}, {Name: "foo", Value: "bar2"}, {Name: "z", Value: "zz"}},
},
}
for _, rt := range tests {
t.Run(rt.testName, func(t *testing.T) {
args := SetArgValues(rt.args, rt.name, rt.value, rt.nArgs)
if !reflect.DeepEqual(args, rt.expectedArgs) {
t.Errorf("expected args: %#v, got: %#v", rt.expectedArgs, args)
}
})
}
}

View File

@ -145,11 +145,10 @@ type ClusterConfiguration struct {
// ControlPlaneComponent holds settings common to control plane component of the cluster
type ControlPlaneComponent struct {
// ExtraArgs is an extra set of flags to pass to the control plane component.
// A key in this map is the flag name as it appears on the
// command line except without leading dash(es).
// TODO: This is temporary and ideally we would like to switch all components to
// use ComponentConfig + ConfigMaps.
ExtraArgs map[string]string
// An argument name in this list is the flag name as it appears on the
// command line except without leading dash(es). Extra arguments will override existing
// default arguments. Duplicate extra arguments are allowed.
ExtraArgs []Arg
// ExtraVolumes is an extra set of host volumes, mounted to the control plane component.
ExtraVolumes []HostPathMount
@ -220,9 +219,9 @@ type NodeRegistrationOptions struct {
// KubeletExtraArgs passes through extra arguments to the kubelet. The arguments here are passed to the kubelet command line via the environment file
// kubeadm writes at runtime for the kubelet to source. This overrides the generic base-level configuration in the kubelet-config ConfigMap
// Flags have higher priority when parsing. These values are local and specific to the node kubeadm is executing on.
// A key in this map is the flag name as it appears on the
// command line except without leading dash(es).
KubeletExtraArgs map[string]string
// An argument name in this list is the flag name as it appears on the command line except without leading dash(es).
// Extra arguments will override existing default arguments. Duplicate extra arguments are allowed.
KubeletExtraArgs []Arg
// IgnorePreflightErrors provides a slice of pre-flight errors to be ignored when the current node is registered, e.g. 'IsPrivilegedUser,Swap'.
// Value 'all' ignores errors from all checks.
@ -267,9 +266,10 @@ type LocalEtcd struct {
// ExtraArgs are extra arguments provided to the etcd binary
// when run inside a static pod.
// A key in this map is the flag name as it appears on the
// command line except without leading dash(es).
ExtraArgs map[string]string
// An argument name in this list is the flag name as it appears on the
// command line except without leading dash(es). Extra arguments will override existing
// default arguments. Duplicate extra arguments are allowed.
ExtraArgs []Arg
// ExtraEnvs is an extra set of environment variables to pass to the control plane component.
// Environment variables passed using ExtraEnvs will override any existing environment variables, or *_proxy environment variables that kubeadm adds by default.
@ -505,3 +505,9 @@ type ResetConfiguration struct {
// ComponentConfigMap is a map between a group name (as in GVK group) and a ComponentConfig
type ComponentConfigMap map[string]ComponentConfig
// Arg represents an argument with a name and a value.
type Arg struct {
Name string
Value string
}

View File

@ -17,6 +17,8 @@ limitations under the License.
package v1beta3
import (
"sort"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/conversion"
@ -43,19 +45,65 @@ func Convert_v1beta3_InitConfiguration_To_kubeadm_InitConfiguration(in *InitConf
// Convert_v1beta3_ControlPlaneComponent_To_kubeadm_ControlPlaneComponent is required due to the missing ControlPlaneComponent.ExtraEnvs in v1beta3.
func Convert_v1beta3_ControlPlaneComponent_To_kubeadm_ControlPlaneComponent(in *ControlPlaneComponent, out *kubeadm.ControlPlaneComponent, s conversion.Scope) error {
out.ExtraEnvs = []v1.EnvVar{}
out.ExtraArgs = convertToArgs(in.ExtraArgs)
return autoConvert_v1beta3_ControlPlaneComponent_To_kubeadm_ControlPlaneComponent(in, out, s)
}
func Convert_kubeadm_ControlPlaneComponent_To_v1beta3_ControlPlaneComponent(in *kubeadm.ControlPlaneComponent, out *ControlPlaneComponent, s conversion.Scope) error {
out.ExtraArgs = convertFromArgs(in.ExtraArgs)
return autoConvert_kubeadm_ControlPlaneComponent_To_v1beta3_ControlPlaneComponent(in, out, s)
}
// Convert_v1beta3_LocalEtcd_To_kubeadm_LocalEtcd is required due to the missing LocalEtcd.ExtraEnvs in v1beta3.
func Convert_v1beta3_LocalEtcd_To_kubeadm_LocalEtcd(in *LocalEtcd, out *kubeadm.LocalEtcd, s conversion.Scope) error {
out.ExtraEnvs = []v1.EnvVar{}
out.ExtraArgs = convertToArgs(in.ExtraArgs)
return autoConvert_v1beta3_LocalEtcd_To_kubeadm_LocalEtcd(in, out, s)
}
func Convert_kubeadm_LocalEtcd_To_v1beta3_LocalEtcd(in *kubeadm.LocalEtcd, out *LocalEtcd, s conversion.Scope) error {
out.ExtraArgs = convertFromArgs(in.ExtraArgs)
return autoConvert_kubeadm_LocalEtcd_To_v1beta3_LocalEtcd(in, out, s)
}
func Convert_v1beta3_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(in *NodeRegistrationOptions, out *kubeadm.NodeRegistrationOptions, s conversion.Scope) error {
out.KubeletExtraArgs = convertToArgs(in.KubeletExtraArgs)
return autoConvert_v1beta3_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(in, out, s)
}
func Convert_kubeadm_NodeRegistrationOptions_To_v1beta3_NodeRegistrationOptions(in *kubeadm.NodeRegistrationOptions, out *NodeRegistrationOptions, s conversion.Scope) error {
out.KubeletExtraArgs = convertFromArgs(in.KubeletExtraArgs)
return autoConvert_kubeadm_NodeRegistrationOptions_To_v1beta3_NodeRegistrationOptions(in, out, s)
}
// convertToArgs takes a argument map and converts it to a slice of arguments.
// Te resulting argument slice is sorted alpha-numerically.
func convertToArgs(in map[string]string) []kubeadm.Arg {
if in == nil {
return nil
}
args := make([]kubeadm.Arg, 0, len(in))
for k, v := range in {
args = append(args, kubeadm.Arg{Name: k, Value: v})
}
sort.Slice(args, func(i, j int) bool {
if args[i].Name == args[j].Name {
return args[i].Value < args[j].Value
}
return args[i].Name < args[j].Name
})
return args
}
// convertFromArgs takes a slice of arguments and returns an argument map.
// Duplicate argument keys will be de-duped, where later keys will take precedence.
func convertFromArgs(in []kubeadm.Arg) map[string]string {
if in == nil {
return nil
}
args := make(map[string]string, len(in))
for _, arg := range in {
args[arg.Name] = arg.Value
}
return args
}

View File

@ -0,0 +1,95 @@
/*
Copyright 2023 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 v1beta3
import (
"reflect"
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func TestConvertToArgs(t *testing.T) {
var tests = []struct {
name string
args map[string]string
expectedArgs []kubeadmapi.Arg
}{
{
name: "nil map returns nil args",
args: nil,
expectedArgs: nil,
},
{
name: "valid args are parsed (sorted)",
args: map[string]string{"c": "d", "a": "b"},
expectedArgs: []kubeadmapi.Arg{
{Name: "a", Value: "b"},
{Name: "c", Value: "d"},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
actual := convertToArgs(tc.args)
if !reflect.DeepEqual(tc.expectedArgs, actual) {
t.Errorf("expected args: %v\n\t got: %v\n\t", tc.expectedArgs, actual)
}
})
}
}
func TestConvertFromArgs(t *testing.T) {
var tests = []struct {
name string
args []kubeadmapi.Arg
expectedArgs map[string]string
}{
{
name: "nil args return nil map",
args: nil,
expectedArgs: nil,
},
{
name: "valid args are parsed",
args: []kubeadmapi.Arg{
{Name: "a", Value: "b"},
{Name: "c", Value: "d"},
},
expectedArgs: map[string]string{"a": "b", "c": "d"},
},
{
name: "duplicates are dropped",
args: []kubeadmapi.Arg{
{Name: "a", Value: "b"},
{Name: "c", Value: "d1"},
{Name: "c", Value: "d2"},
},
expectedArgs: map[string]string{"a": "b", "c": "d2"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
actual := convertFromArgs(tc.args)
if !reflect.DeepEqual(tc.expectedArgs, actual) {
t.Errorf("expected args: %v\n\t got: %v\n\t", tc.expectedArgs, actual)
}
})
}
}

View File

@ -174,16 +174,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*NodeRegistrationOptions)(nil), (*kubeadm.NodeRegistrationOptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta3_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(a.(*NodeRegistrationOptions), b.(*kubeadm.NodeRegistrationOptions), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*kubeadm.NodeRegistrationOptions)(nil), (*NodeRegistrationOptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_NodeRegistrationOptions_To_v1beta3_NodeRegistrationOptions(a.(*kubeadm.NodeRegistrationOptions), b.(*NodeRegistrationOptions), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*Patches)(nil), (*kubeadm.Patches)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta3_Patches_To_kubeadm_Patches(a.(*Patches), b.(*kubeadm.Patches), scope)
}); err != nil {
@ -214,6 +204,11 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddConversionFunc((*kubeadm.NodeRegistrationOptions)(nil), (*NodeRegistrationOptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_NodeRegistrationOptions_To_v1beta3_NodeRegistrationOptions(a.(*kubeadm.NodeRegistrationOptions), b.(*NodeRegistrationOptions), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ControlPlaneComponent)(nil), (*kubeadm.ControlPlaneComponent)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta3_ControlPlaneComponent_To_kubeadm_ControlPlaneComponent(a.(*ControlPlaneComponent), b.(*kubeadm.ControlPlaneComponent), scope)
}); err != nil {
@ -229,6 +224,11 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddConversionFunc((*NodeRegistrationOptions)(nil), (*kubeadm.NodeRegistrationOptions)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta3_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(a.(*NodeRegistrationOptions), b.(*kubeadm.NodeRegistrationOptions), scope)
}); err != nil {
return err
}
return nil
}
@ -378,13 +378,13 @@ func Convert_kubeadm_ClusterConfiguration_To_v1beta3_ClusterConfiguration(in *ku
}
func autoConvert_v1beta3_ControlPlaneComponent_To_kubeadm_ControlPlaneComponent(in *ControlPlaneComponent, out *kubeadm.ControlPlaneComponent, s conversion.Scope) error {
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
// WARNING: in.ExtraArgs requires manual conversion: inconvertible types (map[string]string vs []k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm.Arg)
out.ExtraVolumes = *(*[]kubeadm.HostPathMount)(unsafe.Pointer(&in.ExtraVolumes))
return nil
}
func autoConvert_kubeadm_ControlPlaneComponent_To_v1beta3_ControlPlaneComponent(in *kubeadm.ControlPlaneComponent, out *ControlPlaneComponent, s conversion.Scope) error {
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
// WARNING: in.ExtraArgs requires manual conversion: inconvertible types ([]k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm.Arg vs map[string]string)
out.ExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.ExtraVolumes))
// WARNING: in.ExtraEnvs requires manual conversion: does not exist in peer-type
return nil
@ -669,7 +669,7 @@ func autoConvert_v1beta3_LocalEtcd_To_kubeadm_LocalEtcd(in *LocalEtcd, out *kube
return err
}
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
// WARNING: in.ExtraArgs requires manual conversion: inconvertible types (map[string]string vs []k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm.Arg)
out.ServerCertSANs = *(*[]string)(unsafe.Pointer(&in.ServerCertSANs))
out.PeerCertSANs = *(*[]string)(unsafe.Pointer(&in.PeerCertSANs))
return nil
@ -680,7 +680,7 @@ func autoConvert_kubeadm_LocalEtcd_To_v1beta3_LocalEtcd(in *kubeadm.LocalEtcd, o
return err
}
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
// WARNING: in.ExtraArgs requires manual conversion: inconvertible types ([]k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm.Arg vs map[string]string)
// WARNING: in.ExtraEnvs requires manual conversion: does not exist in peer-type
out.ServerCertSANs = *(*[]string)(unsafe.Pointer(&in.ServerCertSANs))
out.PeerCertSANs = *(*[]string)(unsafe.Pointer(&in.PeerCertSANs))
@ -715,32 +715,22 @@ func autoConvert_v1beta3_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOpti
out.Name = in.Name
out.CRISocket = in.CRISocket
out.Taints = *(*[]corev1.Taint)(unsafe.Pointer(&in.Taints))
out.KubeletExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.KubeletExtraArgs))
// WARNING: in.KubeletExtraArgs requires manual conversion: inconvertible types (map[string]string vs []k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm.Arg)
out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors))
out.ImagePullPolicy = corev1.PullPolicy(in.ImagePullPolicy)
return nil
}
// Convert_v1beta3_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions is an autogenerated conversion function.
func Convert_v1beta3_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(in *NodeRegistrationOptions, out *kubeadm.NodeRegistrationOptions, s conversion.Scope) error {
return autoConvert_v1beta3_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(in, out, s)
}
func autoConvert_kubeadm_NodeRegistrationOptions_To_v1beta3_NodeRegistrationOptions(in *kubeadm.NodeRegistrationOptions, out *NodeRegistrationOptions, s conversion.Scope) error {
out.Name = in.Name
out.CRISocket = in.CRISocket
out.Taints = *(*[]corev1.Taint)(unsafe.Pointer(&in.Taints))
out.KubeletExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.KubeletExtraArgs))
// WARNING: in.KubeletExtraArgs requires manual conversion: inconvertible types ([]k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm.Arg vs map[string]string)
out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors))
out.ImagePullPolicy = corev1.PullPolicy(in.ImagePullPolicy)
return nil
}
// Convert_kubeadm_NodeRegistrationOptions_To_v1beta3_NodeRegistrationOptions is an autogenerated conversion function.
func Convert_kubeadm_NodeRegistrationOptions_To_v1beta3_NodeRegistrationOptions(in *kubeadm.NodeRegistrationOptions, out *NodeRegistrationOptions, s conversion.Scope) error {
return autoConvert_kubeadm_NodeRegistrationOptions_To_v1beta3_NodeRegistrationOptions(in, out, s)
}
func autoConvert_v1beta3_Patches_To_kubeadm_Patches(in *Patches, out *kubeadm.Patches, s conversion.Scope) error {
out.Directory = in.Directory
return nil

View File

@ -26,9 +26,12 @@ limitations under the License.
//
// - TODO https://github.com/kubernetes/kubeadm/issues/2890
// - Support custom environment variables in control plane components under `ClusterConfiguration`.
// Use `APIServer.ExtraEnvs`, `ControllerManager.ExtraEnvs`, `Scheduler.ExtraEnvs`, `Etcd.Local.ExtraEnvs`.
// Use `APIServer.ExtraEnvs`, `ControllerManager.ExtraEnvs`, `Scheduler.ExtraEnvs`, `Etcd.Local.ExtraEnvs`.
// - The ResetConfiguration API type is now supported in v1beta4. Users are able to reset a node by passing a --config file to "kubeadm reset".
// - `dry-run` mode in is now configurable in InitConfiguration and JoinConfiguration config files.
// - Replace the existing string/string extra argument maps with structured extra arguments that support duplicates.
// The change applies to `ClusterConfiguration` - `APIServer.ExtraArgs, `ControllerManager.ExtraArgs`,
// `Scheduler.ExtraArgs`, `Etcd.Local.ExtraArgs`. Also to `NodeRegistrationOptions.KubeletExtraArgs`.
//
// Migration from old kubeadm config versions
//

View File

@ -144,12 +144,11 @@ type ClusterConfiguration struct {
// ControlPlaneComponent holds settings common to control plane component of the cluster
type ControlPlaneComponent struct {
// ExtraArgs is an extra set of flags to pass to the control plane component.
// A key in this map is the flag name as it appears on the
// command line except without leading dash(es).
// TODO: This is temporary and ideally we would like to switch all components to
// use ComponentConfig + ConfigMaps.
// An argument name in this list is the flag name as it appears on the
// command line except without leading dash(es). Extra arguments will override existing
// default arguments. Duplicate extra arguments are allowed.
// +optional
ExtraArgs map[string]string `json:"extraArgs,omitempty"`
ExtraArgs []Arg `json:"extraArgs,omitempty"`
// ExtraVolumes is an extra set of host volumes, mounted to the control plane component.
// +optional
@ -232,10 +231,10 @@ type NodeRegistrationOptions struct {
// KubeletExtraArgs passes through extra arguments to the kubelet. The arguments here are passed to the kubelet command line via the environment file
// kubeadm writes at runtime for the kubelet to source. This overrides the generic base-level configuration in the kubelet-config ConfigMap
// Flags have higher priority when parsing. These values are local and specific to the node kubeadm is executing on.
// A key in this map is the flag name as it appears on the
// command line except without leading dash(es).
// An argument name in this list is the flag name as it appears on the command line except without leading dash(es).
// Extra arguments will override existing default arguments. Duplicate extra arguments are allowed.
// +optional
KubeletExtraArgs map[string]string `json:"kubeletExtraArgs,omitempty"`
KubeletExtraArgs []Arg `json:"kubeletExtraArgs,omitempty"`
// IgnorePreflightErrors provides a slice of pre-flight errors to be ignored when the current node is registered, e.g. 'IsPrivilegedUser,Swap'.
// Value 'all' ignores errors from all checks.
@ -287,10 +286,11 @@ type LocalEtcd struct {
// ExtraArgs are extra arguments provided to the etcd binary
// when run inside a static pod.
// A key in this map is the flag name as it appears on the
// command line except without leading dash(es).
// An argument name in this list is the flag name as it appears on the
// command line except without leading dash(es). Extra arguments will override existing
// default arguments. Duplicate extra arguments are allowed.
// +optional
ExtraArgs map[string]string `json:"extraArgs,omitempty"`
ExtraArgs []Arg `json:"extraArgs,omitempty"`
// ExtraEnvs is an extra set of environment variables to pass to the control plane component.
// Environment variables passed using ExtraEnvs will override any existing environment variables, or *_proxy environment variables that kubeadm adds by default.
@ -500,3 +500,9 @@ type ResetConfiguration struct {
// +optional
SkipPhases []string `json:"skipPhases,omitempty"`
}
// Arg represents an argument with a name and a value.
type Arg struct {
Name string `json:"name"`
Value string `json:"value"`
}

View File

@ -59,6 +59,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*Arg)(nil), (*kubeadm.Arg)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta4_Arg_To_kubeadm_Arg(a.(*Arg), b.(*kubeadm.Arg), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*kubeadm.Arg)(nil), (*Arg)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_Arg_To_v1beta4_Arg(a.(*kubeadm.Arg), b.(*Arg), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*BootstrapTokenDiscovery)(nil), (*kubeadm.BootstrapTokenDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta4_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(a.(*BootstrapTokenDiscovery), b.(*kubeadm.BootstrapTokenDiscovery), scope)
}); err != nil {
@ -292,6 +302,28 @@ func Convert_kubeadm_APIServer_To_v1beta4_APIServer(in *kubeadm.APIServer, out *
return autoConvert_kubeadm_APIServer_To_v1beta4_APIServer(in, out, s)
}
func autoConvert_v1beta4_Arg_To_kubeadm_Arg(in *Arg, out *kubeadm.Arg, s conversion.Scope) error {
out.Name = in.Name
out.Value = in.Value
return nil
}
// Convert_v1beta4_Arg_To_kubeadm_Arg is an autogenerated conversion function.
func Convert_v1beta4_Arg_To_kubeadm_Arg(in *Arg, out *kubeadm.Arg, s conversion.Scope) error {
return autoConvert_v1beta4_Arg_To_kubeadm_Arg(in, out, s)
}
func autoConvert_kubeadm_Arg_To_v1beta4_Arg(in *kubeadm.Arg, out *Arg, s conversion.Scope) error {
out.Name = in.Name
out.Value = in.Value
return nil
}
// Convert_kubeadm_Arg_To_v1beta4_Arg is an autogenerated conversion function.
func Convert_kubeadm_Arg_To_v1beta4_Arg(in *kubeadm.Arg, out *Arg, s conversion.Scope) error {
return autoConvert_kubeadm_Arg_To_v1beta4_Arg(in, out, s)
}
func autoConvert_v1beta4_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(in *BootstrapTokenDiscovery, out *kubeadm.BootstrapTokenDiscovery, s conversion.Scope) error {
out.Token = in.Token
out.APIServerEndpoint = in.APIServerEndpoint
@ -388,7 +420,7 @@ func Convert_kubeadm_ClusterConfiguration_To_v1beta4_ClusterConfiguration(in *ku
}
func autoConvert_v1beta4_ControlPlaneComponent_To_kubeadm_ControlPlaneComponent(in *ControlPlaneComponent, out *kubeadm.ControlPlaneComponent, s conversion.Scope) error {
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.ExtraArgs = *(*[]kubeadm.Arg)(unsafe.Pointer(&in.ExtraArgs))
out.ExtraVolumes = *(*[]kubeadm.HostPathMount)(unsafe.Pointer(&in.ExtraVolumes))
out.ExtraEnvs = *(*[]corev1.EnvVar)(unsafe.Pointer(&in.ExtraEnvs))
return nil
@ -400,7 +432,7 @@ func Convert_v1beta4_ControlPlaneComponent_To_kubeadm_ControlPlaneComponent(in *
}
func autoConvert_kubeadm_ControlPlaneComponent_To_v1beta4_ControlPlaneComponent(in *kubeadm.ControlPlaneComponent, out *ControlPlaneComponent, s conversion.Scope) error {
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.ExtraArgs = *(*[]Arg)(unsafe.Pointer(&in.ExtraArgs))
out.ExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.ExtraVolumes))
out.ExtraEnvs = *(*[]corev1.EnvVar)(unsafe.Pointer(&in.ExtraEnvs))
return nil
@ -681,7 +713,7 @@ func autoConvert_v1beta4_LocalEtcd_To_kubeadm_LocalEtcd(in *LocalEtcd, out *kube
return err
}
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.ExtraArgs = *(*[]kubeadm.Arg)(unsafe.Pointer(&in.ExtraArgs))
out.ExtraEnvs = *(*[]corev1.EnvVar)(unsafe.Pointer(&in.ExtraEnvs))
out.ServerCertSANs = *(*[]string)(unsafe.Pointer(&in.ServerCertSANs))
out.PeerCertSANs = *(*[]string)(unsafe.Pointer(&in.PeerCertSANs))
@ -698,7 +730,7 @@ func autoConvert_kubeadm_LocalEtcd_To_v1beta4_LocalEtcd(in *kubeadm.LocalEtcd, o
return err
}
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.ExtraArgs = *(*[]Arg)(unsafe.Pointer(&in.ExtraArgs))
out.ExtraEnvs = *(*[]corev1.EnvVar)(unsafe.Pointer(&in.ExtraEnvs))
out.ServerCertSANs = *(*[]string)(unsafe.Pointer(&in.ServerCertSANs))
out.PeerCertSANs = *(*[]string)(unsafe.Pointer(&in.PeerCertSANs))
@ -738,7 +770,7 @@ func autoConvert_v1beta4_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOpti
out.Name = in.Name
out.CRISocket = in.CRISocket
out.Taints = *(*[]corev1.Taint)(unsafe.Pointer(&in.Taints))
out.KubeletExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.KubeletExtraArgs))
out.KubeletExtraArgs = *(*[]kubeadm.Arg)(unsafe.Pointer(&in.KubeletExtraArgs))
out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors))
out.ImagePullPolicy = corev1.PullPolicy(in.ImagePullPolicy)
return nil
@ -753,7 +785,7 @@ func autoConvert_kubeadm_NodeRegistrationOptions_To_v1beta4_NodeRegistrationOpti
out.Name = in.Name
out.CRISocket = in.CRISocket
out.Taints = *(*[]corev1.Taint)(unsafe.Pointer(&in.Taints))
out.KubeletExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.KubeletExtraArgs))
out.KubeletExtraArgs = *(*[]Arg)(unsafe.Pointer(&in.KubeletExtraArgs))
out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors))
out.ImagePullPolicy = corev1.PullPolicy(in.ImagePullPolicy)
return nil

View File

@ -71,6 +71,22 @@ func (in *APIServer) DeepCopy() *APIServer {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Arg) DeepCopyInto(out *Arg) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Arg.
func (in *Arg) DeepCopy() *Arg {
if in == nil {
return nil
}
out := new(Arg)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BootstrapTokenDiscovery) DeepCopyInto(out *BootstrapTokenDiscovery) {
*out = *in
@ -135,10 +151,8 @@ func (in *ControlPlaneComponent) DeepCopyInto(out *ControlPlaneComponent) {
*out = *in
if in.ExtraArgs != nil {
in, out := &in.ExtraArgs, &out.ExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
*out = make([]Arg, len(*in))
copy(*out, *in)
}
if in.ExtraVolumes != nil {
in, out := &in.ExtraVolumes, &out.ExtraVolumes
@ -417,10 +431,8 @@ func (in *LocalEtcd) DeepCopyInto(out *LocalEtcd) {
out.ImageMeta = in.ImageMeta
if in.ExtraArgs != nil {
in, out := &in.ExtraArgs, &out.ExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
*out = make([]Arg, len(*in))
copy(*out, *in)
}
if in.ExtraEnvs != nil {
in, out := &in.ExtraEnvs, &out.ExtraEnvs
@ -480,10 +492,8 @@ func (in *NodeRegistrationOptions) DeepCopyInto(out *NodeRegistrationOptions) {
}
if in.KubeletExtraArgs != nil {
in, out := &in.KubeletExtraArgs, &out.KubeletExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
*out = make([]Arg, len(*in))
copy(*out, *in)
}
if in.IgnorePreflightErrors != nil {
in, out := &in.IgnorePreflightErrors, &out.IgnorePreflightErrors

View File

@ -65,6 +65,8 @@ func ValidateClusterConfiguration(c *kubeadm.ClusterConfiguration) field.ErrorLi
allErrs = append(allErrs, ValidateDNS(&c.DNS, field.NewPath("dns"))...)
allErrs = append(allErrs, ValidateNetworking(c, field.NewPath("networking"))...)
allErrs = append(allErrs, ValidateAPIServer(&c.APIServer, field.NewPath("apiServer"))...)
allErrs = append(allErrs, ValidateControllerManager(&c.ControllerManager, field.NewPath("controllerManager"))...)
allErrs = append(allErrs, ValidateScheduler(&c.Scheduler, field.NewPath("scheduler"))...)
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificatesDir"))...)
allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("featureGates"))...)
allErrs = append(allErrs, ValidateHostPort(c.ControlPlaneEndpoint, field.NewPath("controlPlaneEndpoint"))...)
@ -78,6 +80,21 @@ func ValidateClusterConfiguration(c *kubeadm.ClusterConfiguration) field.ErrorLi
func ValidateAPIServer(a *kubeadm.APIServer, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateCertSANs(a.CertSANs, fldPath.Child("certSANs"))...)
allErrs = append(allErrs, ValidateExtraArgs(a.ExtraArgs, fldPath.Child("extraArgs"))...)
return allErrs
}
// ValidateControllerManager validates the controller manager object and collects all encountered errors
func ValidateControllerManager(a *kubeadm.ControlPlaneComponent, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateExtraArgs(a.ExtraArgs, fldPath.Child("extraArgs"))...)
return allErrs
}
// ValidateScheduler validates the scheduler object and collects all encountered errors
func ValidateScheduler(a *kubeadm.ControlPlaneComponent, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateExtraArgs(a.ExtraArgs, fldPath.Child("extraArgs"))...)
return allErrs
}
@ -116,6 +133,7 @@ func ValidateNodeRegistrationOptions(nro *kubeadm.NodeRegistrationOptions, fldPa
}
}
allErrs = append(allErrs, ValidateSocketPath(nro.CRISocket, fldPath.Child("criSocket"))...)
allErrs = append(allErrs, ValidateExtraArgs(nro.KubeletExtraArgs, fldPath.Child("kubeletExtraArgs"))...)
// TODO: Maybe validate .Taints as well in the future using something like validateNodeTaints() in pkg/apis/core/validation
return allErrs
}
@ -288,6 +306,7 @@ func ValidateEtcd(e *kubeadm.Etcd, fldPath *field.Path) field.ErrorList {
if len(e.Local.ImageRepository) > 0 {
allErrs = append(allErrs, ValidateImageRepository(e.Local.ImageRepository, localPath.Child("imageRepository"))...)
}
allErrs = append(allErrs, ValidateExtraArgs(e.Local.ExtraArgs, localPath.Child("extraArgs"))...)
}
if e.External != nil {
requireHTTPS := true
@ -479,9 +498,10 @@ func getClusterNodeMask(c *kubeadm.ClusterConfiguration, isIPv6 bool) (int, erro
maskArg = "node-cidr-mask-size-ipv4"
}
if v, ok := c.ControllerManager.ExtraArgs[maskArg]; ok && v != "" {
maskValue, _ := kubeadm.GetArgValue(c.ControllerManager.ExtraArgs, maskArg, -1)
if len(maskValue) != 0 {
// assume it is an integer, if not it will fail later
maskSize, err = strconv.Atoi(v)
maskSize, err = strconv.Atoi(maskValue)
if err != nil {
return 0, errors.Wrapf(err, "could not parse the value of the kube-controller-manager flag %s as an integer", maskArg)
}
@ -519,7 +539,8 @@ func ValidateNetworking(c *kubeadm.ClusterConfiguration, fldPath *field.Path) fi
}
if len(c.Networking.PodSubnet) != 0 {
allErrs = append(allErrs, ValidateIPNetFromString(c.Networking.PodSubnet, constants.MinimumAddressesInPodSubnet, fldPath.Child("podSubnet"))...)
if c.ControllerManager.ExtraArgs["allocate-node-cidrs"] != "false" {
val, _ := kubeadm.GetArgValue(c.ControllerManager.ExtraArgs, "allocate-node-cidrs", -1)
if val != "false" {
// Pod subnet was already validated, we need to validate now against the node-mask
allErrs = append(allErrs, ValidatePodSubnetNodeMask(c.Networking.PodSubnet, c, fldPath.Child("podSubnet"))...)
}
@ -676,3 +697,16 @@ func ValidateResetConfiguration(c *kubeadm.ResetConfiguration) field.ErrorList {
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificatesDir"))...)
return allErrs
}
// ValidateExtraArgs validates a set of arguments and collects all encountered errors
func ValidateExtraArgs(args []kubeadm.Arg, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for idx, arg := range args {
if len(arg.Name) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, fmt.Sprintf("index %d", idx), "argument has no name"))
}
}
return allErrs
}

View File

@ -254,24 +254,24 @@ func TestValidatePodSubnetNodeMask(t *testing.T) {
var tests = []struct {
name string
subnet string
cmExtraArgs map[string]string
cmExtraArgs []kubeadmapi.Arg
expected bool
}{
// dual-stack:
{"dual IPv4 only, but mask too small. Default node-mask", "10.0.0.16/29", nil, false},
{"dual IPv4 only, but mask too small. Configured node-mask", "10.0.0.16/24", map[string]string{"node-cidr-mask-size-ipv4": "23"}, false},
{"dual IPv4 only, but mask too small. Configured node-mask", "10.0.0.16/24", []kubeadmapi.Arg{{Name: "node-cidr-mask-size-ipv4", Value: "23"}}, false},
{"dual IPv6 only, but mask too small. Default node-mask", "2001:db8::1/112", nil, false},
{"dual IPv6 only, but mask too small. Configured node-mask", "2001:db8::1/64", map[string]string{"node-cidr-mask-size-ipv6": "24"}, false},
{"dual IPv6 only, but mask too small. Configured node-mask", "2001:db8::1/64", []kubeadmapi.Arg{{Name: "node-cidr-mask-size-ipv6", Value: "24"}}, false},
{"dual IPv6 only, but mask difference greater than 16. Default node-mask", "2001:db8::1/12", nil, false},
{"dual IPv6 only, but mask difference greater than 16. Configured node-mask", "2001:db8::1/64", map[string]string{"node-cidr-mask-size-ipv6": "120"}, false},
{"dual IPv6 only, but mask difference greater than 16. Configured node-mask", "2001:db8::1/64", []kubeadmapi.Arg{{Name: "node-cidr-mask-size-ipv6", Value: "120"}}, false},
{"dual IPv4 only CIDR", "10.0.0.16/12", nil, true},
{"dual IPv6 only CIDR", "2001:db8::/48", nil, true},
{"dual, but IPv4 mask too small. Default node-mask", "10.0.0.16/29,2001:db8::/48", nil, false},
{"dual, but IPv4 mask too small. Configured node-mask", "10.0.0.16/24,2001:db8::/48", map[string]string{"node-cidr-mask-size-ipv4": "23"}, false},
{"dual, but IPv4 mask too small. Configured node-mask", "10.0.0.16/24,2001:db8::/48", []kubeadmapi.Arg{{Name: "node-cidr-mask-size-ipv4", Value: "23"}}, false},
{"dual, but IPv6 mask too small. Default node-mask", "2001:db8::1/112,10.0.0.16/16", nil, false},
{"dual, but IPv6 mask too small. Configured node-mask", "10.0.0.16/16,2001:db8::1/64", map[string]string{"node-cidr-mask-size-ipv6": "24"}, false},
{"dual, but IPv6 mask too small. Configured node-mask", "10.0.0.16/16,2001:db8::1/64", []kubeadmapi.Arg{{Name: "node-cidr-mask-size-ipv6", Value: "24"}}, false},
{"dual, but mask difference greater than 16. Default node-mask", "2001:db8::1/12,10.0.0.16/16", nil, false},
{"dual, but mask difference greater than 16. Configured node-mask", "10.0.0.16/16,2001:db8::1/64", map[string]string{"node-cidr-mask-size-ipv6": "120"}, false},
{"dual, but mask difference greater than 16. Configured node-mask", "10.0.0.16/16,2001:db8::1/64", []kubeadmapi.Arg{{Name: "node-cidr-mask-size-ipv6", Value: "120"}}, false},
{"dual IPv4 IPv6", "2001:db8::/48,10.0.0.16/12", nil, true},
{"dual IPv6 IPv4", "2001:db8::/48,10.0.0.16/12", nil, true},
}
@ -1205,7 +1205,10 @@ func TestGetClusterNodeMask(t *testing.T) {
name: "dual ipv4 custom mask",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"node-cidr-mask-size": "21", "node-cidr-mask-size-ipv4": "23"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "node-cidr-mask-size", Value: "21"},
{Name: "node-cidr-mask-size-ipv4", Value: "23"},
},
},
},
isIPv6: false,
@ -1221,7 +1224,9 @@ func TestGetClusterNodeMask(t *testing.T) {
name: "dual ipv6 custom mask",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"node-cidr-mask-size-ipv6": "83"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "node-cidr-mask-size-ipv6", Value: "83"},
},
},
},
isIPv6: true,
@ -1231,7 +1236,9 @@ func TestGetClusterNodeMask(t *testing.T) {
name: "dual ipv4 custom mask",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"node-cidr-mask-size-ipv4": "23"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "node-cidr-mask-size-ipv4", Value: "23"},
},
},
},
isIPv6: false,
@ -1241,7 +1248,9 @@ func TestGetClusterNodeMask(t *testing.T) {
name: "dual ipv4 wrong mask",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"node-cidr-mask-size-ipv4": "aa"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "node-cidr-mask-size-ipv4", Value: "aa"},
},
},
},
isIPv6: false,
@ -1251,7 +1260,9 @@ func TestGetClusterNodeMask(t *testing.T) {
name: "dual ipv6 default mask and legacy flag",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"node-cidr-mask-size": "23"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "node-cidr-mask-size", Value: "23"},
},
},
},
isIPv6: true,
@ -1261,7 +1272,10 @@ func TestGetClusterNodeMask(t *testing.T) {
name: "dual ipv6 custom mask and legacy flag",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"node-cidr-mask-size": "23", "node-cidr-mask-size-ipv6": "83"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "node-cidr-mask-size", Value: "23"},
{Name: "node-cidr-mask-size-ipv6", Value: "83"},
},
},
},
isIPv6: true,
@ -1271,7 +1285,10 @@ func TestGetClusterNodeMask(t *testing.T) {
name: "dual ipv6 custom mask and wrong flag",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"node-cidr-mask-size": "23", "node-cidr-mask-size-ipv6": "a83"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "node-cidr-mask-size", Value: "23"},
{Name: "node-cidr-mask-size-ipv6", Value: "a83"},
},
},
},
isIPv6: true,
@ -1390,3 +1407,34 @@ func TestValidateAbsolutePath(t *testing.T) {
}
}
}
func TestValidateExtraArgs(t *testing.T) {
var tests = []struct {
name string
args []kubeadmapi.Arg
expectedErrors int
}{
{
name: "valid argument",
args: []kubeadmapi.Arg{{Name: "foo", Value: "bar"}},
expectedErrors: 0,
},
{
name: "invalid one argument",
args: []kubeadmapi.Arg{{Name: "", Value: "bar"}},
expectedErrors: 1,
},
{
name: "invalid two arguments",
args: []kubeadmapi.Arg{{Name: "", Value: "foo"}, {Name: "", Value: "bar"}},
expectedErrors: 2,
},
}
for _, tc := range tests {
actual := ValidateExtraArgs(tc.args, nil)
if len(actual) != tc.expectedErrors {
t.Errorf("case %q:\n\t expected errors: %v\n\t got: %v\n\t errors: %v", tc.name, tc.expectedErrors, len(actual), actual)
}
}
}

View File

@ -71,6 +71,22 @@ func (in *APIServer) DeepCopy() *APIServer {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Arg) DeepCopyInto(out *Arg) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Arg.
func (in *Arg) DeepCopy() *Arg {
if in == nil {
return nil
}
out := new(Arg)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BootstrapTokenDiscovery) DeepCopyInto(out *BootstrapTokenDiscovery) {
*out = *in
@ -164,10 +180,8 @@ func (in *ControlPlaneComponent) DeepCopyInto(out *ControlPlaneComponent) {
*out = *in
if in.ExtraArgs != nil {
in, out := &in.ExtraArgs, &out.ExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
*out = make([]Arg, len(*in))
copy(*out, *in)
}
if in.ExtraVolumes != nil {
in, out := &in.ExtraVolumes, &out.ExtraVolumes
@ -447,10 +461,8 @@ func (in *LocalEtcd) DeepCopyInto(out *LocalEtcd) {
out.ImageMeta = in.ImageMeta
if in.ExtraArgs != nil {
in, out := &in.ExtraArgs, &out.ExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
*out = make([]Arg, len(*in))
copy(*out, *in)
}
if in.ExtraEnvs != nil {
in, out := &in.ExtraEnvs, &out.ExtraEnvs
@ -510,10 +522,8 @@ func (in *NodeRegistrationOptions) DeepCopyInto(out *NodeRegistrationOptions) {
}
if in.KubeletExtraArgs != nil {
in, out := &in.KubeletExtraArgs, &out.KubeletExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
*out = make([]Arg, len(*in))
copy(*out, *in)
}
if in.IgnorePreflightErrors != nil {
in, out := &in.IgnorePreflightErrors, &out.IgnorePreflightErrors

View File

@ -54,6 +54,9 @@ func AddIgnorePreflightErrorsFlag(fs *pflag.FlagSet, ignorePreflightErrors *[]st
// AddControlPlanExtraArgsFlags adds the ExtraArgs flags for control plane components
func AddControlPlanExtraArgsFlags(fs *pflag.FlagSet, apiServerExtraArgs, controllerManagerExtraArgs, schedulerExtraArgs *map[string]string) {
// TODO: https://github.com/kubernetes/kubeadm/issues/1601
// Either deprecate these flags or handle duplicate keys.
// Currently the map[string]string returned by NewMapStringString() doesn't allow this.
fs.Var(cliflag.NewMapStringString(apiServerExtraArgs), APIServerExtraArgs, "A set of extra flags to pass to the API Server or override default ones in form of <flagname>=<value>")
fs.Var(cliflag.NewMapStringString(controllerManagerExtraArgs), ControllerManagerExtraArgs, "A set of extra flags to pass to the Controller Manager or override default ones in form of <flagname>=<value>")
fs.Var(cliflag.NewMapStringString(schedulerExtraArgs), SchedulerExtraArgs, "A set of extra flags to pass to the Scheduler or override default ones in form of <flagname>=<value>")

View File

@ -26,6 +26,7 @@ import (
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
@ -78,8 +79,8 @@ func runKubeletFinalizeCertRotation(c workflow.RunData) error {
// If yes, use that path, else use the kubeadm provided value.
cfg := data.Cfg()
pkiPath := filepath.Join(data.KubeletDir(), "pki")
val, ok := cfg.NodeRegistration.KubeletExtraArgs["cert-dir"]
if ok {
val, idx := kubeadmapi.GetArgValue(cfg.NodeRegistration.KubeletExtraArgs, "cert-dir", -1)
if idx > -1 {
pkiPath = val
}

View File

@ -162,46 +162,47 @@ func CreateStaticPodFiles(manifestDir, patchesDir string, cfg *kubeadmapi.Cluste
// getAPIServerCommand builds the right API server command from the given config object and version
func getAPIServerCommand(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint *kubeadmapi.APIEndpoint) []string {
defaultArguments := map[string]string{
"advertise-address": localAPIEndpoint.AdvertiseAddress,
"enable-admission-plugins": "NodeRestriction",
"service-cluster-ip-range": cfg.Networking.ServiceSubnet,
"service-account-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
"service-account-signing-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName),
"service-account-issuer": fmt.Sprintf("https://kubernetes.default.svc.%s", cfg.Networking.DNSDomain),
"client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
"tls-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
"tls-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
"kubelet-client-certificate": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
"kubelet-client-key": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
"enable-bootstrap-token-auth": "true",
"secure-port": fmt.Sprintf("%d", localAPIEndpoint.BindPort),
"allow-privileged": "true",
"kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
defaultArguments := []kubeadmapi.Arg{
{Name: "advertise-address", Value: localAPIEndpoint.AdvertiseAddress},
{Name: "enable-admission-plugins", Value: "NodeRestriction"},
{Name: "service-cluster-ip-range", Value: cfg.Networking.ServiceSubnet},
{Name: "service-account-key-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName)},
{Name: "service-account-signing-key-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName)},
{Name: "service-account-issuer", Value: fmt.Sprintf("https://kubernetes.default.svc.%s", cfg.Networking.DNSDomain)},
{Name: "client-ca-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName)},
{Name: "tls-cert-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName)},
{Name: "tls-private-key-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName)},
{Name: "kubelet-client-certificate", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName)},
{Name: "kubelet-client-key", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName)},
{Name: "enable-bootstrap-token-auth", Value: "true"},
{Name: "secure-port", Value: fmt.Sprintf("%d", localAPIEndpoint.BindPort)},
{Name: "allow-privileged", Value: "true"},
{Name: "kubelet-preferred-address-types", Value: "InternalIP,ExternalIP,Hostname"},
// add options to configure the front proxy. Without the generated client cert, this will never be useable
// so add it unconditionally with recommended values
"requestheader-username-headers": "X-Remote-User",
"requestheader-group-headers": "X-Remote-Group",
"requestheader-extra-headers-prefix": "X-Remote-Extra-",
"requestheader-client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName),
"requestheader-allowed-names": "front-proxy-client",
"proxy-client-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientCertName),
"proxy-client-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientKeyName),
{Name: "requestheader-username-headers", Value: "X-Remote-User"},
{Name: "requestheader-group-headers", Value: "X-Remote-Group"},
{Name: "requestheader-extra-headers-prefix", Value: "X-Remote-Extra-"},
{Name: "requestheader-client-ca-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName)},
{Name: "requestheader-allowed-names", Value: "front-proxy-client"},
{Name: "proxy-client-cert-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientCertName)},
{Name: "proxy-client-key-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientKeyName)},
}
command := []string{"kube-apiserver"}
// If the user set endpoints for an external etcd cluster
if cfg.Etcd.External != nil {
defaultArguments["etcd-servers"] = strings.Join(cfg.Etcd.External.Endpoints, ",")
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "etcd-servers", strings.Join(cfg.Etcd.External.Endpoints, ","), 1)
// Use any user supplied etcd certificates
if cfg.Etcd.External.CAFile != "" {
defaultArguments["etcd-cafile"] = cfg.Etcd.External.CAFile
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "etcd-cafile", cfg.Etcd.External.CAFile, 1)
}
if cfg.Etcd.External.CertFile != "" && cfg.Etcd.External.KeyFile != "" {
defaultArguments["etcd-certfile"] = cfg.Etcd.External.CertFile
defaultArguments["etcd-keyfile"] = cfg.Etcd.External.KeyFile
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "etcd-certfile", cfg.Etcd.External.CertFile, 1)
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "etcd-keyfile", cfg.Etcd.External.KeyFile, 1)
}
} else {
// Default to etcd static pod on localhost
@ -210,24 +211,25 @@ func getAPIServerCommand(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint
if utilsnet.IsIPv6String(localAPIEndpoint.AdvertiseAddress) {
etcdLocalhostAddress = "::1"
}
defaultArguments["etcd-servers"] = fmt.Sprintf("https://%s", net.JoinHostPort(etcdLocalhostAddress, strconv.Itoa(kubeadmconstants.EtcdListenClientPort)))
defaultArguments["etcd-cafile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName)
defaultArguments["etcd-certfile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientCertName)
defaultArguments["etcd-keyfile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientKeyName)
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "etcd-servers", fmt.Sprintf("https://%s", net.JoinHostPort(etcdLocalhostAddress, strconv.Itoa(kubeadmconstants.EtcdListenClientPort))), 1)
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "etcd-cafile", filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName), 1)
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "etcd-certfile", filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientCertName), 1)
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "etcd-keyfile", filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientKeyName), 1)
// Apply user configurations for local etcd
if cfg.Etcd.Local != nil {
if value, ok := cfg.Etcd.Local.ExtraArgs["advertise-client-urls"]; ok {
defaultArguments["etcd-servers"] = value
if value, idx := kubeadmapi.GetArgValue(cfg.Etcd.Local.ExtraArgs, "advertise-client-urls", -1); idx > -1 {
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "etcd-servers", value, 1)
}
}
}
if cfg.APIServer.ExtraArgs == nil {
cfg.APIServer.ExtraArgs = map[string]string{}
cfg.APIServer.ExtraArgs = []kubeadmapi.Arg{}
}
cfg.APIServer.ExtraArgs["authorization-mode"] = getAuthzModes(cfg.APIServer.ExtraArgs["authorization-mode"])
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.APIServer.ExtraArgs)...)
authzVal, _ := kubeadmapi.GetArgValue(cfg.APIServer.ExtraArgs, "authorization-mode", -1)
cfg.APIServer.ExtraArgs = kubeadmapi.SetArgValues(cfg.APIServer.ExtraArgs, "authorization-mode", getAuthzModes(authzVal), 1)
command = append(command, kubeadmutil.ArgumentsToCommand(defaultArguments, cfg.APIServer.ExtraArgs)...)
return command
}
@ -302,46 +304,46 @@ func getControllerManagerCommand(cfg *kubeadmapi.ClusterConfiguration) []string
kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName)
caFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName)
defaultArguments := map[string]string{
"bind-address": "127.0.0.1",
"leader-elect": "true",
"kubeconfig": kubeconfigFile,
"authentication-kubeconfig": kubeconfigFile,
"authorization-kubeconfig": kubeconfigFile,
"client-ca-file": caFile,
"requestheader-client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName),
"root-ca-file": caFile,
"service-account-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName),
"cluster-signing-cert-file": caFile,
"cluster-signing-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName),
"use-service-account-credentials": "true",
"controllers": "*,bootstrapsigner,tokencleaner",
defaultArguments := []kubeadmapi.Arg{
{Name: "bind-address", Value: "127.0.0.1"},
{Name: "leader-elect", Value: "true"},
{Name: "kubeconfig", Value: kubeconfigFile},
{Name: "authentication-kubeconfig", Value: kubeconfigFile},
{Name: "authorization-kubeconfig", Value: kubeconfigFile},
{Name: "client-ca-file", Value: caFile},
{Name: "requestheader-client-ca-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName)},
{Name: "root-ca-file", Value: caFile},
{Name: "service-account-private-key-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName)},
{Name: "cluster-signing-cert-file", Value: caFile},
{Name: "cluster-signing-key-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName)},
{Name: "use-service-account-credentials", Value: "true"},
{Name: "controllers", Value: "*,bootstrapsigner,tokencleaner"},
}
// If using external CA, pass empty string to controller manager instead of ca.key/ca.crt path,
// so that the csrsigning controller fails to start
if res, _ := certphase.UsingExternalCA(cfg); res {
defaultArguments["cluster-signing-key-file"] = ""
defaultArguments["cluster-signing-cert-file"] = ""
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "cluster-signing-key-file", "", 1)
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "cluster-signing-cert-file", "", 1)
}
// Let the controller-manager allocate Node CIDRs for the Pod network.
// Each node will get a subspace of the address CIDR provided with --pod-network-cidr.
if cfg.Networking.PodSubnet != "" {
defaultArguments["allocate-node-cidrs"] = "true"
defaultArguments["cluster-cidr"] = cfg.Networking.PodSubnet
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "allocate-node-cidrs", "true", 1)
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "cluster-cidr", cfg.Networking.PodSubnet, 1)
if cfg.Networking.ServiceSubnet != "" {
defaultArguments["service-cluster-ip-range"] = cfg.Networking.ServiceSubnet
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "service-cluster-ip-range", cfg.Networking.ServiceSubnet, 1)
}
}
// Set cluster name
if cfg.ClusterName != "" {
defaultArguments["cluster-name"] = cfg.ClusterName
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "cluster-name", cfg.ClusterName, 1)
}
command := []string{"kube-controller-manager"}
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.ControllerManager.ExtraArgs)...)
command = append(command, kubeadmutil.ArgumentsToCommand(defaultArguments, cfg.ControllerManager.ExtraArgs)...)
return command
}
@ -349,15 +351,15 @@ func getControllerManagerCommand(cfg *kubeadmapi.ClusterConfiguration) []string
// getSchedulerCommand builds the right scheduler command from the given config object and version
func getSchedulerCommand(cfg *kubeadmapi.ClusterConfiguration) []string {
kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName)
defaultArguments := map[string]string{
"bind-address": "127.0.0.1",
"leader-elect": "true",
"kubeconfig": kubeconfigFile,
"authentication-kubeconfig": kubeconfigFile,
"authorization-kubeconfig": kubeconfigFile,
defaultArguments := []kubeadmapi.Arg{
{Name: "bind-address", Value: "127.0.0.1"},
{Name: "leader-elect", Value: "true"},
{Name: "kubeconfig", Value: kubeconfigFile},
{Name: "authentication-kubeconfig", Value: kubeconfigFile},
{Name: "authorization-kubeconfig", Value: kubeconfigFile},
}
command := []string{"kube-scheduler"}
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.Scheduler.ExtraArgs)...)
command = append(command, kubeadmutil.ArgumentsToCommand(defaultArguments, cfg.Scheduler.ExtraArgs)...)
return command
}

View File

@ -375,11 +375,11 @@ func TestGetAPIServerCommand(t *testing.T) {
CertificatesDir: testCertsDir,
APIServer: kubeadmapi.APIServer{
ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
"service-cluster-ip-range": "baz",
"advertise-address": "9.9.9.9",
"audit-policy-file": "/etc/config/audit.yaml",
"audit-log-path": "/var/log/kubernetes",
ExtraArgs: []kubeadmapi.Arg{
{Name: "service-cluster-ip-range", Value: "baz"},
{Name: "advertise-address", Value: "9.9.9.9"},
{Name: "audit-policy-file", Value: "/etc/config/audit.yaml"},
{Name: "audit-log-path", Value: "/var/log/kubernetes"},
},
},
},
@ -425,8 +425,8 @@ func TestGetAPIServerCommand(t *testing.T) {
CertificatesDir: testCertsDir,
APIServer: kubeadmapi.APIServer{
ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
"authorization-mode": kubeadmconstants.ModeABAC,
ExtraArgs: []kubeadmapi.Arg{
{Name: "authorization-mode", Value: kubeadmconstants.ModeABAC},
},
},
},
@ -470,12 +470,12 @@ func TestGetAPIServerCommand(t *testing.T) {
CertificatesDir: testCertsDir,
APIServer: kubeadmapi.APIServer{
ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
"authorization-mode": strings.Join([]string{
ExtraArgs: []kubeadmapi.Arg{
{Name: "authorization-mode", Value: strings.Join([]string{
kubeadmconstants.ModeNode,
kubeadmconstants.ModeRBAC,
kubeadmconstants.ModeWebhook,
}, ","),
}, ",")},
},
},
},
@ -660,7 +660,7 @@ func TestGetControllerManagerCommand(t *testing.T) {
cfg: &kubeadmapi.ClusterConfiguration{
Networking: kubeadmapi.Networking{PodSubnet: "10.0.1.15/16", DNSDomain: "cluster.local"},
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"node-cidr-mask-size": "20"},
ExtraArgs: []kubeadmapi.Arg{{Name: "node-cidr-mask-size", Value: "20"}},
},
CertificatesDir: testCertsDir,
KubernetesVersion: cpVersion,
@ -726,7 +726,7 @@ func TestGetControllerManagerCommand(t *testing.T) {
DNSDomain: "cluster.local",
},
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"allocate-node-cidrs": "false"},
ExtraArgs: []kubeadmapi.Arg{{Name: "allocate-node-cidrs", Value: "false"}},
},
CertificatesDir: testCertsDir,
KubernetesVersion: cpVersion,
@ -790,7 +790,10 @@ func TestGetControllerManagerCommand(t *testing.T) {
DNSDomain: "cluster.local",
},
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"node-cidr-mask-size-ipv4": "20", "node-cidr-mask-size-ipv6": "80"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "node-cidr-mask-size-ipv4", Value: "20"},
{Name: "node-cidr-mask-size-ipv6", Value: "80"},
},
},
CertificatesDir: testCertsDir,
KubernetesVersion: cpVersion,

View File

@ -72,8 +72,8 @@ func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.ClusterConfiguration)
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeConfigVolumeName, controllerManagerKubeConfigFile, controllerManagerKubeConfigFile, true, &hostPathFileOrCreate)
// Mount for the flexvolume directory (/usr/libexec/kubernetes/kubelet-plugins/volume/exec by default)
// Flexvolume dir must NOT be readonly as it is used for third-party plugins to integrate with their storage backends via unix domain socket.
flexvolumeDirVolumePath, ok := cfg.ControllerManager.ExtraArgs["flex-volume-plugin-dir"]
if !ok {
flexvolumeDirVolumePath, idx := kubeadmapi.GetArgValue(cfg.ControllerManager.ExtraArgs, "flex-volume-plugin-dir", -1)
if idx == -1 {
flexvolumeDirVolumePath = defaultFlexvolumeDirVolumePath
}
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, flexvolumeDirVolumeName, flexvolumeDirVolumePath, flexvolumeDirVolumePath, false, &hostPathDirectoryOrCreate)

View File

@ -239,31 +239,31 @@ func getEtcdCommand(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.A
if utilsnet.IsIPv6String(endpoint.AdvertiseAddress) {
etcdLocalhostAddress = "::1"
}
defaultArguments := map[string]string{
"name": nodeName,
// TODO: start using --initial-corrupt-check once the graduated flag is available:
defaultArguments := []kubeadmapi.Arg{
{Name: "name", Value: nodeName},
// TODO: start using --initial-corrupt-check once the graduated flag is available,
// https://github.com/kubernetes/kubeadm/issues/2676
"experimental-initial-corrupt-check": "true",
"listen-client-urls": fmt.Sprintf("%s,%s", etcdutil.GetClientURLByIP(etcdLocalhostAddress), etcdutil.GetClientURL(endpoint)),
"advertise-client-urls": etcdutil.GetClientURL(endpoint),
"listen-peer-urls": etcdutil.GetPeerURL(endpoint),
"initial-advertise-peer-urls": etcdutil.GetPeerURL(endpoint),
"data-dir": cfg.Etcd.Local.DataDir,
"cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdServerCertName),
"key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdServerKeyName),
"trusted-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName),
"client-cert-auth": "true",
"peer-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdPeerCertName),
"peer-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdPeerKeyName),
"peer-trusted-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName),
"peer-client-cert-auth": "true",
"snapshot-count": "10000",
"listen-metrics-urls": fmt.Sprintf("http://%s", net.JoinHostPort(etcdLocalhostAddress, strconv.Itoa(kubeadmconstants.EtcdMetricsPort))),
"experimental-watch-progress-notify-interval": "5s",
{Name: "experimental-initial-corrupt-check", Value: "true"},
{Name: "listen-client-urls", Value: fmt.Sprintf("%s,%s", etcdutil.GetClientURLByIP(etcdLocalhostAddress), etcdutil.GetClientURL(endpoint))},
{Name: "advertise-client-urls", Value: etcdutil.GetClientURL(endpoint)},
{Name: "listen-peer-urls", Value: etcdutil.GetPeerURL(endpoint)},
{Name: "initial-advertise-peer-urls", Value: etcdutil.GetPeerURL(endpoint)},
{Name: "data-dir", Value: cfg.Etcd.Local.DataDir},
{Name: "cert-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdServerCertName)},
{Name: "key-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdServerKeyName)},
{Name: "trusted-ca-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName)},
{Name: "client-cert-auth", Value: "true"},
{Name: "peer-cert-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdPeerCertName)},
{Name: "peer-key-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdPeerKeyName)},
{Name: "peer-trusted-ca-file", Value: filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName)},
{Name: "peer-client-cert-auth", Value: "true"},
{Name: "snapshot-count", Value: "10000"},
{Name: "listen-metrics-urls", Value: fmt.Sprintf("http://%s", net.JoinHostPort(etcdLocalhostAddress, strconv.Itoa(kubeadmconstants.EtcdMetricsPort)))},
{Name: "experimental-watch-progress-notify-interval", Value: "5s"},
}
if len(initialCluster) == 0 {
defaultArguments["initial-cluster"] = fmt.Sprintf("%s=%s", nodeName, etcdutil.GetPeerURL(endpoint))
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "initial-cluster", fmt.Sprintf("%s=%s", nodeName, etcdutil.GetPeerURL(endpoint)), 1)
} else {
// NB. the joining etcd member should be part of the initialCluster list
endpoints := []string{}
@ -271,12 +271,12 @@ func getEtcdCommand(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.A
endpoints = append(endpoints, fmt.Sprintf("%s=%s", member.Name, member.PeerURL))
}
defaultArguments["initial-cluster"] = strings.Join(endpoints, ",")
defaultArguments["initial-cluster-state"] = "existing"
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "initial-cluster", strings.Join(endpoints, ","), 1)
defaultArguments = kubeadmapi.SetArgValues(defaultArguments, "initial-cluster-state", "existing", 1)
}
command := []string{"etcd"}
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.Etcd.Local.ExtraArgs)...)
command = append(command, kubeadmutil.ArgumentsToCommand(defaultArguments, cfg.Etcd.Local.ExtraArgs)...)
return command
}

View File

@ -176,7 +176,7 @@ func TestGetEtcdCommand(t *testing.T) {
name string
advertiseAddress string
nodeName string
extraArgs map[string]string
extraArgs []kubeadmapi.Arg
initialCluster []etcdutil.Member
expected []string
}{
@ -243,9 +243,9 @@ func TestGetEtcdCommand(t *testing.T) {
name: "Extra args",
advertiseAddress: "1.2.3.4",
nodeName: "bar",
extraArgs: map[string]string{
"listen-client-urls": "https://10.0.1.10:2379",
"advertise-client-urls": "https://10.0.1.10:2379",
extraArgs: []kubeadmapi.Arg{
{Name: "listen-client-urls", Value: "https://10.0.1.10:2379"},
{Name: "advertise-client-urls", Value: "https://10.0.1.10:2379"},
},
expected: []string{
"etcd",

View File

@ -51,7 +51,7 @@ func GetNodeNameAndHostname(cfg *kubeadmapi.NodeRegistrationOptions) (string, st
if cfg.Name != "" {
nodeName = cfg.Name
}
if name, ok := cfg.KubeletExtraArgs["hostname-override"]; ok {
if name, idx := kubeadmapi.GetArgValue(cfg.KubeletExtraArgs, "hostname-override", -1); idx > -1 {
nodeName = name
}
return nodeName, hostname, err
@ -65,23 +65,23 @@ func WriteKubeletDynamicEnvFile(cfg *kubeadmapi.ClusterConfiguration, nodeReg *k
pauseImage: images.GetPauseImage(cfg),
registerTaintsUsingFlags: registerTaintsUsingFlags,
}
stringMap := buildKubeletArgMap(flagOpts)
argList := kubeadmutil.BuildArgumentListFromMap(stringMap, nodeReg.KubeletExtraArgs)
stringMap := buildKubeletArgs(flagOpts)
argList := kubeadmutil.ArgumentsToCommand(stringMap, nodeReg.KubeletExtraArgs)
envFileContent := fmt.Sprintf("%s=%q\n", constants.KubeletEnvFileVariableName, strings.Join(argList, " "))
return writeKubeletFlagBytesToDisk([]byte(envFileContent), kubeletDir)
}
// buildKubeletArgMapCommon takes a kubeletFlagsOpts object and builds based on that a string-string map with flags
// buildKubeletArgsCommon takes a kubeletFlagsOpts object and builds based on that a slice of arguments
// that are common to both Linux and Windows
func buildKubeletArgMapCommon(opts kubeletFlagsOpts) map[string]string {
kubeletFlags := map[string]string{}
kubeletFlags["container-runtime-endpoint"] = opts.nodeRegOpts.CRISocket
func buildKubeletArgsCommon(opts kubeletFlagsOpts) []kubeadmapi.Arg {
kubeletFlags := []kubeadmapi.Arg{}
kubeletFlags = append(kubeletFlags, kubeadmapi.Arg{Name: "container-runtime-endpoint", Value: opts.nodeRegOpts.CRISocket})
// This flag passes the pod infra container image (e.g. "pause" image) to the kubelet
// and prevents its garbage collection
if opts.pauseImage != "" {
kubeletFlags["pod-infra-container-image"] = opts.pauseImage
kubeletFlags = append(kubeletFlags, kubeadmapi.Arg{Name: "pod-infra-container-image", Value: opts.pauseImage})
}
if opts.registerTaintsUsingFlags && opts.nodeRegOpts.Taints != nil && len(opts.nodeRegOpts.Taints) > 0 {
@ -89,8 +89,7 @@ func buildKubeletArgMapCommon(opts kubeletFlagsOpts) map[string]string {
for _, taint := range opts.nodeRegOpts.Taints {
taintStrs = append(taintStrs, taint.ToString())
}
kubeletFlags["register-with-taints"] = strings.Join(taintStrs, ",")
kubeletFlags = append(kubeletFlags, kubeadmapi.Arg{Name: "register-with-taints", Value: strings.Join(taintStrs, ",")})
}
// Pass the "--hostname-override" flag to the kubelet only if it's different from the hostname
@ -100,7 +99,7 @@ func buildKubeletArgMapCommon(opts kubeletFlagsOpts) map[string]string {
}
if nodeName != hostname {
klog.V(1).Infof("setting kubelet hostname-override to %q", nodeName)
kubeletFlags["hostname-override"] = nodeName
kubeletFlags = append(kubeletFlags, kubeadmapi.Arg{Name: "hostname-override", Value: nodeName})
}
return kubeletFlags
@ -121,8 +120,8 @@ func writeKubeletFlagBytesToDisk(b []byte, kubeletDir string) error {
return nil
}
// buildKubeletArgMap takes a kubeletFlagsOpts object and builds based on that a string-string map with flags
// buildKubeletArgs takes a kubeletFlagsOpts object and builds based on that a slice of arguments
// that should be given to the local kubelet daemon.
func buildKubeletArgMap(opts kubeletFlagsOpts) map[string]string {
return buildKubeletArgMapCommon(opts)
func buildKubeletArgs(opts kubeletFlagsOpts) []kubeadmapi.Arg {
return buildKubeletArgsCommon(opts)
}

View File

@ -27,23 +27,25 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func TestBuildKubeletArgMap(t *testing.T) {
func TestBuildKubeletArgs(t *testing.T) {
tests := []struct {
name string
opts kubeletFlagsOpts
expected map[string]string
expected []kubeadmapi.Arg
}{
{
name: "hostname override",
opts: kubeletFlagsOpts{
nodeRegOpts: &kubeadmapi.NodeRegistrationOptions{
CRISocket: "unix:///var/run/containerd/containerd.sock",
KubeletExtraArgs: map[string]string{"hostname-override": "override-name"},
CRISocket: "unix:///var/run/containerd/containerd.sock",
KubeletExtraArgs: []kubeadmapi.Arg{
{Name: "hostname-override", Value: "override-name"},
},
},
},
expected: map[string]string{
"container-runtime-endpoint": "unix:///var/run/containerd/containerd.sock",
"hostname-override": "override-name",
expected: []kubeadmapi.Arg{
{Name: "container-runtime-endpoint", Value: "unix:///var/run/containerd/containerd.sock"},
{Name: "hostname-override", Value: "override-name"},
},
},
{
@ -66,9 +68,9 @@ func TestBuildKubeletArgMap(t *testing.T) {
},
registerTaintsUsingFlags: true,
},
expected: map[string]string{
"container-runtime-endpoint": "unix:///var/run/containerd/containerd.sock",
"register-with-taints": "foo=bar:baz,key=val:eff",
expected: []kubeadmapi.Arg{
{Name: "container-runtime-endpoint", Value: "unix:///var/run/containerd/containerd.sock"},
{Name: "register-with-taints", Value: "foo=bar:baz,key=val:eff"},
},
},
{
@ -79,19 +81,19 @@ func TestBuildKubeletArgMap(t *testing.T) {
},
pauseImage: "registry.k8s.io/pause:3.9",
},
expected: map[string]string{
"container-runtime-endpoint": "unix:///var/run/containerd/containerd.sock",
"pod-infra-container-image": "registry.k8s.io/pause:3.9",
expected: []kubeadmapi.Arg{
{Name: "container-runtime-endpoint", Value: "unix:///var/run/containerd/containerd.sock"},
{Name: "pod-infra-container-image", Value: "registry.k8s.io/pause:3.9"},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := buildKubeletArgMap(test.opts)
actual := buildKubeletArgs(test.opts)
if !reflect.DeepEqual(actual, test.expected) {
t.Errorf(
"failed buildKubeletArgMap:\n\texpected: %v\n\t actual: %v",
"failed buildKubeletArgs:\n\texpected: %v\n\t actual: %v",
test.expected,
actual,
)
@ -116,7 +118,9 @@ func TestGetNodeNameAndHostname(t *testing.T) {
name: "overridden hostname",
opts: kubeletFlagsOpts{
nodeRegOpts: &kubeadmapi.NodeRegistrationOptions{
KubeletExtraArgs: map[string]string{"hostname-override": "override-name"},
KubeletExtraArgs: []kubeadmapi.Arg{
{Name: "hostname-override", Value: "override-name"},
},
},
},
expectedNodeName: "override-name",
@ -126,7 +130,9 @@ func TestGetNodeNameAndHostname(t *testing.T) {
name: "overridden hostname uppercase",
opts: kubeletFlagsOpts{
nodeRegOpts: &kubeadmapi.NodeRegistrationOptions{
KubeletExtraArgs: map[string]string{"hostname-override": "OVERRIDE-NAME"},
KubeletExtraArgs: []kubeadmapi.Arg{
{Name: "hostname-override", Value: "OVERRIDE-NAME"},
},
},
},
expectedNodeName: "OVERRIDE-NAME",
@ -136,7 +142,9 @@ func TestGetNodeNameAndHostname(t *testing.T) {
name: "hostname contains only spaces",
opts: kubeletFlagsOpts{
nodeRegOpts: &kubeadmapi.NodeRegistrationOptions{
KubeletExtraArgs: map[string]string{"hostname-override": " "},
KubeletExtraArgs: []kubeadmapi.Arg{
{Name: "hostname-override", Value: " "},
},
},
},
expectedNodeName: " ",
@ -146,7 +154,9 @@ func TestGetNodeNameAndHostname(t *testing.T) {
name: "empty parameter",
opts: kubeletFlagsOpts{
nodeRegOpts: &kubeadmapi.NodeRegistrationOptions{
KubeletExtraArgs: map[string]string{"hostname-override": ""},
KubeletExtraArgs: []kubeadmapi.Arg{
{Name: "hostname-override", Value: ""},
},
},
},
expectedNodeName: "",

View File

@ -24,41 +24,54 @@ import (
"github.com/pkg/errors"
"k8s.io/klog/v2"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
// BuildArgumentListFromMap takes two string-string maps, one with the base arguments and one
// ArgumentsToCommand takes two Arg slices, one with the base arguments and one
// with optional override arguments. In the return list override arguments will precede base
// arguments
func BuildArgumentListFromMap(baseArguments map[string]string, overrideArguments map[string]string) []string {
// arguments. If an argument is present in the overrides, it will cause
// all instances of the same argument in the base list to be discarded, leaving
// only the instances of this argument in the overrides to be applied.
func ArgumentsToCommand(base []kubeadmapi.Arg, overrides []kubeadmapi.Arg) []string {
var command []string
var keys []string
// Copy the base arguments into a new slice.
args := make([]kubeadmapi.Arg, len(base))
copy(args, base)
argsMap := make(map[string]string)
for k, v := range baseArguments {
argsMap[k] = v
// Go trough the override arguments and delete all instances of arguments with the same name
// in the base list of arguments.
for i := 0; i < len(overrides); i++ {
repeat:
for j := 0; j < len(args); j++ {
if overrides[i].Name == args[j].Name {
// Remove this existing argument and search for another argument
// with the same name in base.
args = append(args[:j], args[j+1:]...)
goto repeat
}
}
}
for k, v := range overrideArguments {
argsMap[k] = v
}
// Concatenate the overrides and the base arguments and then sort them.
args = append(args, overrides...)
sort.Slice(args, func(i, j int) bool {
if args[i].Name == args[j].Name {
return args[i].Value < args[j].Value
}
return args[i].Name < args[j].Name
})
for k := range argsMap {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
command = append(command, fmt.Sprintf("--%s=%s", k, argsMap[k]))
for _, arg := range args {
command = append(command, fmt.Sprintf("--%s=%s", arg.Name, arg.Value))
}
return command
}
// ParseArgumentListToMap parses a CLI argument list in the form "--foo=bar" to a string-string map
func ParseArgumentListToMap(arguments []string) map[string]string {
resultingMap := map[string]string{}
for i, arg := range arguments {
// ArgumentsFromCommand parses a CLI command in the form "--foo=bar" to an Arg slice
func ArgumentsFromCommand(command []string) []kubeadmapi.Arg {
args := []kubeadmapi.Arg{}
for i, arg := range command {
key, val, err := parseArgument(arg)
// Ignore if the first argument doesn't satisfy the criteria, it's most often the binary name
@ -70,24 +83,16 @@ func ParseArgumentListToMap(arguments []string) map[string]string {
continue
}
resultingMap[key] = val
args = append(args, kubeadmapi.Arg{Name: key, Value: val})
}
return resultingMap
}
// ReplaceArgument gets a command list; converts it to a map for easier modification, runs the provided function that
// returns a new modified map, and then converts the map back to a command string slice
func ReplaceArgument(command []string, argMutateFunc func(map[string]string) map[string]string) []string {
argMap := ParseArgumentListToMap(command)
// Save the first command (the executable) if we're sure it's not an argument (i.e. no --)
var newCommand []string
if len(command) > 0 && !strings.HasPrefix(command[0], "--") {
newCommand = append(newCommand, command[0])
}
newArgMap := argMutateFunc(argMap)
newCommand = append(newCommand, BuildArgumentListFromMap(newArgMap, map[string]string{})...)
return newCommand
sort.Slice(args, func(i, j int) bool {
if args[i].Name == args[j].Name {
return args[i].Value < args[j].Value
}
return args[i].Name < args[j].Name
})
return args
}
// parseArgument parses the argument "--foo=bar" to "foo" and "bar"

View File

@ -20,23 +20,25 @@ import (
"reflect"
"sort"
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func TestBuildArgumentListFromMap(t *testing.T) {
func TestArgumentsToCommand(t *testing.T) {
var tests = []struct {
name string
base map[string]string
overrides map[string]string
base []kubeadmapi.Arg
overrides []kubeadmapi.Arg
expected []string
}{
{
name: "override an argument from the base",
base: map[string]string{
"admission-control": "NamespaceLifecycle",
"allow-privileged": "true",
base: []kubeadmapi.Arg{
{Name: "admission-control", Value: "NamespaceLifecycle"},
{Name: "allow-privileged", Value: "true"},
},
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
overrides: []kubeadmapi.Arg{
{Name: "admission-control", Value: "NamespaceLifecycle,LimitRanger"},
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
@ -44,12 +46,43 @@ func TestBuildArgumentListFromMap(t *testing.T) {
},
},
{
name: "add an argument that is not in base",
base: map[string]string{
"allow-privileged": "true",
name: "override an argument from the base and add duplicate",
base: []kubeadmapi.Arg{
{Name: "token-auth-file", Value: "/token"},
{Name: "tls-sni-cert-key", Value: "/some/path/"},
},
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
overrides: []kubeadmapi.Arg{
{Name: "tls-sni-cert-key", Value: "/some/new/path"},
{Name: "tls-sni-cert-key", Value: "/some/new/path/subpath"},
},
expected: []string{
"--tls-sni-cert-key=/some/new/path",
"--tls-sni-cert-key=/some/new/path/subpath",
"--token-auth-file=/token",
},
},
{
name: "override all duplicate arguments from base",
base: []kubeadmapi.Arg{
{Name: "token-auth-file", Value: "/token"},
{Name: "tls-sni-cert-key", Value: "foo"},
{Name: "tls-sni-cert-key", Value: "bar"},
},
overrides: []kubeadmapi.Arg{
{Name: "tls-sni-cert-key", Value: "/some/new/path"},
},
expected: []string{
"--tls-sni-cert-key=/some/new/path",
"--token-auth-file=/token",
},
},
{
name: "add an argument that is not in base",
base: []kubeadmapi.Arg{
{Name: "allow-privileged", Value: "true"},
},
overrides: []kubeadmapi.Arg{
{Name: "admission-control", Value: "NamespaceLifecycle,LimitRanger"},
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
@ -58,12 +91,12 @@ func TestBuildArgumentListFromMap(t *testing.T) {
},
{
name: "allow empty strings in base",
base: map[string]string{
"allow-privileged": "true",
"something-that-allows-empty-string": "",
base: []kubeadmapi.Arg{
{Name: "allow-privileged", Value: "true"},
{Name: "something-that-allows-empty-string", Value: ""},
},
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
overrides: []kubeadmapi.Arg{
{Name: "admission-control", Value: "NamespaceLifecycle,LimitRanger"},
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
@ -73,13 +106,13 @@ func TestBuildArgumentListFromMap(t *testing.T) {
},
{
name: "allow empty strings in overrides",
base: map[string]string{
"allow-privileged": "true",
"something-that-allows-empty-string": "foo",
base: []kubeadmapi.Arg{
{Name: "allow-privileged", Value: "true"},
{Name: "something-that-allows-empty-string", Value: "foo"},
},
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
"something-that-allows-empty-string": "",
overrides: []kubeadmapi.Arg{
{Name: "admission-control", Value: "NamespaceLifecycle,LimitRanger"},
{Name: "something-that-allows-empty-string", Value: ""},
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
@ -91,19 +124,19 @@ func TestBuildArgumentListFromMap(t *testing.T) {
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
actual := BuildArgumentListFromMap(rt.base, rt.overrides)
actual := ArgumentsToCommand(rt.base, rt.overrides)
if !reflect.DeepEqual(actual, rt.expected) {
t.Errorf("failed BuildArgumentListFromMap:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
t.Errorf("failed ArgumentsToCommand:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
}
})
}
}
func TestParseArgumentListToMap(t *testing.T) {
func TestArgumentsFromCommand(t *testing.T) {
var tests = []struct {
name string
args []string
expectedMap map[string]string
name string
args []string
expected []kubeadmapi.Arg
}{
{
name: "normal case",
@ -111,9 +144,9 @@ func TestParseArgumentListToMap(t *testing.T) {
"--admission-control=NamespaceLifecycle,LimitRanger",
"--allow-privileged=true",
},
expectedMap: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
"allow-privileged": "true",
expected: []kubeadmapi.Arg{
{Name: "admission-control", Value: "NamespaceLifecycle,LimitRanger"},
{Name: "allow-privileged", Value: "true"},
},
},
{
@ -123,10 +156,10 @@ func TestParseArgumentListToMap(t *testing.T) {
"--allow-privileged=true",
"--feature-gates=EnableFoo=true,EnableBar=false",
},
expectedMap: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
"allow-privileged": "true",
"feature-gates": "EnableFoo=true,EnableBar=false",
expected: []kubeadmapi.Arg{
{Name: "admission-control", Value: "NamespaceLifecycle,LimitRanger"},
{Name: "allow-privileged", Value: "true"},
{Name: "feature-gates", Value: "EnableFoo=true,EnableBar=false"},
},
},
{
@ -137,75 +170,32 @@ func TestParseArgumentListToMap(t *testing.T) {
"--allow-privileged=true",
"--feature-gates=EnableFoo=true,EnableBar=false",
},
expectedMap: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
"allow-privileged": "true",
"feature-gates": "EnableFoo=true,EnableBar=false",
expected: []kubeadmapi.Arg{
{Name: "admission-control", Value: "NamespaceLifecycle,LimitRanger"},
{Name: "allow-privileged", Value: "true"},
{Name: "feature-gates", Value: "EnableFoo=true,EnableBar=false"},
},
},
{
name: "allow duplicate args",
args: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--tls-sni-cert-key=/some/path",
"--tls-sni-cert-key=/some/path/subpath",
},
expected: []kubeadmapi.Arg{
{Name: "admission-control", Value: "NamespaceLifecycle,LimitRanger"},
{Name: "tls-sni-cert-key", Value: "/some/path"},
{Name: "tls-sni-cert-key", Value: "/some/path/subpath"},
},
},
}
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
actualMap := ParseArgumentListToMap(rt.args)
if !reflect.DeepEqual(actualMap, rt.expectedMap) {
t.Errorf("failed ParseArgumentListToMap:\nexpected:\n%v\nsaw:\n%v", rt.expectedMap, actualMap)
}
})
}
}
func TestReplaceArgument(t *testing.T) {
var tests = []struct {
name string
args []string
mutateFunc func(map[string]string) map[string]string
expectedArgs []string
}{
{
name: "normal case",
args: []string{
"kube-apiserver",
"--admission-control=NamespaceLifecycle,LimitRanger",
"--allow-privileged=true",
},
mutateFunc: func(argMap map[string]string) map[string]string {
argMap["admission-control"] = "NamespaceLifecycle,LimitRanger,ResourceQuota"
return argMap
},
expectedArgs: []string{
"kube-apiserver",
"--admission-control=NamespaceLifecycle,LimitRanger,ResourceQuota",
"--allow-privileged=true",
},
},
{
name: "another normal case",
args: []string{
"kube-apiserver",
"--admission-control=NamespaceLifecycle,LimitRanger",
"--allow-privileged=true",
},
mutateFunc: func(argMap map[string]string) map[string]string {
argMap["new-arg-here"] = "foo"
return argMap
},
expectedArgs: []string{
"kube-apiserver",
"--admission-control=NamespaceLifecycle,LimitRanger",
"--allow-privileged=true",
"--new-arg-here=foo",
},
},
}
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
actualArgs := ReplaceArgument(rt.args, rt.mutateFunc)
sort.Strings(actualArgs)
sort.Strings(rt.expectedArgs)
if !reflect.DeepEqual(actualArgs, rt.expectedArgs) {
t.Errorf("failed ReplaceArgument:\nexpected:\n%v\nsaw:\n%v", rt.expectedArgs, actualArgs)
actual := ArgumentsFromCommand(rt.args)
if !reflect.DeepEqual(actual, rt.expected) {
t.Errorf("failed ArgumentsFromCommand:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
}
})
}
@ -236,7 +226,7 @@ func TestRoundtrip(t *testing.T) {
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
// These two methods should be each other's opposite functions, test that by chaining the methods and see if you get the same result back
actual := BuildArgumentListFromMap(ParseArgumentListToMap(rt.args), map[string]string{})
actual := ArgumentsToCommand(ArgumentsFromCommand(rt.args), []kubeadmapi.Arg{})
sort.Strings(actual)
sort.Strings(rt.args)

View File

@ -295,7 +295,7 @@ func GetAPIServerProbeAddress(endpoint *kubeadmapi.APIEndpoint) string {
// GetControllerManagerProbeAddress returns the kubernetes controller manager probe address
func GetControllerManagerProbeAddress(cfg *kubeadmapi.ClusterConfiguration) string {
if addr, exists := cfg.ControllerManager.ExtraArgs[kubeControllerManagerBindAddressArg]; exists {
if addr, idx := kubeadmapi.GetArgValue(cfg.ControllerManager.ExtraArgs, kubeControllerManagerBindAddressArg, -1); idx > -1 {
return getProbeAddress(addr)
}
return "127.0.0.1"
@ -303,7 +303,7 @@ func GetControllerManagerProbeAddress(cfg *kubeadmapi.ClusterConfiguration) stri
// GetSchedulerProbeAddress returns the kubernetes scheduler probe address
func GetSchedulerProbeAddress(cfg *kubeadmapi.ClusterConfiguration) string {
if addr, exists := cfg.Scheduler.ExtraArgs[kubeSchedulerBindAddressArg]; exists {
if addr, idx := kubeadmapi.GetArgValue(cfg.Scheduler.ExtraArgs, kubeSchedulerBindAddressArg, -1); idx > -1 {
return getProbeAddress(addr)
}
return "127.0.0.1"
@ -320,7 +320,7 @@ func GetEtcdProbeEndpoint(cfg *kubeadmapi.Etcd, isIPv6 bool) (string, int32, v1.
if cfg.Local == nil || cfg.Local.ExtraArgs == nil {
return localhost, kubeadmconstants.EtcdMetricsPort, v1.URISchemeHTTP
}
if arg, exists := cfg.Local.ExtraArgs["listen-metrics-urls"]; exists {
if arg, idx := kubeadmapi.GetArgValue(cfg.Local.ExtraArgs, "listen-metrics-urls", -1); idx > -1 {
// Use the first url in the listen-metrics-urls if multiple URL's are specified.
arg = strings.Split(arg, ",")[0]
parsedURL, err := url.Parse(arg)

View File

@ -107,7 +107,7 @@ func TestGetControllerManagerProbeAddress(t *testing.T) {
desc: "no controller manager extra args leads to 127.0.0.1 being used",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{},
ExtraArgs: []kubeadmapi.Arg{},
},
},
expected: "127.0.0.1",
@ -116,8 +116,8 @@ func TestGetControllerManagerProbeAddress(t *testing.T) {
desc: "setting controller manager extra address arg to something acknowledges it",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
kubeControllerManagerBindAddressArg: "10.10.10.10",
ExtraArgs: []kubeadmapi.Arg{
{Name: kubeControllerManagerBindAddressArg, Value: "10.10.10.10"},
},
},
},
@ -127,8 +127,8 @@ func TestGetControllerManagerProbeAddress(t *testing.T) {
desc: "setting controller manager extra ipv6 address arg to something acknowledges it",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
kubeControllerManagerBindAddressArg: "2001:abcd:bcda::1",
ExtraArgs: []kubeadmapi.Arg{
{Name: kubeControllerManagerBindAddressArg, Value: "2001:abcd:bcda::1"},
},
},
},
@ -138,8 +138,8 @@ func TestGetControllerManagerProbeAddress(t *testing.T) {
desc: "setting controller manager extra address arg to 0.0.0.0 returns empty",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
kubeControllerManagerBindAddressArg: "0.0.0.0",
ExtraArgs: []kubeadmapi.Arg{
{Name: kubeControllerManagerBindAddressArg, Value: "0.0.0.0"},
},
},
},
@ -149,8 +149,8 @@ func TestGetControllerManagerProbeAddress(t *testing.T) {
desc: "setting controller manager extra ipv6 address arg to :: returns empty",
cfg: &kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
kubeControllerManagerBindAddressArg: "::",
ExtraArgs: []kubeadmapi.Arg{
{Name: kubeControllerManagerBindAddressArg, Value: "::"},
},
},
},
@ -178,7 +178,7 @@ func TestGetSchedulerProbeAddress(t *testing.T) {
desc: "no scheduler extra args leads to 127.0.0.1 being used",
cfg: &kubeadmapi.ClusterConfiguration{
Scheduler: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{},
ExtraArgs: []kubeadmapi.Arg{},
},
},
expected: "127.0.0.1",
@ -187,8 +187,8 @@ func TestGetSchedulerProbeAddress(t *testing.T) {
desc: "setting scheduler extra address arg to something acknowledges it",
cfg: &kubeadmapi.ClusterConfiguration{
Scheduler: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
kubeSchedulerBindAddressArg: "10.10.10.10",
ExtraArgs: []kubeadmapi.Arg{
{Name: kubeSchedulerBindAddressArg, Value: "10.10.10.10"},
},
},
},
@ -198,8 +198,8 @@ func TestGetSchedulerProbeAddress(t *testing.T) {
desc: "setting scheduler extra ipv6 address arg to something acknowledges it",
cfg: &kubeadmapi.ClusterConfiguration{
Scheduler: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
kubeSchedulerBindAddressArg: "2001:abcd:bcda::1",
ExtraArgs: []kubeadmapi.Arg{
{Name: kubeSchedulerBindAddressArg, Value: "2001:abcd:bcda::1"},
},
},
},
@ -209,8 +209,8 @@ func TestGetSchedulerProbeAddress(t *testing.T) {
desc: "setting scheduler extra ipv6 address arg to 0.0.0.0 returns empty",
cfg: &kubeadmapi.ClusterConfiguration{
Scheduler: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
kubeSchedulerBindAddressArg: "0.0.0.0",
ExtraArgs: []kubeadmapi.Arg{
{Name: kubeSchedulerBindAddressArg, Value: "0.0.0.0"},
},
},
},
@ -220,8 +220,8 @@ func TestGetSchedulerProbeAddress(t *testing.T) {
desc: "setting scheduler extra ipv6 address arg to :: returns empty",
cfg: &kubeadmapi.ClusterConfiguration{
Scheduler: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
kubeSchedulerBindAddressArg: "::",
ExtraArgs: []kubeadmapi.Arg{
{Name: kubeSchedulerBindAddressArg, Value: "::"},
},
},
},
@ -251,8 +251,9 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
name: "etcd probe URL from two URLs",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-metrics-urls": "https://1.2.3.4:1234,https://4.3.2.1:2381"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "listen-metrics-urls", Value: "https://1.2.3.4:1234,https://4.3.2.1:2381"},
},
},
},
isIPv6: false,
@ -264,8 +265,9 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
name: "etcd probe URL with HTTP scheme",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-metrics-urls": "http://1.2.3.4:1234"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "listen-metrics-urls", Value: "http://1.2.3.4:1234"},
},
},
},
isIPv6: false,
@ -277,8 +279,9 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
name: "etcd probe URL without scheme should result in defaults",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-metrics-urls": "1.2.3.4"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "listen-metrics-urls", Value: "1.2.3.4"},
},
},
},
isIPv6: false,
@ -290,8 +293,9 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
name: "etcd probe URL without port",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-metrics-urls": "https://1.2.3.4"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "listen-metrics-urls", Value: "https://1.2.3.4"},
},
},
},
isIPv6: false,
@ -303,8 +307,9 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
name: "etcd probe URL from two IPv6 URLs",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-metrics-urls": "https://[2001:abcd:bcda::1]:1234,https://[2001:abcd:bcda::2]:2381"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "listen-metrics-urls", Value: "https://[2001:abcd:bcda::1]:1234,https://[2001:abcd:bcda::2]:2381"},
},
},
},
isIPv6: true,
@ -316,8 +321,9 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
name: "etcd probe localhost IPv6 URL with HTTP scheme",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-metrics-urls": "http://[::1]:1234"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "listen-metrics-urls", Value: "http://[::1]:1234"},
},
},
},
isIPv6: true,
@ -329,8 +335,9 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
name: "etcd probe IPv6 URL with HTTP scheme",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-metrics-urls": "http://[2001:abcd:bcda::1]:1234"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "listen-metrics-urls", Value: "http://[2001:abcd:bcda::1]:1234"},
},
},
},
isIPv6: true,
@ -342,8 +349,9 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
name: "etcd probe IPv6 URL without port",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-metrics-urls": "https://[2001:abcd:bcda::1]"},
ExtraArgs: []kubeadmapi.Arg{
{Name: "listen-metrics-urls", Value: "https://[2001:abcd:bcda::1]"},
},
},
},
isIPv6: true,