Skip to content

Commit

Permalink
Allow CAPBK to generate JoinConfiguration discovery kubeconfig
Browse files Browse the repository at this point in the history
Signed-off-by: Vince Prignano <vince@prigna.com>
  • Loading branch information
vincepri committed Jun 26, 2024
1 parent 1e6896e commit fd41ad6
Show file tree
Hide file tree
Showing 14 changed files with 1,305 additions and 45 deletions.
24 changes: 24 additions & 0 deletions bootstrap/kubeadm/api/v1beta1/kubeadm_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
)
Expand Down Expand Up @@ -512,6 +513,29 @@ 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"`

// Config is used (optionally) to generate a Kubeconfig based on the Cluster's information.
// The kubeconfig is generated with a server and context matching the Cluster's name,
// 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
Config *FileDiscoveryConfig `json:"config,omitempty"`
}

// FileDiscoveryConfig contains elements describing how to generate the kubeconfig for bootstrapping.
type FileDiscoveryConfig struct {
// Cluster contains information about how to communicate with the kubernetes cluster.
//
// By default the following fields are automatically populated:
// - Name with the Cluster's name.
// - Server with the Cluster's ControlPlaneEndpoint.
// - CertificateAuthorityData with the Cluster's CA certificate.
// +optional
Cluster *clientcmdv1.Cluster `json:"cluster,omitempty"`

// User contains information that describes identity information.
// This is use to tell the kubernetes cluster who you are.
User clientcmdv1.AuthInfo `json:"user"`
}

// HostPathMount contains elements describing volumes that are mounted from the
Expand Down
29 changes: 28 additions & 1 deletion bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -741,6 +743,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.Config != nil {
kubeconfig, err := r.resolveDiscoveryConfig(ctx, 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,
Expand Down Expand Up @@ -842,6 +853,47 @@ func (r *KubeadmConfigReconciler) resolveUsers(ctx context.Context, cfg *bootstr
return collected, nil
}

func (r *KubeadmConfigReconciler) resolveDiscoveryConfig(ctx context.Context, config *bootstrapv1.KubeadmConfig) (*bootstrapv1.File, error) {
cfg := config.Spec.JoinConfiguration.Discovery.File
if cfg == nil || cfg.Config == nil {
return nil, errors.New("no discovery configuration file to resolve")
}
kubeconfig := clientcmdv1.Config{
CurrentContext: "default",
Contexts: []clientcmdv1.NamedContext{
{
Name: "default",
Context: clientcmdv1.Context{
Cluster: "default",
AuthInfo: "default",
},
},
},
Clusters: []clientcmdv1.NamedCluster{
{
Name: "default",
Cluster: *cfg.Config.Cluster,
},
},
AuthInfos: []clientcmdv1.NamedAuthInfo{
{
Name: "default",
AuthInfo: cfg.Config.User,
},
},
}
b, err := yaml.Marshal(kubeconfig)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal kubeconfig from JoinConfiguration.Discovery.File.Config")
}
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{}
Expand Down Expand Up @@ -970,7 +1022,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
Expand Down Expand Up @@ -1026,6 +1078,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.Config
if config.Spec.JoinConfiguration.Discovery.File.Config == nil {
// Nothing else to do.
return ctrl.Result{}, nil
}

if cfg.Cluster == nil {
cfg.Cluster = &clientcmdv1.Cluster{}
}

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 = cluster.Spec.ControlPlaneEndpoint.String()
log.V(3).Info("Altering JoinConfiguration.Discovery.File.Config.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.Config.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) {
Expand Down
5 changes: 5 additions & 0 deletions bootstrap/kubeadm/types/upstreamv1beta2/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.Config 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)
}
36 changes: 24 additions & 12 deletions bootstrap/kubeadm/types/upstreamv1beta2/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions bootstrap/kubeadm/types/upstreamv1beta3/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.Config 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)
}
36 changes: 24 additions & 12 deletions bootstrap/kubeadm/types/upstreamv1beta3/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions bootstrap/kubeadm/types/upstreamv1beta4/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.Config 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 {
Expand Down
Loading

0 comments on commit fd41ad6

Please sign in to comment.