From 7c42e01ca4caf3df9b96cd53e075f5be506b7122 Mon Sep 17 00:00:00 2001 From: Vince Prignano Date: Tue, 25 Jun 2024 21:26:02 -0700 Subject: [PATCH] Allow CAPBK to generate JoinConfiguration discovery kubeconfig Signed-off-by: Vince Prignano --- Makefile | 3 +- .../kubeadm/api/v1beta1/kubeadm_types.go | 125 ++++++++++++++ .../api/v1beta1/zz_generated.deepcopy.go | 135 ++++++++++++++- ...strap.cluster.x-k8s.io_kubeadmconfigs.yaml | 148 +++++++++++++++++ ...uster.x-k8s.io_kubeadmconfigtemplates.yaml | 152 +++++++++++++++++ .../controllers/kubeadmconfig_controller.go | 109 +++++++++++- .../kubeadmconfig_controller_test.go | 145 +++++++++++++++- .../types/upstreamv1beta1/conversion.go | 5 + .../types/upstreamv1beta1/conversion_test.go | 5 + .../zz_generated.conversion.go | 40 +++-- .../upstreamv1beta1/zz_generated.deepcopy.go | 2 +- .../types/upstreamv1beta2/conversion.go | 5 + .../types/upstreamv1beta2/conversion_test.go | 5 + .../zz_generated.conversion.go | 36 ++-- .../types/upstreamv1beta3/conversion.go | 5 + .../types/upstreamv1beta3/conversion_test.go | 10 ++ .../zz_generated.conversion.go | 36 ++-- .../types/upstreamv1beta4/conversion.go | 5 + .../types/upstreamv1beta4/conversion_test.go | 10 ++ .../zz_generated.conversion.go | 36 ++-- ...cluster.x-k8s.io_kubeadmcontrolplanes.yaml | 151 +++++++++++++++++ ...x-k8s.io_kubeadmcontrolplanetemplates.yaml | 155 ++++++++++++++++++ .../bootstrap/kubeadm/v1alpha3/conversion.go | 8 + .../kubeadm/v1alpha3/conversion_test.go | 10 ++ .../bootstrap/kubeadm/v1alpha4/conversion.go | 13 ++ .../kubeadm/v1alpha4/conversion_test.go | 10 ++ .../v1alpha4/zz_generated.conversion.go | 36 ++-- .../kubeadm/v1alpha3/conversion.go | 4 + .../kubeadm/v1alpha3/conversion_test.go | 10 ++ .../kubeadm/v1alpha4/conversion.go | 8 + .../kubeadm/v1alpha4/conversion_test.go | 10 ++ 31 files changed, 1365 insertions(+), 67 deletions(-) diff --git a/Makefile b/Makefile index dcd3239410be..8725a8d06c82 100644 --- a/Makefile +++ b/Makefile @@ -492,10 +492,11 @@ generate-go-conversions-kubeadm-bootstrap: $(CONVERSION_GEN) ## Generate convers --go-header-file=./hack/boilerplate/boilerplate.generatego.txt \ ./internal/apis/bootstrap/kubeadm/v1alpha3 \ ./internal/apis/bootstrap/kubeadm/v1alpha4 - $(MAKE) clean-generated-conversions SRC_DIRS="./bootstrap/kubeadm/types/upstreamv1beta2,./bootstrap/kubeadm/types/upstreamv1beta3,./bootstrap/kubeadm/types/upstreamv1beta4" + $(MAKE) clean-generated-conversions SRC_DIRS="./bootstrap/kubeadm/types/upstreamv1beta1,./bootstrap/kubeadm/types/upstreamv1beta2,./bootstrap/kubeadm/types/upstreamv1beta3,./bootstrap/kubeadm/types/upstreamv1beta4" $(CONVERSION_GEN) \ --output-file=zz_generated.conversion.go \ --go-header-file=./hack/boilerplate/boilerplate.generatego.txt \ + ./bootstrap/kubeadm/types/upstreamv1beta1 \ ./bootstrap/kubeadm/types/upstreamv1beta2 \ ./bootstrap/kubeadm/types/upstreamv1beta3 \ ./bootstrap/kubeadm/types/upstreamv1beta4 diff --git a/bootstrap/kubeadm/api/v1beta1/kubeadm_types.go b/bootstrap/kubeadm/api/v1beta1/kubeadm_types.go index 67b4f51cd764..b65c365cb9e6 100644 --- a/bootstrap/kubeadm/api/v1beta1/kubeadm_types.go +++ b/bootstrap/kubeadm/api/v1beta1/kubeadm_types.go @@ -512,6 +512,131 @@ type BootstrapTokenDiscovery struct { type FileDiscovery struct { // KubeConfigPath is used to specify the actual file path or URL to the kubeconfig file from which to load cluster information KubeConfigPath string `json:"kubeConfigPath"` + + // KubeConfig is used (optionally) to generate a KubeConfig based on the KubeadmConfig's information. + // The file is generated at the path specified in KubeConfigPath. + // + // Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + // Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + // + // +optional + KubeConfig *FileDiscoveryKubeConfig `json:"kubeConfig,omitempty"` +} + +// FileDiscoveryKubeConfig contains elements describing how to generate the kubeconfig for bootstrapping. +type FileDiscoveryKubeConfig struct { + // Cluster contains information about how to communicate with the kubernetes cluster. + // + // By default the following fields are automatically populated: + // - Server with the Cluster's ControlPlaneEndpoint. + // - CertificateAuthorityData with the Cluster's CA certificate. + // +optional + Cluster *KubeConfigCluster `json:"cluster,omitempty"` + + // User contains information that describes identity information. + // This is used to tell the kubernetes cluster who you are. + User KubeConfigUser `json:"user"` +} + +// KubeConfigCluster contains information about how to communicate with a kubernetes cluster. +// +// Adapted from clientcmdv1.Cluster. +type KubeConfigCluster struct { + // Server is the address of the kubernetes cluster (https://hostname:port). + Server string `json:"server"` + // TLSServerName is used to check server certificate. If TLSServerName is empty, the hostname used to contact the server is used. + // +optional + TLSServerName string `json:"tls-server-name,omitempty"` + // InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure. + // +optional + InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"` + // CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority + // +optional + CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"` + // ProxyURL is the URL to the proxy to be used for all requests made by this + // client. URLs with "http", "https", and "socks5" schemes are supported. If + // this configuration is not provided or the empty string, the client + // attempts to construct a proxy configuration from http_proxy and + // https_proxy environment variables. If these environment variables are not + // set, the client does not attempt to proxy requests. + // + // socks5 proxying does not currently support spdy streaming endpoints (exec, + // attach, port forward). + // +optional + ProxyURL string `json:"proxy-url,omitempty"` +} + +// KubeConfigUser contains information that describes identity information. +// This is used to tell the kubernetes cluster who you are. +// +// Adapted from clientcmdv1.AuthInfo. +type KubeConfigUser struct { + // AuthProvider specifies a custom authentication plugin for the kubernetes cluster. + // +optional + AuthProvider *KubeConfigAuthProvider `json:"auth-provider,omitempty"` + // Exec specifies a custom exec-based authentication plugin for the kubernetes cluster. + // +optional + Exec *KubeConfigAuthExec `json:"exec,omitempty"` +} + +// KubeConfigAuthProvider holds the configuration for a specified auth provider. +type KubeConfigAuthProvider struct { + // Name is the name of the authentication plugin. + Name string `json:"name"` + + // Config holds the parameters for the authentication plugin. + Config map[string]string `json:"config"` +} + +// KubeConfigAuthExec specifies a command to provide client credentials. The command is exec'd +// and outputs structured stdout holding credentials. +// +// See the client.authentication.k8s.io API group for specifications of the exact input +// and output format. +type KubeConfigAuthExec struct { + // Command to execute. + Command string `json:"command"` + // Arguments to pass to the command when executing it. + // +optional + Args []string `json:"args,omitempty"` + // Env defines additional environment variables to expose to the process. These + // are unioned with the host's environment, as well as variables client-go uses + // to pass argument to the plugin. + // +optional + Env []KubeConfigAuthExecEnv `json:"env,omitempty"` + + // Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + // the same encoding version as the input. + APIVersion string `json:"apiVersion,omitempty"` + + // ProvideClusterInfo determines whether or not to provide cluster information, + // which could potentially contain very large CA data, to this exec plugin as a + // part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + // to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + // reading this environment variable. + ProvideClusterInfo bool `json:"provideClusterInfo"` + + // InteractiveMode determines this plugin's relationship with standard input. Valid + // values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + // exec plugin wants to use standard input if it is available), or "Always" (this exec + // plugin requires standard input to function). See ExecInteractiveMode values for more + // details. + // + // If APIVersion is client.authentication.k8s.io/v1alpha1 or + // client.authentication.k8s.io/v1beta1, then this field is optional and defaults + // to "IfAvailable" when unset. Otherwise, this field is required. + // + // +kubebuilder:validation:Enum=Never;IfAvailable;Always + // +kubebuilder:default=Never + // +optional + InteractiveMode string `json:"interactiveMode,omitempty"` +} + +// KubeConfigAuthExecEnv is used for setting environment variables when executing an exec-based +// credential plugin. +type KubeConfigAuthExecEnv struct { + Name string `json:"name"` + Value string `json:"value"` } // HostPathMount contains elements describing volumes that are mounted from the diff --git a/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go b/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go index 425b90edab0b..dcd587f5d772 100644 --- a/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go +++ b/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go @@ -279,7 +279,7 @@ func (in *Discovery) DeepCopyInto(out *Discovery) { if in.File != nil { in, out := &in.File, &out.File *out = new(FileDiscovery) - **out = **in + (*in).DeepCopyInto(*out) } if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout @@ -395,6 +395,11 @@ func (in *File) DeepCopy() *File { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FileDiscovery) DeepCopyInto(out *FileDiscovery) { *out = *in + if in.KubeConfig != nil { + in, out := &in.KubeConfig, &out.KubeConfig + *out = new(FileDiscoveryKubeConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FileDiscovery. @@ -407,6 +412,27 @@ func (in *FileDiscovery) DeepCopy() *FileDiscovery { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FileDiscoveryKubeConfig) DeepCopyInto(out *FileDiscoveryKubeConfig) { + *out = *in + if in.Cluster != nil { + in, out := &in.Cluster, &out.Cluster + *out = new(KubeConfigCluster) + (*in).DeepCopyInto(*out) + } + in.User.DeepCopyInto(&out.User) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FileDiscoveryKubeConfig. +func (in *FileDiscoveryKubeConfig) DeepCopy() *FileDiscoveryKubeConfig { + if in == nil { + return nil + } + out := new(FileDiscoveryKubeConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FileSource) DeepCopyInto(out *FileSource) { *out = *in @@ -608,6 +634,113 @@ func (in *JoinControlPlane) DeepCopy() *JoinControlPlane { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeConfigAuthExec) DeepCopyInto(out *KubeConfigAuthExec) { + *out = *in + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]KubeConfigAuthExecEnv, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeConfigAuthExec. +func (in *KubeConfigAuthExec) DeepCopy() *KubeConfigAuthExec { + if in == nil { + return nil + } + out := new(KubeConfigAuthExec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeConfigAuthExecEnv) DeepCopyInto(out *KubeConfigAuthExecEnv) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeConfigAuthExecEnv. +func (in *KubeConfigAuthExecEnv) DeepCopy() *KubeConfigAuthExecEnv { + if in == nil { + return nil + } + out := new(KubeConfigAuthExecEnv) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeConfigAuthProvider) DeepCopyInto(out *KubeConfigAuthProvider) { + *out = *in + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeConfigAuthProvider. +func (in *KubeConfigAuthProvider) DeepCopy() *KubeConfigAuthProvider { + if in == nil { + return nil + } + out := new(KubeConfigAuthProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeConfigCluster) DeepCopyInto(out *KubeConfigCluster) { + *out = *in + if in.CertificateAuthorityData != nil { + in, out := &in.CertificateAuthorityData, &out.CertificateAuthorityData + *out = make([]byte, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeConfigCluster. +func (in *KubeConfigCluster) DeepCopy() *KubeConfigCluster { + if in == nil { + return nil + } + out := new(KubeConfigCluster) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeConfigUser) DeepCopyInto(out *KubeConfigUser) { + *out = *in + if in.AuthProvider != nil { + in, out := &in.AuthProvider, &out.AuthProvider + *out = new(KubeConfigAuthProvider) + (*in).DeepCopyInto(*out) + } + if in.Exec != nil { + in, out := &in.Exec, &out.Exec + *out = new(KubeConfigAuthExec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeConfigUser. +func (in *KubeConfigUser) DeepCopy() *KubeConfigUser { + if in == nil { + return nil + } + out := new(KubeConfigUser) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeadmConfig) DeepCopyInto(out *KubeadmConfig) { *out = *in diff --git a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml index 3375f583c6cc..4d7e5a35019c 100644 --- a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml +++ b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml @@ -2840,6 +2840,154 @@ spec: File is used to specify a file or URL to a kubeconfig file from which to load cluster information BootstrapToken and File are mutually exclusive properties: + kubeConfig: + description: |- + KubeConfig is used (optionally) to generate a KubeConfig based on the KubeadmConfig's information. + The file is generated at the path specified in KubeConfigPath. + + + Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + properties: + cluster: + description: |- + Cluster contains information about how to communicate with the kubernetes cluster. + + + By default the following fields are automatically populated: + - Server with the Cluster's ControlPlaneEndpoint. + - CertificateAuthorityData with the Cluster's CA certificate. + properties: + certificate-authority-data: + description: CertificateAuthorityData contains + PEM-encoded certificate authority certificates. + Overrides CertificateAuthority + format: byte + type: string + insecure-skip-tls-verify: + description: InsecureSkipTLSVerify skips the validity + check for the server's certificate. This will + make your HTTPS connections insecure. + type: boolean + proxy-url: + description: |- + ProxyURL is the URL to the proxy to be used for all requests made by this + client. URLs with "http", "https", and "socks5" schemes are supported. If + this configuration is not provided or the empty string, the client + attempts to construct a proxy configuration from http_proxy and + https_proxy environment variables. If these environment variables are not + set, the client does not attempt to proxy requests. + + + socks5 proxying does not currently support spdy streaming endpoints (exec, + attach, port forward). + type: string + server: + description: Server is the address of the kubernetes + cluster (https://hostname:port). + type: string + tls-server-name: + description: TLSServerName is used to check server + certificate. If TLSServerName is empty, the + hostname used to contact the server is used. + type: string + required: + - server + type: object + user: + description: |- + User contains information that describes identity information. + This is used to tell the kubernetes cluster who you are. + properties: + auth-provider: + description: AuthProvider specifies a custom authentication + plugin for the kubernetes cluster. + properties: + config: + additionalProperties: + type: string + description: Config holds the parameters for + the authentication plugin. + type: object + name: + description: Name is the name of the authentication + plugin. + type: string + required: + - config + - name + type: object + exec: + description: Exec specifies a custom exec-based + authentication plugin for the kubernetes cluster. + properties: + apiVersion: + description: |- + Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + the same encoding version as the input. + type: string + args: + description: Arguments to pass to the command + when executing it. + items: + type: string + type: array + command: + description: Command to execute. + type: string + env: + description: |- + Env defines additional environment variables to expose to the process. These + are unioned with the host's environment, as well as variables client-go uses + to pass argument to the plugin. + items: + description: |- + KubeConfigAuthExecEnv is used for setting environment variables when executing an exec-based + credential plugin. + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + interactiveMode: + default: Never + description: |- + InteractiveMode determines this plugin's relationship with standard input. Valid + values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + exec plugin wants to use standard input if it is available), or "Always" (this exec + plugin requires standard input to function). See ExecInteractiveMode values for more + details. + + + If APIVersion is client.authentication.k8s.io/v1alpha1 or + client.authentication.k8s.io/v1beta1, then this field is optional and defaults + to "IfAvailable" when unset. Otherwise, this field is required. + enum: + - Never + - IfAvailable + - Always + type: string + provideClusterInfo: + description: |- + ProvideClusterInfo determines whether or not to provide cluster information, + which could potentially contain very large CA data, to this exec plugin as a + part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + reading this environment variable. + type: boolean + required: + - command + - provideClusterInfo + type: object + type: object + required: + - user + type: object kubeConfigPath: description: KubeConfigPath is used to specify the actual file path or URL to the kubeconfig file from which to diff --git a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml index b9970098e05d..c79a0dcbf87d 100644 --- a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml +++ b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml @@ -2805,6 +2805,158 @@ spec: File is used to specify a file or URL to a kubeconfig file from which to load cluster information BootstrapToken and File are mutually exclusive properties: + kubeConfig: + description: |- + KubeConfig is used (optionally) to generate a KubeConfig based on the KubeadmConfig's information. + The file is generated at the path specified in KubeConfigPath. + + + Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + properties: + cluster: + description: |- + Cluster contains information about how to communicate with the kubernetes cluster. + + + By default the following fields are automatically populated: + - Server with the Cluster's ControlPlaneEndpoint. + - CertificateAuthorityData with the Cluster's CA certificate. + properties: + certificate-authority-data: + description: CertificateAuthorityData + contains PEM-encoded certificate authority + certificates. Overrides CertificateAuthority + format: byte + type: string + insecure-skip-tls-verify: + description: InsecureSkipTLSVerify skips + the validity check for the server's + certificate. This will make your HTTPS + connections insecure. + type: boolean + proxy-url: + description: |- + ProxyURL is the URL to the proxy to be used for all requests made by this + client. URLs with "http", "https", and "socks5" schemes are supported. If + this configuration is not provided or the empty string, the client + attempts to construct a proxy configuration from http_proxy and + https_proxy environment variables. If these environment variables are not + set, the client does not attempt to proxy requests. + + + socks5 proxying does not currently support spdy streaming endpoints (exec, + attach, port forward). + type: string + server: + description: Server is the address of + the kubernetes cluster (https://hostname:port). + type: string + tls-server-name: + description: TLSServerName is used to + check server certificate. If TLSServerName + is empty, the hostname used to contact + the server is used. + type: string + required: + - server + type: object + user: + description: |- + User contains information that describes identity information. + This is used to tell the kubernetes cluster who you are. + properties: + auth-provider: + description: AuthProvider specifies a + custom authentication plugin for the + kubernetes cluster. + properties: + config: + additionalProperties: + type: string + description: Config holds the parameters + for the authentication plugin. + type: object + name: + description: Name is the name of the + authentication plugin. + type: string + required: + - config + - name + type: object + exec: + description: Exec specifies a custom exec-based + authentication plugin for the kubernetes + cluster. + properties: + apiVersion: + description: |- + Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + the same encoding version as the input. + type: string + args: + description: Arguments to pass to + the command when executing it. + items: + type: string + type: array + command: + description: Command to execute. + type: string + env: + description: |- + Env defines additional environment variables to expose to the process. These + are unioned with the host's environment, as well as variables client-go uses + to pass argument to the plugin. + items: + description: |- + KubeConfigAuthExecEnv is used for setting environment variables when executing an exec-based + credential plugin. + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + interactiveMode: + default: Never + description: |- + InteractiveMode determines this plugin's relationship with standard input. Valid + values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + exec plugin wants to use standard input if it is available), or "Always" (this exec + plugin requires standard input to function). See ExecInteractiveMode values for more + details. + + + If APIVersion is client.authentication.k8s.io/v1alpha1 or + client.authentication.k8s.io/v1beta1, then this field is optional and defaults + to "IfAvailable" when unset. Otherwise, this field is required. + enum: + - Never + - IfAvailable + - Always + type: string + provideClusterInfo: + description: |- + ProvideClusterInfo determines whether or not to provide cluster information, + which could potentially contain very large CA data, to this exec plugin as a + part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + reading this environment variable. + type: boolean + required: + - command + - provideClusterInfo + type: object + type: object + required: + - user + type: object kubeConfigPath: description: KubeConfigPath is used to specify the actual file path or URL to the kubeconfig diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go index 0cf55f48e373..a223418be756 100644 --- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go +++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" kerrors "k8s.io/apimachinery/pkg/util/errors" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" bootstrapapi "k8s.io/cluster-bootstrap/token/api" bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets" "k8s.io/klog/v2" @@ -40,6 +41,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/yaml" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" @@ -634,6 +636,15 @@ func (r *KubeadmConfigReconciler) joinWorker(ctx context.Context, scope *Scope) return ctrl.Result{}, err } + if discoveryFile := scope.Config.Spec.JoinConfiguration.Discovery.File; discoveryFile != nil && discoveryFile.KubeConfig != nil { + kubeconfig, err := r.resolveDiscoveryKubeConfig(scope.Config) + if err != nil { + conditions.MarkFalse(scope.Config, bootstrapv1.DataSecretAvailableCondition, bootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error()) + return ctrl.Result{}, err + } + files = append(files, *kubeconfig) + } + nodeInput := &cloudinit.NodeInput{ BaseUserData: cloudinit.BaseUserData{ AdditionalFiles: files, @@ -741,6 +752,15 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S return ctrl.Result{}, err } + if discoveryFile := scope.Config.Spec.JoinConfiguration.Discovery.File; discoveryFile != nil && discoveryFile.KubeConfig != nil { + kubeconfig, err := r.resolveDiscoveryKubeConfig(scope.Config) + if err != nil { + conditions.MarkFalse(scope.Config, bootstrapv1.DataSecretAvailableCondition, bootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error()) + return ctrl.Result{}, err + } + files = append(files, *kubeconfig) + } + controlPlaneJoinInput := &cloudinit.ControlPlaneJoinInput{ JoinConfiguration: joinData, Certificates: certificates, @@ -842,6 +862,64 @@ func (r *KubeadmConfigReconciler) resolveUsers(ctx context.Context, cfg *bootstr return collected, nil } +func (r *KubeadmConfigReconciler) resolveDiscoveryKubeConfig(config *bootstrapv1.KubeadmConfig) (*bootstrapv1.File, error) { + cfg := config.Spec.JoinConfiguration.Discovery.File + if cfg == nil || cfg.KubeConfig == nil { + return nil, errors.New("no discovery configuration file to resolve") + } + cluster := clientcmdv1.Cluster{ + Server: cfg.KubeConfig.Cluster.Server, + TLSServerName: cfg.KubeConfig.Cluster.TLSServerName, + InsecureSkipTLSVerify: cfg.KubeConfig.Cluster.InsecureSkipTLSVerify, + CertificateAuthorityData: cfg.KubeConfig.Cluster.CertificateAuthorityData, + ProxyURL: cfg.KubeConfig.Cluster.ProxyURL, + } + user := clientcmdv1.AuthInfo{} + if cfg.KubeConfig.User.AuthProvider != nil { + user.AuthProvider = &clientcmdv1.AuthProviderConfig{ + Name: cfg.KubeConfig.User.AuthProvider.Name, + Config: cfg.KubeConfig.User.AuthProvider.Config, + } + } + if cfg.KubeConfig.User.Exec != nil { + user.Exec = &clientcmdv1.ExecConfig{ + Command: cfg.KubeConfig.User.Exec.Command, + Args: cfg.KubeConfig.User.Exec.Args, + APIVersion: cfg.KubeConfig.User.Exec.APIVersion, + ProvideClusterInfo: cfg.KubeConfig.User.Exec.ProvideClusterInfo, + InteractiveMode: clientcmdv1.ExecInteractiveMode(cfg.KubeConfig.User.Exec.InteractiveMode), + } + for _, env := range cfg.KubeConfig.User.Exec.Env { + user.Exec.Env = append(user.Exec.Env, clientcmdv1.ExecEnvVar{Name: env.Name, Value: env.Value}) + } + } + kubeconfig := clientcmdv1.Config{ + CurrentContext: "default", + Contexts: []clientcmdv1.NamedContext{ + { + Name: "default", + Context: clientcmdv1.Context{ + Cluster: "default", + AuthInfo: "default", + }, + }, + }, + Clusters: []clientcmdv1.NamedCluster{{Name: "default", Cluster: cluster}}, + AuthInfos: []clientcmdv1.NamedAuthInfo{{Name: "default", AuthInfo: user}}, + } + + b, err := yaml.Marshal(kubeconfig) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal kubeconfig from JoinConfiguration.Discovery.File.KubeConfig") + } + return &bootstrapv1.File{ + Path: cfg.KubeConfigPath, + Owner: "root:root", + Permissions: "0640", + Content: string(b), + }, nil +} + // resolveSecretUserContent returns passwd fetched from a referenced secret object. func (r *KubeadmConfigReconciler) resolveSecretPasswordContent(ctx context.Context, ns string, source bootstrapv1.User) ([]byte, error) { secret := &corev1.Secret{} @@ -970,7 +1048,7 @@ func (r *KubeadmConfigReconciler) reconcileDiscovery(ctx context.Context, cluste // if config already contains a file discovery configuration, respect it without further validations if config.Spec.JoinConfiguration.Discovery.File != nil { - return ctrl.Result{}, nil + return r.reconcileDiscoveryFile(ctx, cluster, config, certificates) } // otherwise it is necessary to ensure token discovery is properly configured @@ -1026,6 +1104,35 @@ func (r *KubeadmConfigReconciler) reconcileDiscovery(ctx context.Context, cluste return ctrl.Result{}, nil } +func (r *KubeadmConfigReconciler) reconcileDiscoveryFile(ctx context.Context, cluster *clusterv1.Cluster, config *bootstrapv1.KubeadmConfig, certificates secret.Certificates) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx) + cfg := config.Spec.JoinConfiguration.Discovery.File.KubeConfig + if cfg == nil { + // Nothing else to do. + return ctrl.Result{}, nil + } + + if cfg.Cluster == nil { + cfg.Cluster = &bootstrapv1.KubeConfigCluster{} + } + + if cfg.Cluster.Server == "" { + if !cluster.Spec.ControlPlaneEndpoint.IsValid() { + log.V(1).Info("Waiting for Cluster Controller to set Cluster.Spec.ControlPlaneEndpoint") + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + cfg.Cluster.Server = fmt.Sprintf("https://%s", cluster.Spec.ControlPlaneEndpoint.String()) + log.V(3).Info("Altering JoinConfiguration.Discovery.File.KubeConfig.Cluster.Server", "apiServerEndpoint", cfg.Cluster.Server) + } + + if len(cfg.Cluster.CertificateAuthorityData) == 0 { + cfg.Cluster.CertificateAuthorityData = certificates.GetByPurpose(secret.ClusterCA).KeyPair.Cert + log.V(3).Info("Altering JoinConfiguration.Discovery.File.KubeConfig.Cluster.CertificateAuthorityData") + } + + return ctrl.Result{}, nil +} + // reconcileTopLevelObjectSettings injects into config.ClusterConfiguration values from top level objects like cluster and machine. // The implementation func respect user provided config values, but in case some of them are missing, values from top level objects are used. func (r *KubeadmConfigReconciler) reconcileTopLevelObjectSettings(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine, config *bootstrapv1.KubeadmConfig) { diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go index a2f02c3cc1a0..35663a18bfa0 100644 --- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go +++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go @@ -46,9 +46,11 @@ import ( "sigs.k8s.io/cluster-api/feature" "sigs.k8s.io/cluster-api/internal/test/builder" "sigs.k8s.io/cluster-api/util" + "sigs.k8s.io/cluster-api/util/certs" "sigs.k8s.io/cluster-api/util/conditions" "sigs.k8s.io/cluster-api/util/patch" "sigs.k8s.io/cluster-api/util/secret" + utilyaml "sigs.k8s.io/cluster-api/util/yaml" ) // MachineToBootstrapMapFunc return kubeadm bootstrap configref name when configref exists. @@ -1497,6 +1499,35 @@ func TestKubeadmConfigReconciler_Reconcile_DiscoveryReconcileBehaviors(t *testin return nil }, }, + { + name: "Respect discoveryConfiguration.File.KubeConfig", + cluster: goodcluster, + config: &bootstrapv1.KubeadmConfig{ + Spec: bootstrapv1.KubeadmConfigSpec{ + JoinConfiguration: &bootstrapv1.JoinConfiguration{ + Discovery: bootstrapv1.Discovery{ + File: &bootstrapv1.FileDiscovery{ + KubeConfigPath: "/bootstrap-kubeconfig.yaml", + KubeConfig: &bootstrapv1.FileDiscoveryKubeConfig{ + User: bootstrapv1.KubeConfigUser{ + Token: "admin", + }, + }, + }, + }, + }, + }, + }, + validateDiscovery: func(g *WithT, c *bootstrapv1.KubeadmConfig) error { + d := c.Spec.JoinConfiguration.Discovery + g.Expect(d.BootstrapToken).To(BeNil()) + g.Expect(d.File.KubeConfig.User.Token).To(Equal("admin")) + g.Expect(d.File.KubeConfig.Cluster).ToNot(BeNil()) + g.Expect(d.File.KubeConfig.Cluster.Server).To(Equal("https://example.com:6443")) + g.Expect(d.File.KubeConfig.Cluster.CertificateAuthorityData).To(BeEquivalentTo("ca-data")) + return nil + }, + }, { name: "Respect discoveryConfiguration.BootstrapToken.APIServerEndpoint", cluster: goodcluster, @@ -1573,7 +1604,15 @@ func TestKubeadmConfigReconciler_Reconcile_DiscoveryReconcileBehaviors(t *testin KubeadmInitLock: &myInitLocker{}, } - res, err := k.reconcileDiscovery(ctx, tc.cluster, tc.config, secret.Certificates{}) + res, err := k.reconcileDiscovery(ctx, tc.cluster, tc.config, secret.Certificates{ + &secret.Certificate{ + Purpose: secret.ClusterCA, + KeyPair: &certs.KeyPair{ + Cert: []byte("ca-data"), + Key: []byte("ca-key"), + }, + }, + }) g.Expect(res.IsZero()).To(BeTrue()) g.Expect(err).ToNot(HaveOccurred()) @@ -2147,6 +2186,110 @@ func TestKubeadmConfigReconciler_ResolveFiles(t *testing.T) { } } +func TestKubeadmConfigReconciler_ResolveDiscoveryFileKubeConfig(t *testing.T) { + cases := map[string]struct { + cfg *bootstrapv1.KubeadmConfig + expect *bootstrapv1.File + err string + }{ + "should generate the bootstrap kubeconfig correctly": { + cfg: &bootstrapv1.KubeadmConfig{ + Spec: bootstrapv1.KubeadmConfigSpec{ + JoinConfiguration: &bootstrapv1.JoinConfiguration{ + Discovery: bootstrapv1.Discovery{ + File: &bootstrapv1.FileDiscovery{ + KubeConfigPath: "/bootstrap-kubeconfig.yaml", + KubeConfig: &bootstrapv1.FileDiscoveryKubeConfig{ + User: bootstrapv1.KubeConfigUser{ + Exec: &bootstrapv1.KubeConfigAuthExec{ + APIVersion: "client.authentication.k8s.io/v1", + Command: "/usr/bin/bootstrap", + InteractiveMode: "Never", + Env: []bootstrapv1.KubeConfigAuthExecEnv{ + {Name: "ENV_TEST", Value: "value"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expect: &bootstrapv1.File{ + Path: "/bootstrap-kubeconfig.yaml", + Owner: "root:root", + Permissions: "0640", + Content: utilyaml.Raw(` +clusters: +- cluster: + certificate-authority-data: Y2EtZGF0YQ== + server: https://example.com:6443 + name: default +contexts: +- context: + cluster: default + user: default + name: default +current-context: default +preferences: {} +users: +- name: default + user: + exec: + apiVersion: client.authentication.k8s.io/v1 + args: null + command: /usr/bin/bootstrap + env: + - name: ENV_TEST + value: value + interactiveMode: Never + provideClusterInfo: false +`), + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + g := NewWithT(t) + + myclient := fake.NewClientBuilder().Build() + k := &KubeadmConfigReconciler{ + Client: myclient, + SecretCachingClient: myclient, + KubeadmInitLock: &myInitLocker{}, + } + + _, err := k.reconcileDiscoveryFile(ctx, &clusterv1.Cluster{ + Spec: clusterv1.ClusterSpec{ + ControlPlaneEndpoint: clusterv1.APIEndpoint{ + Host: "example.com", + Port: 6443, + }, + }, + }, tc.cfg, secret.Certificates{ + &secret.Certificate{ + Purpose: secret.ClusterCA, + KeyPair: &certs.KeyPair{ + Cert: []byte("ca-data"), + Key: []byte("ca-key"), + }, + }, + }) + g.Expect(err).ToNot(HaveOccurred()) + + file, err := k.resolveDiscoveryKubeConfig(tc.cfg) + if tc.err != "" { + g.Expect(err).To(MatchError(ContainSubstring(tc.err))) + return + } + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(file).To(BeEquivalentTo(tc.expect)) + }) + } +} + func TestKubeadmConfigReconciler_ResolveUsers(t *testing.T) { fakePasswd := "bar" testSecret := &corev1.Secret{ diff --git a/bootstrap/kubeadm/types/upstreamv1beta1/conversion.go b/bootstrap/kubeadm/types/upstreamv1beta1/conversion.go index eb3f43d4532c..5944965f69f0 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta1/conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta1/conversion.go @@ -91,3 +91,8 @@ func Convert_v1beta1_NodeRegistrationOptions_To_upstreamv1beta1_NodeRegistration // NodeRegistrationOptions.IgnorePreflightErrors and ImagePullPolicy does not exist in kubeadm v1beta1, dropping those info. return autoConvert_v1beta1_NodeRegistrationOptions_To_upstreamv1beta1_NodeRegistrationOptions(in, out, s) } + +func Convert_v1beta1_FileDiscovery_To_upstreamv1beta1_FileDiscovery(in *bootstrapv1.FileDiscovery, out *FileDiscovery, s apimachineryconversion.Scope) error { + // JoinConfiguration.Discovery.File.KubeConfig does not exist in kubeadm because it's internal to Cluster API, dropping those info. + return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta1_FileDiscovery(in, out, s) +} diff --git a/bootstrap/kubeadm/types/upstreamv1beta1/conversion_test.go b/bootstrap/kubeadm/types/upstreamv1beta1/conversion_test.go index b170100c3615..a685676a6ac6 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta1/conversion_test.go +++ b/bootstrap/kubeadm/types/upstreamv1beta1/conversion_test.go @@ -103,6 +103,11 @@ func bootstrapv1JoinConfigurationFuzzer(obj *bootstrapv1.JoinConfiguration, c fu // JoinConfiguration.SkipPhases does not exist in kubeadm v1beta1 types, pinning it to avoid cabpk v1beta1 --> kubeadm v1beta1 --> cabpk v1beta1 round trip errors. obj.SkipPhases = nil + + // JoinConfiguration.Discovery.File.KubeConfig is internal to Cluster API and does not exist in kubeadm v1beta1 types, pinning it to avoid round trip errors. + if obj.Discovery.File != nil { + obj.Discovery.File.KubeConfig = nil + } } func bootstrapv1NodeRegistrationOptionsFuzzer(obj *bootstrapv1.NodeRegistrationOptions, c fuzz.Continue) { diff --git a/bootstrap/kubeadm/types/upstreamv1beta1/zz_generated.conversion.go b/bootstrap/kubeadm/types/upstreamv1beta1/zz_generated.conversion.go index 0497d927b800..3a35e2f91972 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta1/zz_generated.conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta1/zz_generated.conversion.go @@ -1,5 +1,5 @@ -//go:build !ignore_autogenerated_kubeadm_types -// +build !ignore_autogenerated_kubeadm_types +//go:build !ignore_autogenerated +// +build !ignore_autogenerated /* Copyright The Kubernetes Authors. @@ -153,11 +153,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_FileDiscovery_To_upstreamv1beta1_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*HostPathMount)(nil), (*v1beta1.HostPathMount)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_upstreamv1beta1_HostPathMount_To_v1beta1_HostPathMount(a.(*HostPathMount), b.(*v1beta1.HostPathMount), scope) }); err != nil { @@ -233,6 +228,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_FileDiscovery_To_upstreamv1beta1_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.InitConfiguration)(nil), (*InitConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_InitConfiguration_To_upstreamv1beta1_InitConfiguration(a.(*v1beta1.InitConfiguration), b.(*InitConfiguration), scope) }); err != nil { @@ -505,7 +505,15 @@ func Convert_v1beta1_DNS_To_upstreamv1beta1_DNS(in *v1beta1.DNS, out *DNS, s con func autoConvert_upstreamv1beta1_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1.Discovery, s conversion.Scope) error { out.BootstrapToken = (*v1beta1.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*v1beta1.FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(v1beta1.FileDiscovery) + if err := Convert_upstreamv1beta1_FileDiscovery_To_v1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -518,7 +526,15 @@ func Convert_upstreamv1beta1_Discovery_To_v1beta1_Discovery(in *Discovery, out * func autoConvert_v1beta1_Discovery_To_upstreamv1beta1_Discovery(in *v1beta1.Discovery, out *Discovery, s conversion.Scope) error { out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + if err := Convert_v1beta1_FileDiscovery_To_upstreamv1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -589,14 +605,10 @@ func Convert_upstreamv1beta1_FileDiscovery_To_v1beta1_FileDiscovery(in *FileDisc func autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta1_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { out.KubeConfigPath = in.KubeConfigPath + // WARNING: in.KubeConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_FileDiscovery_To_upstreamv1beta1_FileDiscovery is an autogenerated conversion function. -func Convert_v1beta1_FileDiscovery_To_upstreamv1beta1_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { - return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta1_FileDiscovery(in, out, s) -} - func autoConvert_upstreamv1beta1_HostPathMount_To_v1beta1_HostPathMount(in *HostPathMount, out *v1beta1.HostPathMount, s conversion.Scope) error { out.Name = in.Name out.HostPath = in.HostPath diff --git a/bootstrap/kubeadm/types/upstreamv1beta1/zz_generated.deepcopy.go b/bootstrap/kubeadm/types/upstreamv1beta1/zz_generated.deepcopy.go index aeaaa3afff3d..00364f438193 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta1/zz_generated.deepcopy.go +++ b/bootstrap/kubeadm/types/upstreamv1beta1/zz_generated.deepcopy.go @@ -23,7 +23,7 @@ package upstreamv1beta1 import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/bootstrap/kubeadm/types/upstreamv1beta2/conversion.go b/bootstrap/kubeadm/types/upstreamv1beta2/conversion.go index 486e8962528a..f40ac2ab3cdc 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta2/conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta2/conversion.go @@ -101,3 +101,8 @@ func Convert_v1beta1_NodeRegistrationOptions_To_upstreamv1beta2_NodeRegistration // NodeRegistrationOptions.IgnorePreflightErrors and ImagePullPolicy does not exist in kubeadm v1beta2, dropping those info. return autoConvert_v1beta1_NodeRegistrationOptions_To_upstreamv1beta2_NodeRegistrationOptions(in, out, s) } + +func Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in *bootstrapv1.FileDiscovery, out *FileDiscovery, s apimachineryconversion.Scope) error { + // JoinConfiguration.Discovery.File.KubeConfig does not exist in kubeadm because it's internal to Cluster API, dropping those info. + return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in, out, s) +} diff --git a/bootstrap/kubeadm/types/upstreamv1beta2/conversion_test.go b/bootstrap/kubeadm/types/upstreamv1beta2/conversion_test.go index 7066cb264892..5ba28bec70a4 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta2/conversion_test.go +++ b/bootstrap/kubeadm/types/upstreamv1beta2/conversion_test.go @@ -114,6 +114,11 @@ func bootstrapv1JoinConfigurationFuzzer(obj *bootstrapv1.JoinConfiguration, c fu obj.Patches = nil obj.SkipPhases = nil + + // JoinConfiguration.Discovery.File.KubeConfig is internal to Cluster API and does not exist in kubeadm v1beta1 types, pinning it to avoid round trip errors. + if obj.Discovery.File != nil { + obj.Discovery.File.KubeConfig = nil + } } func bootstrapv1NodeRegistrationOptionsFuzzer(obj *bootstrapv1.NodeRegistrationOptions, c fuzz.Continue) { diff --git a/bootstrap/kubeadm/types/upstreamv1beta2/zz_generated.conversion.go b/bootstrap/kubeadm/types/upstreamv1beta2/zz_generated.conversion.go index 0769ceb3a65f..c9d29c1d2ba2 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta2/zz_generated.conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta2/zz_generated.conversion.go @@ -153,11 +153,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*HostPathMount)(nil), (*v1beta1.HostPathMount)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_upstreamv1beta2_HostPathMount_To_v1beta1_HostPathMount(a.(*HostPathMount), b.(*v1beta1.HostPathMount), scope) }); err != nil { @@ -233,6 +228,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.InitConfiguration)(nil), (*InitConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_InitConfiguration_To_upstreamv1beta2_InitConfiguration(a.(*v1beta1.InitConfiguration), b.(*InitConfiguration), scope) }); err != nil { @@ -505,7 +505,15 @@ func Convert_v1beta1_DNS_To_upstreamv1beta2_DNS(in *v1beta1.DNS, out *DNS, s con func autoConvert_upstreamv1beta2_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1.Discovery, s conversion.Scope) error { out.BootstrapToken = (*v1beta1.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*v1beta1.FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(v1beta1.FileDiscovery) + if err := Convert_upstreamv1beta2_FileDiscovery_To_v1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -518,7 +526,15 @@ func Convert_upstreamv1beta2_Discovery_To_v1beta1_Discovery(in *Discovery, out * func autoConvert_v1beta1_Discovery_To_upstreamv1beta2_Discovery(in *v1beta1.Discovery, out *Discovery, s conversion.Scope) error { out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + if err := Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -589,14 +605,10 @@ func Convert_upstreamv1beta2_FileDiscovery_To_v1beta1_FileDiscovery(in *FileDisc func autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { out.KubeConfigPath = in.KubeConfigPath + // WARNING: in.KubeConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery is an autogenerated conversion function. -func Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { - return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in, out, s) -} - func autoConvert_upstreamv1beta2_HostPathMount_To_v1beta1_HostPathMount(in *HostPathMount, out *v1beta1.HostPathMount, s conversion.Scope) error { out.Name = in.Name out.HostPath = in.HostPath diff --git a/bootstrap/kubeadm/types/upstreamv1beta3/conversion.go b/bootstrap/kubeadm/types/upstreamv1beta3/conversion.go index feef95647790..b6ff0ac5bdb3 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta3/conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta3/conversion.go @@ -64,3 +64,8 @@ func Convert_upstreamv1beta3_JoinControlPlane_To_v1beta1_JoinControlPlane(in *Jo // JoinControlPlane.CertificateKey does not exist in CABPK v1beta1 version, because Cluster API does not use automatic copy certs. return autoConvert_upstreamv1beta3_JoinControlPlane_To_v1beta1_JoinControlPlane(in, out, s) } + +func Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in *bootstrapv1.FileDiscovery, out *FileDiscovery, s apimachineryconversion.Scope) error { + // JoinConfiguration.Discovery.File.KubeConfig does not exist in kubeadm because it's internal to Cluster API, dropping those info. + return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in, out, s) +} diff --git a/bootstrap/kubeadm/types/upstreamv1beta3/conversion_test.go b/bootstrap/kubeadm/types/upstreamv1beta3/conversion_test.go index 7c5c7f5082cf..336e6014f06e 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta3/conversion_test.go +++ b/bootstrap/kubeadm/types/upstreamv1beta3/conversion_test.go @@ -65,6 +65,7 @@ func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { return []interface{}{ initConfigurationFuzzer, joinConfigurationFuzzer, + bootstrapv1JoinConfigurationFuzzer, nodeRegistrationOptionsFuzzer, joinControlPlanesFuzzer, } @@ -87,6 +88,15 @@ func joinConfigurationFuzzer(obj *JoinConfiguration, c fuzz.Continue) { obj.SkipPhases = nil } +func bootstrapv1JoinConfigurationFuzzer(obj *bootstrapv1.JoinConfiguration, c fuzz.Continue) { + c.FuzzNoCustom(obj) + + // JoinConfiguration.Discovery.File.KubeConfig is internal to Cluster API and does not exist in kubeadm v1beta1 types, pinning it to avoid round trip errors. + if obj.Discovery.File != nil { + obj.Discovery.File.KubeConfig = nil + } +} + func nodeRegistrationOptionsFuzzer(obj *NodeRegistrationOptions, c fuzz.Continue) { c.FuzzNoCustom(obj) diff --git a/bootstrap/kubeadm/types/upstreamv1beta3/zz_generated.conversion.go b/bootstrap/kubeadm/types/upstreamv1beta3/zz_generated.conversion.go index 084d9d501c84..cfd45bf23834 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta3/zz_generated.conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta3/zz_generated.conversion.go @@ -153,11 +153,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*HostPathMount)(nil), (*v1beta1.HostPathMount)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_upstreamv1beta3_HostPathMount_To_v1beta1_HostPathMount(a.(*HostPathMount), b.(*v1beta1.HostPathMount), scope) }); err != nil { @@ -248,6 +243,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) + }); err != nil { + return err + } return nil } @@ -493,7 +493,15 @@ func Convert_v1beta1_DNS_To_upstreamv1beta3_DNS(in *v1beta1.DNS, out *DNS, s con func autoConvert_upstreamv1beta3_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1.Discovery, s conversion.Scope) error { out.BootstrapToken = (*v1beta1.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*v1beta1.FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(v1beta1.FileDiscovery) + if err := Convert_upstreamv1beta3_FileDiscovery_To_v1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -506,7 +514,15 @@ func Convert_upstreamv1beta3_Discovery_To_v1beta1_Discovery(in *Discovery, out * func autoConvert_v1beta1_Discovery_To_upstreamv1beta3_Discovery(in *v1beta1.Discovery, out *Discovery, s conversion.Scope) error { out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + if err := Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -577,14 +593,10 @@ func Convert_upstreamv1beta3_FileDiscovery_To_v1beta1_FileDiscovery(in *FileDisc func autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { out.KubeConfigPath = in.KubeConfigPath + // WARNING: in.KubeConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery is an autogenerated conversion function. -func Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { - return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in, out, s) -} - func autoConvert_upstreamv1beta3_HostPathMount_To_v1beta1_HostPathMount(in *HostPathMount, out *v1beta1.HostPathMount, s conversion.Scope) error { out.Name = in.Name out.HostPath = in.HostPath diff --git a/bootstrap/kubeadm/types/upstreamv1beta4/conversion.go b/bootstrap/kubeadm/types/upstreamv1beta4/conversion.go index 5a34620fefa4..b8b8987ae000 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta4/conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta4/conversion.go @@ -176,6 +176,11 @@ func Convert_v1beta1_Discovery_To_upstreamv1beta4_Discovery(in *bootstrapv1.Disc return autoConvert_v1beta1_Discovery_To_upstreamv1beta4_Discovery(in, out, s) } +func Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(in *bootstrapv1.FileDiscovery, out *FileDiscovery, s apimachineryconversion.Scope) error { + // JoinConfiguration.Discovery.File.KubeConfig does not exist in kubeadm because it's internal to Cluster API, dropping those info. + return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(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) []Arg { diff --git a/bootstrap/kubeadm/types/upstreamv1beta4/conversion_test.go b/bootstrap/kubeadm/types/upstreamv1beta4/conversion_test.go index 2bacf29088dd..2d8d92ba7c40 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta4/conversion_test.go +++ b/bootstrap/kubeadm/types/upstreamv1beta4/conversion_test.go @@ -74,6 +74,7 @@ func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { nodeRegistrationOptionsFuzzer, joinControlPlaneFuzzer, bootstrapv1APIServerFuzzer, + bootstrapv1JoinConfigurationFuzzer, } } @@ -157,6 +158,15 @@ func bootstrapv1APIServerFuzzer(obj *bootstrapv1.APIServer, c fuzz.Continue) { obj.TimeoutForControlPlane = nil } +func bootstrapv1JoinConfigurationFuzzer(obj *bootstrapv1.JoinConfiguration, c fuzz.Continue) { + c.FuzzNoCustom(obj) + + // JoinConfiguration.Discovery.File.KubeConfig is internal to Cluster API and does not exist in kubeadm v1beta1 types, pinning it to avoid round trip errors. + if obj.Discovery.File != nil { + obj.Discovery.File.KubeConfig = nil + } +} + func TestTimeoutForControlPlaneMigration(t *testing.T) { timeout := metav1.Duration{Duration: 10 * time.Second} t.Run("from ClusterConfiguration to InitConfiguration and back", func(t *testing.T) { diff --git a/bootstrap/kubeadm/types/upstreamv1beta4/zz_generated.conversion.go b/bootstrap/kubeadm/types/upstreamv1beta4/zz_generated.conversion.go index b2cde96018a9..fd2350a66e96 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta4/zz_generated.conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta4/zz_generated.conversion.go @@ -123,11 +123,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*HostPathMount)(nil), (*v1beta1.HostPathMount)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_upstreamv1beta4_HostPathMount_To_v1beta1_HostPathMount(a.(*HostPathMount), b.(*v1beta1.HostPathMount), scope) }); err != nil { @@ -233,6 +228,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.JoinConfiguration)(nil), (*JoinConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_JoinConfiguration_To_upstreamv1beta4_JoinConfiguration(a.(*v1beta1.JoinConfiguration), b.(*JoinConfiguration), scope) }); err != nil { @@ -473,7 +473,15 @@ func Convert_v1beta1_DNS_To_upstreamv1beta4_DNS(in *v1beta1.DNS, out *DNS, s con func autoConvert_upstreamv1beta4_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1.Discovery, s conversion.Scope) error { out.BootstrapToken = (*v1beta1.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*v1beta1.FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(v1beta1.FileDiscovery) + if err := Convert_upstreamv1beta4_FileDiscovery_To_v1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken return nil } @@ -485,7 +493,15 @@ func Convert_upstreamv1beta4_Discovery_To_v1beta1_Discovery(in *Discovery, out * func autoConvert_v1beta1_Discovery_To_upstreamv1beta4_Discovery(in *v1beta1.Discovery, out *Discovery, s conversion.Scope) error { out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + if err := Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken // WARNING: in.Timeout requires manual conversion: does not exist in peer-type return nil @@ -567,14 +583,10 @@ func Convert_upstreamv1beta4_FileDiscovery_To_v1beta1_FileDiscovery(in *FileDisc func autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { out.KubeConfigPath = in.KubeConfigPath + // WARNING: in.KubeConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery is an autogenerated conversion function. -func Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { - return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(in, out, s) -} - func autoConvert_upstreamv1beta4_HostPathMount_To_v1beta1_HostPathMount(in *HostPathMount, out *v1beta1.HostPathMount, s conversion.Scope) error { out.Name = in.Name out.HostPath = in.HostPath diff --git a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml index 1171314f06ed..d4ce1082ab41 100644 --- a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml +++ b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml @@ -3315,6 +3315,157 @@ spec: File is used to specify a file or URL to a kubeconfig file from which to load cluster information BootstrapToken and File are mutually exclusive properties: + kubeConfig: + description: |- + KubeConfig is used (optionally) to generate a KubeConfig based on the KubeadmConfig's information. + The file is generated at the path specified in KubeConfigPath. + + + Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + properties: + cluster: + description: |- + Cluster contains information about how to communicate with the kubernetes cluster. + + + By default the following fields are automatically populated: + - Server with the Cluster's ControlPlaneEndpoint. + - CertificateAuthorityData with the Cluster's CA certificate. + properties: + certificate-authority-data: + description: CertificateAuthorityData contains + PEM-encoded certificate authority certificates. + Overrides CertificateAuthority + format: byte + type: string + insecure-skip-tls-verify: + description: InsecureSkipTLSVerify skips the + validity check for the server's certificate. + This will make your HTTPS connections insecure. + type: boolean + proxy-url: + description: |- + ProxyURL is the URL to the proxy to be used for all requests made by this + client. URLs with "http", "https", and "socks5" schemes are supported. If + this configuration is not provided or the empty string, the client + attempts to construct a proxy configuration from http_proxy and + https_proxy environment variables. If these environment variables are not + set, the client does not attempt to proxy requests. + + + socks5 proxying does not currently support spdy streaming endpoints (exec, + attach, port forward). + type: string + server: + description: Server is the address of the + kubernetes cluster (https://hostname:port). + type: string + tls-server-name: + description: TLSServerName is used to check + server certificate. If TLSServerName is + empty, the hostname used to contact the + server is used. + type: string + required: + - server + type: object + user: + description: |- + User contains information that describes identity information. + This is used to tell the kubernetes cluster who you are. + properties: + auth-provider: + description: AuthProvider specifies a custom + authentication plugin for the kubernetes + cluster. + properties: + config: + additionalProperties: + type: string + description: Config holds the parameters + for the authentication plugin. + type: object + name: + description: Name is the name of the authentication + plugin. + type: string + required: + - config + - name + type: object + exec: + description: Exec specifies a custom exec-based + authentication plugin for the kubernetes + cluster. + properties: + apiVersion: + description: |- + Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + the same encoding version as the input. + type: string + args: + description: Arguments to pass to the + command when executing it. + items: + type: string + type: array + command: + description: Command to execute. + type: string + env: + description: |- + Env defines additional environment variables to expose to the process. These + are unioned with the host's environment, as well as variables client-go uses + to pass argument to the plugin. + items: + description: |- + KubeConfigAuthExecEnv is used for setting environment variables when executing an exec-based + credential plugin. + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + interactiveMode: + default: Never + description: |- + InteractiveMode determines this plugin's relationship with standard input. Valid + values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + exec plugin wants to use standard input if it is available), or "Always" (this exec + plugin requires standard input to function). See ExecInteractiveMode values for more + details. + + + If APIVersion is client.authentication.k8s.io/v1alpha1 or + client.authentication.k8s.io/v1beta1, then this field is optional and defaults + to "IfAvailable" when unset. Otherwise, this field is required. + enum: + - Never + - IfAvailable + - Always + type: string + provideClusterInfo: + description: |- + ProvideClusterInfo determines whether or not to provide cluster information, + which could potentially contain very large CA data, to this exec plugin as a + part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + reading this environment variable. + type: boolean + required: + - command + - provideClusterInfo + type: object + type: object + required: + - user + type: object kubeConfigPath: description: KubeConfigPath is used to specify the actual file path or URL to the kubeconfig file from diff --git a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml index 44f0656ef654..26beed13a672 100644 --- a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml +++ b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml @@ -2013,6 +2013,161 @@ spec: File is used to specify a file or URL to a kubeconfig file from which to load cluster information BootstrapToken and File are mutually exclusive properties: + kubeConfig: + description: |- + KubeConfig is used (optionally) to generate a KubeConfig based on the KubeadmConfig's information. + The file is generated at the path specified in KubeConfigPath. + + + Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + properties: + cluster: + description: |- + Cluster contains information about how to communicate with the kubernetes cluster. + + + By default the following fields are automatically populated: + - Server with the Cluster's ControlPlaneEndpoint. + - CertificateAuthorityData with the Cluster's CA certificate. + properties: + certificate-authority-data: + description: CertificateAuthorityData + contains PEM-encoded certificate + authority certificates. Overrides + CertificateAuthority + format: byte + type: string + insecure-skip-tls-verify: + description: InsecureSkipTLSVerify + skips the validity check for the + server's certificate. This will + make your HTTPS connections insecure. + type: boolean + proxy-url: + description: |- + ProxyURL is the URL to the proxy to be used for all requests made by this + client. URLs with "http", "https", and "socks5" schemes are supported. If + this configuration is not provided or the empty string, the client + attempts to construct a proxy configuration from http_proxy and + https_proxy environment variables. If these environment variables are not + set, the client does not attempt to proxy requests. + + + socks5 proxying does not currently support spdy streaming endpoints (exec, + attach, port forward). + type: string + server: + description: Server is the address + of the kubernetes cluster (https://hostname:port). + type: string + tls-server-name: + description: TLSServerName is used + to check server certificate. If + TLSServerName is empty, the hostname + used to contact the server is used. + type: string + required: + - server + type: object + user: + description: |- + User contains information that describes identity information. + This is used to tell the kubernetes cluster who you are. + properties: + auth-provider: + description: AuthProvider specifies + a custom authentication plugin for + the kubernetes cluster. + properties: + config: + additionalProperties: + type: string + description: Config holds the + parameters for the authentication + plugin. + type: object + name: + description: Name is the name + of the authentication plugin. + type: string + required: + - config + - name + type: object + exec: + description: Exec specifies a custom + exec-based authentication plugin + for the kubernetes cluster. + properties: + apiVersion: + description: |- + Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + the same encoding version as the input. + type: string + args: + description: Arguments to pass + to the command when executing + it. + items: + type: string + type: array + command: + description: Command to execute. + type: string + env: + description: |- + Env defines additional environment variables to expose to the process. These + are unioned with the host's environment, as well as variables client-go uses + to pass argument to the plugin. + items: + description: |- + KubeConfigAuthExecEnv is used for setting environment variables when executing an exec-based + credential plugin. + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + interactiveMode: + default: Never + description: |- + InteractiveMode determines this plugin's relationship with standard input. Valid + values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + exec plugin wants to use standard input if it is available), or "Always" (this exec + plugin requires standard input to function). See ExecInteractiveMode values for more + details. + + + If APIVersion is client.authentication.k8s.io/v1alpha1 or + client.authentication.k8s.io/v1beta1, then this field is optional and defaults + to "IfAvailable" when unset. Otherwise, this field is required. + enum: + - Never + - IfAvailable + - Always + type: string + provideClusterInfo: + description: |- + ProvideClusterInfo determines whether or not to provide cluster information, + which could potentially contain very large CA data, to this exec plugin as a + part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + reading this environment variable. + type: boolean + required: + - command + - provideClusterInfo + type: object + type: object + required: + - user + type: object kubeConfigPath: description: KubeConfigPath is used to specify the actual file path or URL to the kubeconfig diff --git a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go index 12e4c657c1d1..8599b762b81a 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go @@ -77,6 +77,10 @@ func (src *KubeadmConfig) ConvertTo(dstRaw conversion.Hub) error { } dst.Spec.JoinConfiguration.Patches = restored.Spec.JoinConfiguration.Patches dst.Spec.JoinConfiguration.SkipPhases = restored.Spec.JoinConfiguration.SkipPhases + + if restored.Spec.JoinConfiguration.Discovery.File != nil && restored.Spec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.JoinConfiguration.Discovery.File.KubeConfig + } } if restored.Spec.JoinConfiguration != nil && restored.Spec.JoinConfiguration.NodeRegistration.ImagePullPolicy != "" { @@ -177,6 +181,10 @@ func (src *KubeadmConfigTemplate) ConvertTo(dstRaw conversion.Hub) error { } dst.Spec.Template.Spec.JoinConfiguration.Patches = restored.Spec.Template.Spec.JoinConfiguration.Patches dst.Spec.Template.Spec.JoinConfiguration.SkipPhases = restored.Spec.Template.Spec.JoinConfiguration.SkipPhases + + if restored.Spec.Template.Spec.JoinConfiguration.Discovery.File != nil && restored.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig + } } if restored.Spec.Template.Spec.JoinConfiguration != nil && restored.Spec.Template.Spec.JoinConfiguration.NodeRegistration.ImagePullPolicy != "" { diff --git a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion_test.go b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion_test.go index cac6e82f4c9a..44dcecc73010 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion_test.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion_test.go @@ -58,6 +58,7 @@ func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { // the values for ID and Secret to working alphanumeric values. kubeadmBootstrapTokenStringFuzzerV1UpstreamBeta1, kubeadmBootstrapTokenStringFuzzerV1Beta1, + bootstrapv1JoinConfigurationFuzzer, } } @@ -91,3 +92,12 @@ func kubeadmBootstrapTokenStringFuzzerV1Beta1(in *bootstrapv1.BootstrapTokenStri in.ID = "abcdef" in.Secret = "abcdef0123456789" } + +func bootstrapv1JoinConfigurationFuzzer(obj *bootstrapv1.JoinConfiguration, c fuzz.Continue) { + c.FuzzNoCustom(obj) + + // JoinConfiguration.Discovery.File.KubeConfig is internal to Cluster API and does not exist in kubeadm v1beta1 types, pinning it to avoid round trip errors. + if obj.Discovery.File != nil { + obj.Discovery.File.KubeConfig = nil + } +} diff --git a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go index 576fceebdf3f..0b1ce159d738 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go @@ -62,6 +62,10 @@ func (src *KubeadmConfig) ConvertTo(dstRaw conversion.Hub) error { } dst.Spec.JoinConfiguration.Patches = restored.Spec.JoinConfiguration.Patches dst.Spec.JoinConfiguration.SkipPhases = restored.Spec.JoinConfiguration.SkipPhases + + if restored.Spec.JoinConfiguration.Discovery.File != nil && restored.Spec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.JoinConfiguration.Discovery.File.KubeConfig + } } if restored.Spec.JoinConfiguration != nil && restored.Spec.JoinConfiguration.NodeRegistration.ImagePullPolicy != "" { @@ -143,6 +147,10 @@ func (src *KubeadmConfigTemplate) ConvertTo(dstRaw conversion.Hub) error { } dst.Spec.Template.Spec.JoinConfiguration.Patches = restored.Spec.Template.Spec.JoinConfiguration.Patches dst.Spec.Template.Spec.JoinConfiguration.SkipPhases = restored.Spec.Template.Spec.JoinConfiguration.SkipPhases + + if restored.Spec.Template.Spec.JoinConfiguration.Discovery.File != nil && restored.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig + } } if restored.Spec.Template.Spec.JoinConfiguration != nil && restored.Spec.Template.Spec.JoinConfiguration.NodeRegistration.ImagePullPolicy != "" { @@ -220,3 +228,8 @@ func Convert_v1beta1_KubeadmConfigTemplateResource_To_v1alpha4_KubeadmConfigTemp // KubeadmConfigTemplateResource.metadata does not exist in kubeadm v1alpha4. return autoConvert_v1beta1_KubeadmConfigTemplateResource_To_v1alpha4_KubeadmConfigTemplateResource(in, out, s) } + +func Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in *bootstrapv1.FileDiscovery, out *FileDiscovery, s apiconversion.Scope) error { + // JoinConfiguration.Discovery.File.KubeConfig does not exist in v1alpha4 APIs. + return autoConvert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in, out, s) +} diff --git a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion_test.go b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion_test.go index be6a5a20a9ab..e0a8f39cc8c3 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion_test.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion_test.go @@ -59,6 +59,7 @@ func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { // the values for ID and Secret to working alphanumeric values. kubeadmBootstrapTokenStringFuzzerV1Beta1, kubeadmBootstrapTokenStringFuzzerV1Alpha4, + bootstrapv1JoinConfigurationFuzzer, } } @@ -71,3 +72,12 @@ func kubeadmBootstrapTokenStringFuzzerV1Alpha4(in *BootstrapTokenString, _ fuzz. in.ID = fakeID in.Secret = fakeSecret } + +func bootstrapv1JoinConfigurationFuzzer(obj *bootstrapv1.JoinConfiguration, c fuzz.Continue) { + c.FuzzNoCustom(obj) + + // JoinConfiguration.Discovery.File.KubeConfig is internal to Cluster API and does not exist in kubeadm v1beta1 types, pinning it to avoid round trip errors. + if obj.Discovery.File != nil { + obj.Discovery.File.KubeConfig = nil + } +} diff --git a/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go index 5b88cc6fad97..131202cc4238 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go @@ -180,11 +180,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*FileSource)(nil), (*v1beta1.FileSource)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha4_FileSource_To_v1beta1_FileSource(a.(*FileSource), b.(*v1beta1.FileSource), scope) }); err != nil { @@ -375,6 +370,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.File)(nil), (*File)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_File_To_v1alpha4_File(a.(*v1beta1.File), b.(*File), scope) }); err != nil { @@ -675,7 +675,15 @@ func Convert_v1beta1_DNS_To_v1alpha4_DNS(in *v1beta1.DNS, out *DNS, s conversion func autoConvert_v1alpha4_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1.Discovery, s conversion.Scope) error { out.BootstrapToken = (*v1beta1.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*v1beta1.FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(v1beta1.FileDiscovery) + if err := Convert_v1alpha4_FileDiscovery_To_v1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -688,7 +696,15 @@ func Convert_v1alpha4_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1 func autoConvert_v1beta1_Discovery_To_v1alpha4_Discovery(in *v1beta1.Discovery, out *Discovery, s conversion.Scope) error { out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + if err := Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -807,14 +823,10 @@ func Convert_v1alpha4_FileDiscovery_To_v1beta1_FileDiscovery(in *FileDiscovery, func autoConvert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { out.KubeConfigPath = in.KubeConfigPath + // WARNING: in.KubeConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery is an autogenerated conversion function. -func Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { - return autoConvert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in, out, s) -} - func autoConvert_v1alpha4_FileSource_To_v1beta1_FileSource(in *FileSource, out *v1beta1.FileSource, s conversion.Scope) error { if err := Convert_v1alpha4_SecretFileSource_To_v1beta1_SecretFileSource(&in.Secret, &out.Secret, s); err != nil { return err diff --git a/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go b/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go index 955f75edfc83..807c8ecb9dad 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go +++ b/internal/apis/controlplane/kubeadm/v1alpha3/conversion.go @@ -82,6 +82,10 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error { } dst.Spec.KubeadmConfigSpec.JoinConfiguration.Patches = restored.Spec.KubeadmConfigSpec.JoinConfiguration.Patches dst.Spec.KubeadmConfigSpec.JoinConfiguration.SkipPhases = restored.Spec.KubeadmConfigSpec.JoinConfiguration.SkipPhases + + if restored.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File != nil && restored.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File.KubeConfig + } } dst.Spec.RolloutBefore = restored.Spec.RolloutBefore diff --git a/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go b/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go index 2d8217c19da4..78cad9499034 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go +++ b/internal/apis/controlplane/kubeadm/v1alpha3/conversion_test.go @@ -53,6 +53,7 @@ func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { cabpkBootstrapTokenStringFuzzer, dnsFuzzer, kubeadmClusterConfigurationFuzzer, + bootstrapv1JoinConfigurationFuzzer, } } @@ -78,3 +79,12 @@ func kubeadmClusterConfigurationFuzzer(obj *upstreamv1beta1.ClusterConfiguration // ClusterConfiguration.UseHyperKubeImage has been removed in v1alpha4, so setting it to false in order to avoid v1alpha3 --> v1alpha4 --> v1alpha3 round trip errors. obj.UseHyperKubeImage = false } + +func bootstrapv1JoinConfigurationFuzzer(obj *bootstrapv1.JoinConfiguration, c fuzz.Continue) { + c.FuzzNoCustom(obj) + + // JoinConfiguration.Discovery.File.KubeConfig is internal to Cluster API and does not exist in kubeadm v1beta1 types, pinning it to avoid round trip errors. + if obj.Discovery.File != nil { + obj.Discovery.File.KubeConfig = nil + } +} diff --git a/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go b/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go index 2fd47a0fe076..65878ed797b7 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go +++ b/internal/apis/controlplane/kubeadm/v1alpha4/conversion.go @@ -78,6 +78,10 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.KubeadmConfigSpec.JoinConfiguration = &bootstrapv1.JoinConfiguration{} } dst.Spec.KubeadmConfigSpec.JoinConfiguration.NodeRegistration.ImagePullPolicy = restored.Spec.KubeadmConfigSpec.JoinConfiguration.NodeRegistration.ImagePullPolicy + + if restored.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File != nil && restored.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File.KubeConfig + } } if restored.Spec.KubeadmConfigSpec.InitConfiguration != nil && restored.Spec.KubeadmConfigSpec.InitConfiguration.NodeRegistration.ImagePullPolicy != "" { @@ -179,6 +183,10 @@ func (src *KubeadmControlPlaneTemplate) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.Template.Spec.KubeadmConfigSpec.JoinConfiguration = &bootstrapv1.JoinConfiguration{} } dst.Spec.Template.Spec.KubeadmConfigSpec.JoinConfiguration.NodeRegistration.ImagePullPolicy = restored.Spec.Template.Spec.KubeadmConfigSpec.JoinConfiguration.NodeRegistration.ImagePullPolicy + + if restored.Spec.Template.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File != nil && restored.Spec.Template.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.Template.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.Template.Spec.KubeadmConfigSpec.JoinConfiguration.Discovery.File.KubeConfig + } } if restored.Spec.Template.Spec.KubeadmConfigSpec.InitConfiguration != nil && restored.Spec.Template.Spec.KubeadmConfigSpec.InitConfiguration.NodeRegistration.ImagePullPolicy != "" { diff --git a/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go b/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go index db232834f7b8..e68d1dd153e1 100644 --- a/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go +++ b/internal/apis/controlplane/kubeadm/v1alpha4/conversion_test.go @@ -65,6 +65,7 @@ func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { cabpkBootstrapTokenStringFuzzer, kubeadmBootstrapTokenStringFuzzerV1Alpha4, kubeadmControlPlaneTemplateResourceSpecFuzzerV1Alpha4, + bootstrapv1JoinConfigurationFuzzer, } } @@ -87,3 +88,12 @@ func kubeadmControlPlaneTemplateResourceSpecFuzzerV1Alpha4(in *KubeadmControlPla in.Spec.MachineTemplate.ObjectMeta = clusterv1alpha4.ObjectMeta{} in.Spec.MachineTemplate.InfrastructureRef = corev1.ObjectReference{} } + +func bootstrapv1JoinConfigurationFuzzer(obj *bootstrapv1.JoinConfiguration, c fuzz.Continue) { + c.FuzzNoCustom(obj) + + // JoinConfiguration.Discovery.File.KubeConfig is internal to Cluster API and does not exist in kubeadm v1beta1 types, pinning it to avoid round trip errors. + if obj.Discovery.File != nil { + obj.Discovery.File.KubeConfig = nil + } +}