diff --git a/Dockerfile b/Dockerfile index 1ba5bb6e2..343cc3333 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,16 +27,17 @@ COPY go.sum go.sum RUN go mod download # Copy the go source -COPY cmd/main.go cmd/main.go +COPY cmd/app.go cmd/app.go COPY api/ api/ COPY internal/ internal/ +COPY pkg/ pkg/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. -RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags="${LD_FLAGS}" -a -o manager cmd/main.go +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags="${LD_FLAGS}" -a -o manager cmd/app.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/Makefile b/Makefile index 561d85d38..4c1d45bf0 100644 --- a/Makefile +++ b/Makefile @@ -181,11 +181,11 @@ LD_FLAGS += -X github.com/Mirantis/hmc/internal/telemetry.segmentToken=$(SEGMENT .PHONY: build build: generate-all fmt vet ## Build manager binary. - go build -ldflags="${LD_FLAGS}" -o bin/manager cmd/main.go + go build -ldflags="${LD_FLAGS}" -o bin/manager cmd/app.go .PHONY: run run: generate-all fmt vet ## Run a controller from your host. - go run ./cmd/main.go + go run ./cmd/app.go # If you wish to build the manager image targeting other platforms you can use the --platform flag. # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go index 16f3ef3e4..1929e19ee 100644 --- a/api/v1alpha1/common.go +++ b/api/v1alpha1/common.go @@ -42,11 +42,6 @@ type ( ) const ( - // Provider CAPA - ProviderCAPAName = "cluster-api-provider-aws" - // Provider Azure - ProviderAzureName = "cluster-api-provider-azure" - ProviderVSphereName = "cluster-api-provider-vsphere" // Provider K0smotron ProviderK0smotronName = "k0smotron" // Provider Sveltos diff --git a/api/v1alpha1/management_types.go b/api/v1alpha1/management_types.go index 850450c35..9abe83b15 100644 --- a/api/v1alpha1/management_types.go +++ b/api/v1alpha1/management_types.go @@ -90,6 +90,10 @@ type Provider struct { Name string `json:"name"` } +func (p Provider) String() string { + return p.Name +} + func (in *Component) HelmValues() (values map[string]any, err error) { if in.Config != nil { err = yaml.Unmarshal(in.Config.Raw, &values) @@ -97,16 +101,6 @@ func (in *Component) HelmValues() (values map[string]any, err error) { return values, err } -func GetDefaultProviders() []Provider { - return []Provider{ - {Name: ProviderK0smotronName}, - {Name: ProviderCAPAName}, - {Name: ProviderAzureName}, - {Name: ProviderVSphereName}, - {Name: ProviderSveltosName}, - } -} - // Templates returns a list of provider templates explicitly defined in the Management object func (in *Management) Templates() []string { templates := []string{} diff --git a/cmd/app.go b/cmd/app.go new file mode 100644 index 000000000..960e86b2e --- /dev/null +++ b/cmd/app.go @@ -0,0 +1,29 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "os" + + "github.com/Mirantis/hmc/pkg/manager" + _ "github.com/Mirantis/hmc/pkg/providers/azure" + _ "github.com/Mirantis/hmc/pkg/providers/vsphere" +) + +func main() { + if err := manager.Main(); err != nil { + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index b96923d15..c5ec36ee7 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/fluxcd/pkg/apis/meta v1.8.0 github.com/fluxcd/pkg/runtime v0.50.1 github.com/fluxcd/source-controller/api v1.4.1 + github.com/go-logr/logr v1.4.2 github.com/google/uuid v1.6.0 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/onsi/ginkgo/v2 v2.22.0 @@ -84,7 +85,6 @@ require ( github.com/go-errors/errors v1.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-ldap/ldap/v3 v3.4.8 // indirect - github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect diff --git a/internal/controller/managedcluster_controller.go b/internal/controller/managedcluster_controller.go index 3dcc43ade..f213022f6 100644 --- a/internal/controller/managedcluster_controller.go +++ b/internal/controller/managedcluster_controller.go @@ -48,11 +48,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" hmc "github.com/Mirantis/hmc/api/v1alpha1" - "github.com/Mirantis/hmc/internal/credspropagation" "github.com/Mirantis/hmc/internal/helm" "github.com/Mirantis/hmc/internal/sveltos" "github.com/Mirantis/hmc/internal/telemetry" "github.com/Mirantis/hmc/internal/utils/status" + "github.com/Mirantis/hmc/pkg/credspropagation" + providersloader "github.com/Mirantis/hmc/pkg/providers" ) const ( @@ -578,35 +579,16 @@ func (r *ManagedClusterReconciler) releaseCluster(ctx context.Context, namespace return err } - var ( - gvkAWSCluster = schema.GroupVersionKind{ - Group: "infrastructure.cluster.x-k8s.io", - Version: "v1beta2", - Kind: "AWSCluster", - } - - gvkAzureCluster = schema.GroupVersionKind{ - Group: "infrastructure.cluster.x-k8s.io", - Version: "v1beta1", - Kind: "AzureCluster", - } - - gvkMachine = schema.GroupVersionKind{ - Group: "cluster.x-k8s.io", - Version: "v1beta1", - Kind: "Machine", - } - ) - - providerGVKs := map[string]schema.GroupVersionKind{ - "aws": gvkAWSCluster, - "azure": gvkAzureCluster, + gvkMachine := schema.GroupVersionKind{ + Group: "cluster.x-k8s.io", + Version: "v1beta1", + Kind: "Machine", } // Associate the provider with it's GVK for _, provider := range providers { - gvk, ok := providerGVKs[provider] - if !ok { + gvk := providersloader.GetClusterGVK(provider) + if !gvk.Empty() { continue } @@ -640,13 +622,12 @@ func (r *ManagedClusterReconciler) getInfraProvidersNames(ctx context.Context, t return nil, err } - const infraPrefix = "infrastructure-" var ( ips = make([]string, 0, len(template.Status.Providers)) - lprefix = len(infraPrefix) + lprefix = len(providersloader.InfraPrefix) ) for _, v := range template.Status.Providers { - if idx := strings.Index(v, infraPrefix); idx > -1 { + if idx := strings.Index(v, providersloader.InfraPrefix); idx > -1 { ips = append(ips, v[idx+lprefix:]) } } @@ -722,54 +703,37 @@ func (r *ManagedClusterReconciler) reconcileCredentialPropagation(ctx context.Co } for _, provider := range providers { - switch provider { - case "aws": - l.Info("Skipping creds propagation for AWS") - case "azure": - l.Info("Azure creds propagation start") - if err := credspropagation.PropagateAzureSecrets(ctx, propnCfg); err != nil { - errMsg := fmt.Sprintf("failed to create Azure CCM credentials: %s", err) - apimeta.SetStatusCondition(managedCluster.GetConditions(), metav1.Condition{ - Type: hmc.CredentialsPropagatedCondition, - Status: metav1.ConditionFalse, - Reason: hmc.FailedReason, - Message: errMsg, - }) - - return errors.New(errMsg) - } + titleName := providersloader.GetProviderTitleName(provider) + f, ok := providersloader.CredentialPropagationFunc(provider) + if !ok || titleName == "" { apimeta.SetStatusCondition(managedCluster.GetConditions(), metav1.Condition{ Type: hmc.CredentialsPropagatedCondition, - Status: metav1.ConditionTrue, - Reason: hmc.SucceededReason, - Message: "Azure CCM credentials created", + Status: metav1.ConditionFalse, + Reason: hmc.FailedReason, + Message: "unsupported infrastructure provider " + provider, }) - case "vsphere": - l.Info("vSphere creds propagation start") - if err := credspropagation.PropagateVSphereSecrets(ctx, propnCfg); err != nil { - errMsg := fmt.Sprintf("failed to create vSphere CCM credentials: %s", err) - apimeta.SetStatusCondition(managedCluster.GetConditions(), metav1.Condition{ - Type: hmc.CredentialsPropagatedCondition, - Status: metav1.ConditionFalse, - Reason: hmc.FailedReason, - Message: errMsg, - }) - return errors.New(errMsg) - } + continue + } + + enabled, err := f(ctx, propnCfg, l) + if err != nil { + errMsg := fmt.Sprintf("failed to create %s CCM credentials: %s", titleName, err) apimeta.SetStatusCondition(managedCluster.GetConditions(), metav1.Condition{ Type: hmc.CredentialsPropagatedCondition, - Status: metav1.ConditionTrue, - Reason: hmc.SucceededReason, - Message: "vSphere CCM credentials created", + Status: metav1.ConditionFalse, + Reason: hmc.FailedReason, + Message: errMsg, }) - default: + + return errors.New(errMsg) + } else if enabled { apimeta.SetStatusCondition(managedCluster.GetConditions(), metav1.Condition{ Type: hmc.CredentialsPropagatedCondition, - Status: metav1.ConditionFalse, - Reason: hmc.FailedReason, - Message: "unsupported infrastructure provider " + provider, + Status: metav1.ConditionTrue, + Reason: hmc.SucceededReason, + Message: titleName + " CCM credentials created", }) } } diff --git a/internal/controller/managedcluster_controller_test.go b/internal/controller/managedcluster_controller_test.go index f252e2e7c..507362359 100644 --- a/internal/controller/managedcluster_controller_test.go +++ b/internal/controller/managedcluster_controller_test.go @@ -31,6 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" hmc "github.com/Mirantis/hmc/api/v1alpha1" + providersloader "github.com/Mirantis/hmc/pkg/providers" ) var _ = Describe("ManagedCluster Controller", func() { @@ -97,7 +98,7 @@ var _ = Describe("ManagedCluster Controller", func() { Raw: []byte(`{"foo":"bar"}`), }, }, - Providers: hmc.Providers{"infrastructure-aws"}, + Providers: hmc.Providers{providersloader.InfraPrefix + "aws"}, } Expect(k8sClient.Status().Update(ctx, template)).To(Succeed()) } @@ -144,7 +145,7 @@ var _ = Describe("ManagedCluster Controller", func() { } Expect(k8sClient.Create(ctx, management)).To(Succeed()) management.Status = hmc.ManagementStatus{ - AvailableProviders: hmc.Providers{"infrastructure-aws"}, + AvailableProviders: hmc.Providers{providersloader.InfraPrefix + "aws"}, } Expect(k8sClient.Status().Update(ctx, management)).To(Succeed()) } diff --git a/internal/controller/release_controller.go b/internal/controller/release_controller.go index be19aa876..49b101bac 100644 --- a/internal/controller/release_controller.go +++ b/internal/controller/release_controller.go @@ -48,6 +48,7 @@ import ( "github.com/Mirantis/hmc/internal/build" "github.com/Mirantis/hmc/internal/helm" "github.com/Mirantis/hmc/internal/utils" + "github.com/Mirantis/hmc/pkg/providers" ) // ReleaseReconciler reconciles a Template object @@ -194,7 +195,7 @@ func (r *ReleaseReconciler) ensureManagement(ctx context.Context) error { if err != nil { return err } - mgmtObj.Spec.Providers = hmc.GetDefaultProviders() + mgmtObj.Spec.Providers = providers.List() getter := helm.NewMemoryRESTClientGetter(r.Config, r.RESTMapper()) actionConfig := new(action.Configuration) diff --git a/internal/webhook/managedcluster_webhook.go b/internal/webhook/managedcluster_webhook.go index 71603351f..732e76691 100644 --- a/internal/webhook/managedcluster_webhook.go +++ b/internal/webhook/managedcluster_webhook.go @@ -31,6 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" hmcv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" + providersloader "github.com/Mirantis/hmc/pkg/providers" ) type ManagedClusterValidator struct { @@ -239,7 +240,7 @@ func (v *ManagedClusterValidator) validateCredential(ctx context.Context, manage hasInfra := false for _, v := range template.Status.Providers { - if strings.HasPrefix(v, "infrastructure-") { + if strings.HasPrefix(v, providersloader.InfraPrefix) { hasInfra = true break } @@ -269,25 +270,17 @@ func isCredMatchTemplate(cred *hmcv1alpha1.Credential, template *hmcv1alpha1.Clu } for _, provider := range template.Status.Providers { - switch provider { - case "infrastructure-aws": - if idtyKind != "AWSClusterStaticIdentity" && - idtyKind != "AWSClusterRoleIdentity" && - idtyKind != "AWSClusterControllerIdentity" { - return errMsg(provider) - } - case "infrastructure-azure": - if idtyKind != "AzureClusterIdentity" { - return errMsg(provider) - } - case "infrastructure-vsphere": - if idtyKind != "VSphereClusterIdentity" { - return errMsg(provider) - } - default: - if strings.HasPrefix(provider, "infrastructure-") { + idtys, found := providersloader.GetClusterIdentityKind(provider) + if !found { + if strings.HasPrefix(provider, providersloader.InfraPrefix) { return fmt.Errorf("unsupported infrastructure provider %s", provider) } + + continue + } + + if !slices.Contains(idtys, idtyKind) { + return errMsg(provider) } } diff --git a/internal/webhook/managedcluster_webhook_test.go b/internal/webhook/managedcluster_webhook_test.go index 9e9986727..907ca6116 100644 --- a/internal/webhook/managedcluster_webhook_test.go +++ b/internal/webhook/managedcluster_webhook_test.go @@ -27,6 +27,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/Mirantis/hmc/api/v1alpha1" + providersloader "github.com/Mirantis/hmc/pkg/providers" "github.com/Mirantis/hmc/test/objects/credential" "github.com/Mirantis/hmc/test/objects/managedcluster" "github.com/Mirantis/hmc/test/objects/management" @@ -43,7 +44,7 @@ var ( mgmt = management.NewManagement( management.WithAvailableProviders(v1alpha1.Providers{ - "infrastructure-aws", + providersloader.InfraPrefix + "aws", "control-plane-k0smotron", "bootstrap-k0smotron", }), @@ -110,7 +111,7 @@ func TestManagedClusterValidateCreate(t *testing.T) { template.NewClusterTemplate( template.WithName(testTemplateName), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -155,7 +156,7 @@ func TestManagedClusterValidateCreate(t *testing.T) { template.NewClusterTemplate( template.WithName(testTemplateName), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -184,7 +185,7 @@ func TestManagedClusterValidateCreate(t *testing.T) { template.NewClusterTemplate( template.WithName(testTemplateName), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -205,7 +206,7 @@ func TestManagedClusterValidateCreate(t *testing.T) { existingObjects: []runtime.Object{ cred, management.NewManagement(management.WithAvailableProviders(v1alpha1.Providers{ - "infrastructure-aws", + providersloader.InfraPrefix + "aws", "control-plane-k0smotron", "bootstrap-k0smotron", })), @@ -231,7 +232,7 @@ func TestManagedClusterValidateCreate(t *testing.T) { template.NewClusterTemplate( template.WithName(testTemplateName), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -260,7 +261,7 @@ func TestManagedClusterValidateCreate(t *testing.T) { template.NewClusterTemplate( template.WithName(testTemplateName), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -276,10 +277,18 @@ func TestManagedClusterValidateCreate(t *testing.T) { managedcluster.WithCredential(testCredentialName), ), existingObjects: []runtime.Object{ - cred, + credential.NewCredential( + credential.WithName(testCredentialName), + credential.WithReady(true), + credential.WithIdentityRef( + &corev1.ObjectReference{ + Kind: "SomeOtherDummyClusterStaticIdentity", + Name: "otherdummyclid", + }), + ), management.NewManagement( management.WithAvailableProviders(v1alpha1.Providers{ - "infrastructure-azure", + providersloader.InfraPrefix + "aws", "control-plane-k0smotron", "bootstrap-k0smotron", }), @@ -287,14 +296,14 @@ func TestManagedClusterValidateCreate(t *testing.T) { template.NewClusterTemplate( template.WithName(testTemplateName), template.WithProvidersStatus( - "infrastructure-azure", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), ), }, - err: "the ManagedCluster is invalid: wrong kind of the ClusterIdentity \"AWSClusterStaticIdentity\" for provider \"infrastructure-azure\"", + err: "the ManagedCluster is invalid: wrong kind of the ClusterIdentity \"SomeOtherDummyClusterStaticIdentity\" for provider \"infrastructure-aws\"", }, } for _, tt := range tests { @@ -374,7 +383,7 @@ func TestManagedClusterValidateUpdate(t *testing.T) { template.WithName(testTemplateName), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -383,7 +392,7 @@ func TestManagedClusterValidateUpdate(t *testing.T) { template.WithName(upgradeTargetTemplateName), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -409,7 +418,7 @@ func TestManagedClusterValidateUpdate(t *testing.T) { template.WithName(testTemplateName), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -418,7 +427,7 @@ func TestManagedClusterValidateUpdate(t *testing.T) { template.WithName(newTemplateName), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -447,7 +456,7 @@ func TestManagedClusterValidateUpdate(t *testing.T) { ValidationError: "validation error example", }), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -477,7 +486,7 @@ func TestManagedClusterValidateUpdate(t *testing.T) { ValidationError: "validation error example", }), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -511,7 +520,7 @@ func TestManagedClusterValidateUpdate(t *testing.T) { ValidationError: "validation error example", }), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -545,7 +554,7 @@ func TestManagedClusterValidateUpdate(t *testing.T) { ValidationError: "validation error example", }), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), @@ -581,7 +590,7 @@ func TestManagedClusterValidateUpdate(t *testing.T) { ValidationError: "validation error example", }), template.WithProvidersStatus( - "infrastructure-aws", + providersloader.InfraPrefix+"aws", "control-plane-k0smotron", "bootstrap-k0smotron", ), diff --git a/internal/webhook/management_webhook_test.go b/internal/webhook/management_webhook_test.go index 054bed6ec..2d60917c4 100644 --- a/internal/webhook/management_webhook_test.go +++ b/internal/webhook/management_webhook_test.go @@ -26,6 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/Mirantis/hmc/api/v1alpha1" + providersloader "github.com/Mirantis/hmc/pkg/providers" "github.com/Mirantis/hmc/test/objects/managedcluster" "github.com/Mirantis/hmc/test/objects/management" "github.com/Mirantis/hmc/test/objects/release" @@ -99,14 +100,14 @@ func TestManagementValidateUpdate(t *testing.T) { capiVersion = "v1beta1" capiVersionOther = "v1alpha3" - infraAWSProvider = "infrastructure-aws" - infraOtherProvider = "infrastructure-other-provider" + infraAWSProvider = providersloader.InfraPrefix + "aws" + infraOtherProvider = providersloader.InfraPrefix + "other-provider" ) validStatus := v1alpha1.TemplateValidationStatus{Valid: true} componentAwsDefaultTpl := v1alpha1.Provider{ - Name: v1alpha1.ProviderCAPAName, + Name: providersloader.ProviderPrefix + "aws", Component: v1alpha1.Component{ Template: template.DefaultName, }, diff --git a/internal/credspropagation/common.go b/pkg/credspropagation/credspropagation.go similarity index 76% rename from internal/credspropagation/common.go rename to pkg/credspropagation/credspropagation.go index 9d72e5759..d877e613d 100644 --- a/internal/credspropagation/common.go +++ b/pkg/credspropagation/credspropagation.go @@ -35,8 +35,9 @@ type PropagationCfg struct { SystemNamespace string } -func applyCCMConfigs(ctx context.Context, kubeconfSecret *corev1.Secret, objects ...client.Object) error { - clnt, err := makeClientFromSecret(kubeconfSecret) +// ApplyCCMConfigs applies Cloud Controller Manager configurations using the provided kubeconfig secret +func ApplyCCMConfigs(ctx context.Context, kubeconfSecret *corev1.Secret, objects ...client.Object) error { + clnt, err := MakeClientFromSecret(kubeconfSecret) if err != nil { return fmt.Errorf("failed to create k8s client: %w", err) } @@ -53,7 +54,8 @@ func applyCCMConfigs(ctx context.Context, kubeconfSecret *corev1.Secret, objects return nil } -func makeSecret(name, namespace string, data map[string][]byte) *corev1.Secret { +// MakeSecret creates a new Secret resource with the specified name, namespace, and data +func MakeSecret(name, namespace string, data map[string][]byte) *corev1.Secret { s := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -65,7 +67,8 @@ func makeSecret(name, namespace string, data map[string][]byte) *corev1.Secret { return s } -func makeConfigMap(name, namespace string, data map[string]string) *corev1.ConfigMap { +// MakeConfigMap creates a new ConfigMap resource with the specified name, namespace, and data +func MakeConfigMap(name, namespace string, data map[string]string) *corev1.ConfigMap { c := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -77,7 +80,8 @@ func makeConfigMap(name, namespace string, data map[string]string) *corev1.Confi return c } -func makeClientFromSecret(kubeconfSecret *corev1.Secret) (client.Client, error) { +// MakeClientFromSecret creates a new Kubernetes client using the provided kubeconfig secret +func MakeClientFromSecret(kubeconfSecret *corev1.Secret) (client.Client, error) { scheme := runtime.NewScheme() if err := clientgoscheme.AddToScheme(scheme); err != nil { return nil, err diff --git a/cmd/main.go b/pkg/manager/manager.go similarity index 92% rename from cmd/main.go rename to pkg/manager/manager.go index 35c8e2c2a..55dd88474 100644 --- a/cmd/main.go +++ b/pkg/manager/manager.go @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package main +package manager import ( "crypto/tls" "flag" + "fmt" "os" + "strings" hcv2 "github.com/fluxcd/helm-controller/api/v2" sourcev1 "github.com/fluxcd/source-controller/api/v1" @@ -26,7 +28,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/dynamic" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - _ "k8s.io/client-go/plugin/pkg/client/auth" + _ "k8s.io/client-go/plugin/pkg/client/auth" // k8s client auth for CLI application capz "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" capv "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1" ctrl "sigs.k8s.io/controller-runtime" @@ -36,11 +38,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" hmcmirantiscomv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" + "github.com/Mirantis/hmc/internal/build" "github.com/Mirantis/hmc/internal/controller" "github.com/Mirantis/hmc/internal/helm" "github.com/Mirantis/hmc/internal/telemetry" "github.com/Mirantis/hmc/internal/utils" hmcwebhook "github.com/Mirantis/hmc/internal/webhook" + "github.com/Mirantis/hmc/pkg/providers" ) var ( @@ -60,7 +64,7 @@ func init() { // +kubebuilder:scaffold:scheme } -func main() { +func Main() error { //nolint:maintidx // Cyclomatic Complexity is what it is, effective `main` of CLI application var ( metricsAddr string probeAddr string @@ -107,6 +111,24 @@ func main() { Development: true, } opts.BindFlags(flag.CommandLine) + + flag.Usage = func() { + var defaultUsage strings.Builder + { + oldOutput := flag.CommandLine.Output() + flag.CommandLine.SetOutput(&defaultUsage) + flag.PrintDefaults() + flag.CommandLine.SetOutput(oldOutput) + } + + _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + _, _ = fmt.Fprint(os.Stderr, defaultUsage.String()) + _, _ = fmt.Fprintf(os.Stderr, "\nSupported providers:\n") + for _, el := range providers.List() { + _, _ = fmt.Fprintf(os.Stderr, " - %s\n", el) + } + _, _ = fmt.Fprintf(os.Stderr, "\nVersion: %s\n", build.Version) + } flag.Parse() ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) @@ -114,7 +136,7 @@ func main() { determinedRepositoryType, err := utils.DetermineDefaultRepositoryType(defaultRegistryURL) if err != nil { setupLog.Error(err, "failed to determine default repository type") - os.Exit(1) + return err } // if the enable-http2 flag is false (the default), http/2 should be disabled @@ -167,19 +189,19 @@ func main() { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), managerOpts) if err != nil { setupLog.Error(err, "unable to start manager") - os.Exit(1) + return err } dc, err := dynamic.NewForConfig(mgr.GetConfig()) if err != nil { setupLog.Error(err, "failed to create dynamic client") - os.Exit(1) + return err } ctx := ctrl.SetupSignalHandler() if err = hmcmirantiscomv1alpha1.SetupIndexers(ctx, mgr); err != nil { setupLog.Error(err, "unable to setup indexers") - os.Exit(1) + return err } currentNamespace := utils.CurrentNamespace() @@ -199,19 +221,19 @@ func main() { TemplateReconciler: templateReconciler, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ClusterTemplate") - os.Exit(1) + return err } if err = (&controller.ServiceTemplateReconciler{ TemplateReconciler: templateReconciler, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ServiceTemplate") - os.Exit(1) + return err } if err = (&controller.ProviderTemplateReconciler{ TemplateReconciler: templateReconciler, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ProviderTemplate") - os.Exit(1) + return err } if err = (&controller.ManagedClusterReconciler{ Client: mgr.GetClient(), @@ -220,7 +242,7 @@ func main() { SystemNamespace: currentNamespace, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ManagedCluster") - os.Exit(1) + return err } if err = (&controller.ManagementReconciler{ Client: mgr.GetClient(), @@ -231,7 +253,7 @@ func main() { CreateAccessManagement: createAccessManagement, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Management") - os.Exit(1) + return err } if err = (&controller.AccessManagementReconciler{ Client: mgr.GetClient(), @@ -239,7 +261,7 @@ func main() { SystemNamespace: currentNamespace, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "AccessManagement") - os.Exit(1) + return err } templateChainReconciler := controller.TemplateChainReconciler{ @@ -250,13 +272,13 @@ func main() { TemplateChainReconciler: templateChainReconciler, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ClusterTemplateChain") - os.Exit(1) + return err } if err = (&controller.ServiceTemplateChainReconciler{ TemplateChainReconciler: templateChainReconciler, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ServiceTemplateChain") - os.Exit(1) + return err } if err = (&controller.ReleaseReconciler{ @@ -275,7 +297,7 @@ func main() { }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Release") - os.Exit(1) + return err } if enableTelemetry { @@ -284,7 +306,7 @@ func main() { SystemNamespace: currentNamespace, }); err != nil { setupLog.Error(err, "unable to create telemetry tracker") - os.Exit(1) + return err } } @@ -292,7 +314,7 @@ func main() { Client: mgr.GetClient(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Credential") - os.Exit(1) + return err } if err = (&controller.MultiClusterServiceReconciler{ @@ -300,7 +322,7 @@ func main() { SystemNamespace: currentNamespace, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "MultiClusterService") - os.Exit(1) + return err } // TODO (zerospiel): disabled until the #605 // if err = (&controller.BackupReconciler{ @@ -308,31 +330,33 @@ func main() { // Scheme: mgr.GetScheme(), // }).SetupWithManager(mgr); err != nil { // setupLog.Error(err, "unable to create controller", "controller", "Backup") - // os.Exit(1) + // return err // } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") - os.Exit(1) + return err } if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") - os.Exit(1) + return err } if enableWebhook { if err := setupWebhooks(mgr, currentNamespace); err != nil { setupLog.Error(err, "failed to setup webhooks") - os.Exit(1) + return err } } setupLog.Info("starting manager") if err := mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") - os.Exit(1) + return err } + + return nil } func setupWebhooks(mgr ctrl.Manager, currentNamespace string) error { diff --git a/pkg/providers/aws/provider.go b/pkg/providers/aws/provider.go new file mode 100644 index 000000000..ebfe37be5 --- /dev/null +++ b/pkg/providers/aws/provider.go @@ -0,0 +1,63 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/Mirantis/hmc/pkg/credspropagation" +) + +// Tier-1 provider, always registered. + +type Provider struct{} + +func (*Provider) GetName() string { + return "aws" +} + +func (*Provider) GetTitleName() string { + return "AWS" +} + +func (*Provider) GetClusterGVK() schema.GroupVersionKind { + return schema.GroupVersionKind{ + Group: "infrastructure.cluster.x-k8s.io", + Version: "v1beta2", + Kind: "AWSCluster", + } +} + +func (*Provider) GetClusterIdentityKinds() []string { + return []string{"AWSClusterStaticIdentity", "AWSClusterRoleIdentity", "AWSClusterControllerIdentity"} +} + +func (p *Provider) CredentialPropagationFunc() func( + ctx context.Context, + propnCfg *credspropagation.PropagationCfg, + l logr.Logger, +) (enabled bool, err error) { + return func( + _ context.Context, + _ *credspropagation.PropagationCfg, + l logr.Logger, + ) (enabled bool, err error) { + l.Info("Skipping creds propagation for " + p.GetTitleName()) + return enabled, err + } +} diff --git a/internal/credspropagation/azure.go b/pkg/providers/azure/creds.go similarity index 90% rename from internal/credspropagation/azure.go rename to pkg/providers/azure/creds.go index 05eb6973a..d83819240 100644 --- a/internal/credspropagation/azure.go +++ b/pkg/providers/azure/creds.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package credspropagation +package azure import ( "context" @@ -23,9 +23,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capz "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/Mirantis/hmc/pkg/credspropagation" ) -func PropagateAzureSecrets(ctx context.Context, cfg *PropagationCfg) error { +func PropagateAzureSecrets(ctx context.Context, cfg *credspropagation.PropagationCfg) error { azureCluster := &capz.AzureCluster{} if err := cfg.Client.Get(ctx, client.ObjectKey{ Name: cfg.ManagedCluster.Name, @@ -55,7 +57,7 @@ func PropagateAzureSecrets(ctx context.Context, cfg *PropagationCfg) error { return fmt.Errorf("failed to generate Azure CCM secret: %w", err) } - if err := applyCCMConfigs(ctx, cfg.KubeconfSecret, ccmSecret); err != nil { + if err := credspropagation.ApplyCCMConfigs(ctx, cfg.KubeconfSecret, ccmSecret); err != nil { return fmt.Errorf("failed to apply Azure CCM secret: %w", err) } @@ -92,5 +94,5 @@ func generateAzureCCMSecret(azureCluster *capz.AzureCluster, azureClIdty *capz.A "cloud-config": azureJSON, } - return makeSecret("azure-cloud-provider", metav1.NamespaceSystem, secretData), nil + return credspropagation.MakeSecret("azure-cloud-provider", metav1.NamespaceSystem, secretData), nil } diff --git a/pkg/providers/azure/provider.go b/pkg/providers/azure/provider.go new file mode 100644 index 000000000..d93e2a5d7 --- /dev/null +++ b/pkg/providers/azure/provider.go @@ -0,0 +1,69 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package azure + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/Mirantis/hmc/pkg/credspropagation" + "github.com/Mirantis/hmc/pkg/providers" +) + +type Provider struct{} + +var _ providers.ProviderModule = (*Provider)(nil) + +func init() { + providers.Register(&Provider{}) +} + +func (*Provider) GetName() string { + return "azure" +} + +func (*Provider) GetTitleName() string { + return "Azure" +} + +func (*Provider) GetClusterGVK() schema.GroupVersionKind { + return schema.GroupVersionKind{ + Group: "infrastructure.cluster.x-k8s.io", + Version: "v1beta1", + Kind: "AzureCluster", + } +} + +func (*Provider) GetClusterIdentityKinds() []string { + return []string{"AzureClusterIdentity"} +} + +func (p *Provider) CredentialPropagationFunc() func( + ctx context.Context, + propnCfg *credspropagation.PropagationCfg, + l logr.Logger, +) (enabled bool, err error) { + return func( + ctx context.Context, + propnCfg *credspropagation.PropagationCfg, + l logr.Logger, + ) (enabled bool, err error) { + l.Info(p.GetTitleName() + " creds propagation start") + enabled, err = true, PropagateAzureSecrets(ctx, propnCfg) + return enabled, err + } +} diff --git a/pkg/providers/providers.go b/pkg/providers/providers.go new file mode 100644 index 000000000..94b808ffe --- /dev/null +++ b/pkg/providers/providers.go @@ -0,0 +1,164 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package providers + +import ( + "context" + "fmt" + "slices" + "strings" + "sync" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime/schema" + + hmc "github.com/Mirantis/hmc/api/v1alpha1" + "github.com/Mirantis/hmc/pkg/credspropagation" + "github.com/Mirantis/hmc/pkg/providers/aws" +) + +const ( + // InfraPrefix is the prefix used for infrastructure provider names + InfraPrefix = "infrastructure-" + // ProviderPrefix is the prefix used for cluster API provider names + ProviderPrefix = "cluster-api-provider-" +) + +var ( + mu sync.RWMutex + + providers = []hmc.Provider{ + { + Name: hmc.ProviderK0smotronName, + }, + { + Name: hmc.ProviderSveltosName, + }, + } + + registry map[string]ProviderModule +) + +type ProviderModule interface { + // GetName returns the short name of the provider + GetName() string + // GetTitleName returns the display title of the provider + GetTitleName() string + // GetClusterGVK returns the GroupVersionKind for the provider's cluster resource + GetClusterGVK() schema.GroupVersionKind + // GetClusterIdentityKinds returns a list of supported cluster identity kinds + GetClusterIdentityKinds() []string + // CredentialPropagationFunc returns a function to handle credential propagation + CredentialPropagationFunc() func( + ctx context.Context, + propnCfg *credspropagation.PropagationCfg, + l logr.Logger, + ) (enabled bool, err error) +} + +// Register adds a new provider module to the registry +func Register(p ProviderModule) { + mu.Lock() + defer mu.Unlock() + + if registry == nil { + registry = make(map[string]ProviderModule) + } + + shortName := p.GetName() + + if _, exists := registry[shortName]; exists { + panic(fmt.Sprintf("provider %q already registered", shortName)) + } + + providers = append(providers, + hmc.Provider{ + Name: ProviderPrefix + p.GetName(), + }, + ) + + registry[shortName] = p +} + +// List returns a copy of all registered providers +func List() []hmc.Provider { + return slices.Clone(providers) +} + +// CredentialPropagationFunc returns the credential propagation function for a given provider +func CredentialPropagationFunc(fullName string) ( + func(ctx context.Context, propnCfg *credspropagation.PropagationCfg, l logr.Logger) (enabled bool, err error), bool, +) { + mu.RLock() + defer mu.RUnlock() + + shortName := strings.TrimPrefix(fullName, ProviderPrefix) + + module, ok := registry[shortName] + if !ok { + return nil, false + } + + f := module.CredentialPropagationFunc() + + return f, f != nil +} + +// GetClusterGVK returns the GroupVersionKind for a provider's cluster resource +func GetClusterGVK(shortName string) schema.GroupVersionKind { + mu.RLock() + defer mu.RUnlock() + + module, ok := registry[shortName] + if !ok { + return schema.GroupVersionKind{} + } + + return module.GetClusterGVK() +} + +// GetClusterIdentityKind returns the supported identity kinds for a given infrastructure provider +func GetClusterIdentityKind(infraName string) ([]string, bool) { + mu.RLock() + defer mu.RUnlock() + + shortName := strings.TrimPrefix(infraName, InfraPrefix) + + module, ok := registry[shortName] + if !ok { + return nil, false + } + + list := slices.Clone(module.GetClusterIdentityKinds()) + + return list, list != nil +} + +// GetProviderTitleName returns the display title for a given provider +func GetProviderTitleName(shortName string) string { + mu.RLock() + defer mu.RUnlock() + + module, ok := registry[shortName] + if !ok { + return "" + } + + return module.GetTitleName() +} + +func init() { + Register(&aws.Provider{}) +} diff --git a/internal/credspropagation/vsphere.go b/pkg/providers/vsphere/creds.go similarity index 89% rename from internal/credspropagation/vsphere.go rename to pkg/providers/vsphere/creds.go index 189a6bd32..39411e692 100644 --- a/internal/credspropagation/vsphere.go +++ b/pkg/providers/vsphere/creds.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package credspropagation +package vsphere import ( "bytes" @@ -28,9 +28,10 @@ import ( "sigs.k8s.io/yaml" hmc "github.com/Mirantis/hmc/api/v1alpha1" + "github.com/Mirantis/hmc/pkg/credspropagation" ) -func PropagateVSphereSecrets(ctx context.Context, cfg *PropagationCfg) error { +func PropagateSecrets(ctx context.Context, cfg *credspropagation.PropagationCfg) error { vsphereCluster := &capv.VSphereCluster{} if err := cfg.Client.Get(ctx, client.ObjectKey{ Name: cfg.ManagedCluster.Name, @@ -77,7 +78,7 @@ func PropagateVSphereSecrets(ctx context.Context, cfg *PropagationCfg) error { return fmt.Errorf("failed to generate VSphere CSI secret: %w", err) } - if err := applyCCMConfigs(ctx, cfg.KubeconfSecret, ccmSecret, ccmConfig, csiSecret); err != nil { + if err := credspropagation.ApplyCCMConfigs(ctx, cfg.KubeconfSecret, ccmSecret, ccmConfig, csiSecret); err != nil { return fmt.Errorf("failed to apply VSphere CCM/CSI secrets: %w", err) } @@ -119,8 +120,8 @@ func generateVSphereCCMConfigs(vCl *capv.VSphereCluster, vScrt *corev1.Secret, v cmData := map[string]string{ "vsphere.conf": string(ccmCfgYaml), } - return makeSecret(secretName, metav1.NamespaceSystem, secretData), - makeConfigMap("cloud-config", metav1.NamespaceSystem, cmData), + return credspropagation.MakeSecret(secretName, metav1.NamespaceSystem, secretData), + credspropagation.MakeConfigMap("cloud-config", metav1.NamespaceSystem, cmData), nil } @@ -161,5 +162,5 @@ datacenters = "{{ .Datacenter }}" "csi-vsphere.conf": buf.Bytes(), } - return makeSecret("vcenter-config-secret", metav1.NamespaceSystem, secretData), nil + return credspropagation.MakeSecret("vcenter-config-secret", metav1.NamespaceSystem, secretData), nil } diff --git a/pkg/providers/vsphere/provider.go b/pkg/providers/vsphere/provider.go new file mode 100644 index 000000000..a2da1265b --- /dev/null +++ b/pkg/providers/vsphere/provider.go @@ -0,0 +1,65 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vsphere + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/Mirantis/hmc/pkg/credspropagation" + "github.com/Mirantis/hmc/pkg/providers" +) + +type Provider struct{} + +var _ providers.ProviderModule = (*Provider)(nil) + +func init() { + providers.Register(&Provider{}) +} + +func (*Provider) GetName() string { + return "vsphere" +} + +func (*Provider) GetTitleName() string { + return "vSphere" +} + +func (*Provider) GetClusterGVK() schema.GroupVersionKind { + return schema.GroupVersionKind{} +} + +func (*Provider) GetClusterIdentityKinds() []string { + return []string{"VSphereClusterIdentity"} +} + +func (p *Provider) CredentialPropagationFunc() func( + ctx context.Context, + propnCfg *credspropagation.PropagationCfg, + l logr.Logger, +) (enabled bool, err error) { + return func( + ctx context.Context, + propnCfg *credspropagation.PropagationCfg, + l logr.Logger, + ) (enabled bool, err error) { + l.Info(p.GetTitleName() + " creds propagation start") + enabled, err = true, PropagateSecrets(ctx, propnCfg) + return enabled, err + } +}