diff --git a/cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go b/cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go index 77a3487f70b..db72b541e83 100644 --- a/cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go +++ b/cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go @@ -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 } diff --git a/cmd/kubeadm/app/phases/controlplane/manifests.go b/cmd/kubeadm/app/phases/controlplane/manifests.go index 1c57110171f..691b104c0b6 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests.go @@ -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 } diff --git a/cmd/kubeadm/app/phases/controlplane/manifests_test.go b/cmd/kubeadm/app/phases/controlplane/manifests_test.go index 5ff2c1db285..78b09014d11 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests_test.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests_test.go @@ -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, diff --git a/cmd/kubeadm/app/phases/controlplane/volumes.go b/cmd/kubeadm/app/phases/controlplane/volumes.go index d193b4e0186..9aec46ce4df 100644 --- a/cmd/kubeadm/app/phases/controlplane/volumes.go +++ b/cmd/kubeadm/app/phases/controlplane/volumes.go @@ -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) diff --git a/cmd/kubeadm/app/phases/etcd/local.go b/cmd/kubeadm/app/phases/etcd/local.go index 129db183cc9..e58c6eb9ce7 100644 --- a/cmd/kubeadm/app/phases/etcd/local.go +++ b/cmd/kubeadm/app/phases/etcd/local.go @@ -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 } diff --git a/cmd/kubeadm/app/phases/etcd/local_test.go b/cmd/kubeadm/app/phases/etcd/local_test.go index 5fed54e5dc8..4aa181a51cc 100644 --- a/cmd/kubeadm/app/phases/etcd/local_test.go +++ b/cmd/kubeadm/app/phases/etcd/local_test.go @@ -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", diff --git a/cmd/kubeadm/app/phases/kubelet/flags.go b/cmd/kubeadm/app/phases/kubelet/flags.go index 083fbfc0d2a..79e2dc564fb 100644 --- a/cmd/kubeadm/app/phases/kubelet/flags.go +++ b/cmd/kubeadm/app/phases/kubelet/flags.go @@ -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) } diff --git a/cmd/kubeadm/app/phases/kubelet/flags_test.go b/cmd/kubeadm/app/phases/kubelet/flags_test.go index 6c0d8116a3a..b4881379574 100644 --- a/cmd/kubeadm/app/phases/kubelet/flags_test.go +++ b/cmd/kubeadm/app/phases/kubelet/flags_test.go @@ -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: "", diff --git a/cmd/kubeadm/app/util/staticpod/utils.go b/cmd/kubeadm/app/util/staticpod/utils.go index 4f74e7e84ed..1b909587fc6 100644 --- a/cmd/kubeadm/app/util/staticpod/utils.go +++ b/cmd/kubeadm/app/util/staticpod/utils.go @@ -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, int, v1.UR 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) diff --git a/cmd/kubeadm/app/util/staticpod/utils_test.go b/cmd/kubeadm/app/util/staticpod/utils_test.go index 8f7b2461cdc..9a8c8fb41d0 100644 --- a/cmd/kubeadm/app/util/staticpod/utils_test.go +++ b/cmd/kubeadm/app/util/staticpod/utils_test.go @@ -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,