From c1975bb840dc36201acfeb5d68ea91f7724c8594 Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Wed, 31 Jan 2024 13:57:28 -0700 Subject: [PATCH 1/3] rosa: use the openshift/rosa libraries where we can Signed-off-by: Steve Kuznetsov --- .../rosacontrolplane_controller.go | 265 ++++++++---------- exp/controllers/rosamachinepool_controller.go | 44 ++- go.mod | 41 ++- go.sum | 89 +++--- .../autoscalingapi_mock.go | 99 +++++++ pkg/rosa/client.go | 63 +---- pkg/rosa/clusters.go | 77 ----- pkg/rosa/idps.go | 87 ++---- pkg/rosa/nodepools.go | 146 ---------- pkg/rosa/users.go | 58 ---- pkg/rosa/util.go | 60 ---- pkg/rosa/versions.go | 90 +----- test/mocks/aws_ec2api_mock.go | 250 +++++++++++++++++ 13 files changed, 624 insertions(+), 745 deletions(-) delete mode 100644 pkg/rosa/clusters.go delete mode 100644 pkg/rosa/nodepools.go delete mode 100644 pkg/rosa/users.go delete mode 100644 pkg/rosa/util.go diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 34a28ae4c8..5b00a49be8 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -26,7 +26,11 @@ import ( "strings" "time" + idputils "github.com/openshift-online/ocm-common/pkg/idp/utils" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + rosaaws "github.com/openshift/rosa/pkg/aws" + "github.com/openshift/rosa/pkg/ocm" + "github.com/zgalor/weberr" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,6 +39,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" "k8s.io/klog/v2" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -57,8 +62,6 @@ import ( ) const ( - rosaCreatorArnProperty = "rosa_creator_arn" - rosaControlPlaneKind = "ROSAControlPlane" // ROSAControlPlaneFinalizer allows the controller to clean up resources on delete. ROSAControlPlaneFinalizer = "rosacontrolplane.controlplane.cluster.x-k8s.io" @@ -185,31 +188,34 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc } } - rosaClient, err := rosa.NewRosaClient(ctx, rosaScope) + ocmClient, err := rosa.NewOCMClient(ctx, rosaScope) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to create a rosa client: %w", err) + // TODO: need to expose in status, as likely the credentials are invalid + return ctrl.Result{}, fmt.Errorf("failed to create OCM client: %w", err) } - defer rosaClient.Close() - failureMessage, err := validateControlPlaneSpec(rosaClient, rosaScope) + creator, err := rosaaws.CreatorForCallerIdentity(rosaScope.Identity) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to validate ROSAControlPlane.spec: %w", err) + return ctrl.Result{}, fmt.Errorf("failed to transform caller identity to creator: %w", err) } - if failureMessage != nil { - rosaScope.ControlPlane.Status.FailureMessage = failureMessage + + if validationMessage, validationError := validateControlPlaneSpec(ocmClient, rosaScope); validationError != nil { + return ctrl.Result{}, fmt.Errorf("validate ROSAControlPlane.spec: %w", err) + } else if validationMessage != "" { + rosaScope.ControlPlane.Status.FailureMessage = ptr.To(validationMessage) // dont' requeue because input is invalid and manual intervention is needed. return ctrl.Result{}, nil } else { rosaScope.ControlPlane.Status.FailureMessage = nil } - cluster, err := rosaClient.GetCluster() - if err != nil { + cluster, err := ocmClient.GetCluster(rosaScope.ControlPlane.Spec.RosaClusterName, creator) + if weberr.GetType(err) != weberr.NotFound && err != nil { return ctrl.Result{}, err } - if clusterID := cluster.ID(); clusterID != "" { - rosaScope.ControlPlane.Status.ID = &clusterID + if cluster != nil { + rosaScope.ControlPlane.Status.ID = ptr.To(cluster.ID()) rosaScope.ControlPlane.Status.ConsoleURL = cluster.Console().URL() rosaScope.ControlPlane.Status.OIDCEndpointURL = cluster.AWS().STS().OIDCEndpointURL() rosaScope.ControlPlane.Status.Ready = false @@ -225,10 +231,10 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc } rosaScope.ControlPlane.Spec.ControlPlaneEndpoint = *apiEndpoint - if err := r.reconcileKubeconfig(ctx, rosaScope, rosaClient, cluster); err != nil { + if err := r.reconcileKubeconfig(ctx, rosaScope, ocmClient, cluster); err != nil { return ctrl.Result{}, fmt.Errorf("failed to reconcile kubeconfig: %w", err) } - if err := r.reconcileClusterVersion(rosaScope, rosaClient, cluster); err != nil { + if err := r.reconcileClusterVersion(rosaScope, ocmClient, cluster); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -256,125 +262,88 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc return ctrl.Result{RequeueAfter: time.Second * 60}, nil } - // Create the cluster: - clusterBuilder := cmv1.NewCluster(). - Name(rosaScope.RosaClusterName()). - MultiAZ(true). - Product( - cmv1.NewProduct(). - ID("rosa"), - ). - Region( - cmv1.NewCloudRegion(). - ID(*rosaScope.ControlPlane.Spec.Region), - ). - FIPS(false). - EtcdEncryption(false). - DisableUserWorkloadMonitoring(true). - Version( - cmv1.NewVersion(). - ID(rosa.VersionID(rosaScope.ControlPlane.Spec.Version)). - ChannelGroup("stable"), - ). - ExpirationTimestamp(time.Now().Add(1 * time.Hour)). - Hypershift(cmv1.NewHypershift().Enabled(true)) - - networkBuilder := cmv1.NewNetwork() - networkBuilder = networkBuilder.Type("OVNKubernetes") - networkBuilder = networkBuilder.MachineCIDR(*rosaScope.ControlPlane.Spec.MachineCIDR) - clusterBuilder = clusterBuilder.Network(networkBuilder) - - stsBuilder := cmv1.NewSTS().RoleARN(*rosaScope.ControlPlane.Spec.InstallerRoleARN) - // stsBuilder = stsBuilder.ExternalID(config.ExternalID) - stsBuilder = stsBuilder.SupportRoleARN(*rosaScope.ControlPlane.Spec.SupportRoleARN) - roles := []*cmv1.OperatorIAMRoleBuilder{} - for _, role := range []struct { - Name string - Namespace string - RoleARN string - Path string - }{ - { - Name: "cloud-credentials", - Namespace: "openshift-ingress-operator", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.IngressARN, - }, - { - Name: "installer-cloud-credentials", - Namespace: "openshift-image-registry", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.ImageRegistryARN, - }, - { - Name: "ebs-cloud-credentials", - Namespace: "openshift-cluster-csi-drivers", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.StorageARN, - }, - { - Name: "cloud-credentials", - Namespace: "openshift-cloud-network-config-controller", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.NetworkARN, - }, - { - Name: "kube-controller-manager", - Namespace: "kube-system", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.KubeCloudControllerARN, - }, - { - Name: "kms-provider", - Namespace: "kube-system", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.KMSProviderARN, - }, - { - Name: "control-plane-operator", - Namespace: "kube-system", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.ControlPlaneOperatorARN, + _, machineCIDR, err := net.ParseCIDR(*rosaScope.ControlPlane.Spec.MachineCIDR) + if err != nil { + // TODO: expose in status, exit reconciliation + rosaScope.Error(err, "rosacontrolplane.spec.machineCIDR invalid") + } + + spec := ocm.Spec{ + Name: rosaScope.RosaClusterName(), + Region: *rosaScope.ControlPlane.Spec.Region, + MultiAZ: true, + Version: ocm.CreateVersionID(rosaScope.ControlPlane.Spec.Version, ocm.DefaultChannelGroup), + ChannelGroup: "stable", + Expiration: time.Now().Add(1 * time.Hour), + DisableWorkloadMonitoring: ptr.To(true), + + SubnetIds: rosaScope.ControlPlane.Spec.Subnets, + AvailabilityZones: rosaScope.ControlPlane.Spec.AvailabilityZones, + NetworkType: "OVNKubernetes", + MachineCIDR: *machineCIDR, + IsSTS: true, + RoleARN: *rosaScope.ControlPlane.Spec.InstallerRoleARN, + SupportRoleARN: *rosaScope.ControlPlane.Spec.SupportRoleARN, + OperatorIAMRoles: []ocm.OperatorIAMRole{ + { + Name: "cloud-credentials", + Namespace: "openshift-ingress-operator", + RoleARN: rosaScope.ControlPlane.Spec.RolesRef.IngressARN, + }, + { + Name: "installer-cloud-credentials", + Namespace: "openshift-image-registry", + RoleARN: rosaScope.ControlPlane.Spec.RolesRef.ImageRegistryARN, + }, + { + Name: "ebs-cloud-credentials", + Namespace: "openshift-cluster-csi-drivers", + RoleARN: rosaScope.ControlPlane.Spec.RolesRef.StorageARN, + }, + { + Name: "cloud-credentials", + Namespace: "openshift-cloud-network-config-controller", + RoleARN: rosaScope.ControlPlane.Spec.RolesRef.NetworkARN, + }, + { + Name: "kube-controller-manager", + Namespace: "kube-system", + RoleARN: rosaScope.ControlPlane.Spec.RolesRef.KubeCloudControllerARN, + }, + { + Name: "kms-provider", + Namespace: "kube-system", + RoleARN: rosaScope.ControlPlane.Spec.RolesRef.KMSProviderARN, + }, + { + Name: "control-plane-operator", + Namespace: "kube-system", + RoleARN: rosaScope.ControlPlane.Spec.RolesRef.ControlPlaneOperatorARN, + }, + { + Name: "capa-controller-manager", + Namespace: "kube-system", + RoleARN: rosaScope.ControlPlane.Spec.RolesRef.NodePoolManagementARN, + }, }, - { - Name: "capa-controller-manager", - Namespace: "kube-system", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.NodePoolManagementARN, + WorkerRoleARN: *rosaScope.ControlPlane.Spec.WorkerRoleARN, + OidcConfigId: *rosaScope.ControlPlane.Spec.OIDCID, + Mode: "auto", + Hypershift: ocm.Hypershift{ + Enabled: true, }, - } { - roles = append(roles, cmv1.NewOperatorIAMRole(). - Name(role.Name). - Namespace(role.Namespace). - RoleARN(role.RoleARN)) - } - stsBuilder = stsBuilder.OperatorIAMRoles(roles...) - - instanceIAMRolesBuilder := cmv1.NewInstanceIAMRoles() - instanceIAMRolesBuilder.WorkerRoleARN(*rosaScope.ControlPlane.Spec.WorkerRoleARN) - stsBuilder = stsBuilder.InstanceIAMRoles(instanceIAMRolesBuilder) - stsBuilder.OidcConfig(cmv1.NewOidcConfig().ID(*rosaScope.ControlPlane.Spec.OIDCID)) - stsBuilder.AutoMode(true) - - awsBuilder := cmv1.NewAWS(). - AccountID(*rosaScope.Identity.Account). - BillingAccountID(*rosaScope.Identity.Account). - SubnetIDs(rosaScope.ControlPlane.Spec.Subnets...). - STS(stsBuilder) - clusterBuilder = clusterBuilder.AWS(awsBuilder) - - clusterNodesBuilder := cmv1.NewClusterNodes() - clusterNodesBuilder = clusterNodesBuilder.AvailabilityZones(rosaScope.ControlPlane.Spec.AvailabilityZones...) - clusterBuilder = clusterBuilder.Nodes(clusterNodesBuilder) - - clusterProperties := map[string]string{} - clusterProperties[rosaCreatorArnProperty] = *rosaScope.Identity.Arn - - clusterBuilder = clusterBuilder.Properties(clusterProperties) - clusterSpec, err := clusterBuilder.Build() - if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to create description of cluster: %v", err) + BillingAccount: *rosaScope.Identity.Account, + AWSCreator: creator, } - newCluster, err := rosaClient.CreateCluster(clusterSpec) + cluster, err = ocmClient.CreateCluster(spec) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to create ROSA cluster: %w", err) + // TODO: need to expose in status, as likely the spec is invalid + return ctrl.Result{}, fmt.Errorf("failed to create OCM cluster: %w", err) } - rosaScope.Info("cluster created", "state", newCluster.Status().State()) - clusterID := newCluster.ID() + rosaScope.Info("cluster created", "state", cluster.Status().State()) + clusterID := cluster.ID() rosaScope.ControlPlane.Status.ID = &clusterID return ctrl.Result{}, nil @@ -383,17 +352,21 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc func (r *ROSAControlPlaneReconciler) reconcileDelete(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (res ctrl.Result, reterr error) { rosaScope.Info("Reconciling ROSAControlPlane delete") - rosaClient, err := rosa.NewRosaClient(ctx, rosaScope) + ocmClient, err := rosa.NewOCMClient(ctx, rosaScope) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to create a rosa client: %w", err) + // TODO: need to expose in status, as likely the credentials are invalid + return ctrl.Result{}, fmt.Errorf("failed to create OCM client: %w", err) } - defer rosaClient.Close() - cluster, err := rosaClient.GetCluster() + creator, err := rosaaws.CreatorForCallerIdentity(rosaScope.Identity) if err != nil { - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("failed to transform caller identity to creator: %w", err) } + cluster, err := ocmClient.GetCluster(rosaScope.ControlPlane.Spec.RosaClusterName, creator) + if weberr.GetType(err) != weberr.NotFound && err != nil { + return ctrl.Result{}, err + } if cluster == nil { // cluster is fully deleted, remove finalizer. controllerutil.RemoveFinalizer(rosaScope.ControlPlane, ROSAControlPlaneFinalizer) @@ -401,7 +374,7 @@ func (r *ROSAControlPlaneReconciler) reconcileDelete(ctx context.Context, rosaSc } if cluster.Status().State() != cmv1.ClusterStateUninstalling { - if err := rosaClient.DeleteCluster(cluster.ID()); err != nil { + if _, err := ocmClient.DeleteCluster(cluster.ID(), true, creator); err != nil { return ctrl.Result{}, err } } @@ -412,20 +385,20 @@ func (r *ROSAControlPlaneReconciler) reconcileDelete(ctx context.Context, rosaSc return ctrl.Result{RequeueAfter: time.Second * 60}, nil } -func (r *ROSAControlPlaneReconciler) reconcileClusterVersion(rosaScope *scope.ROSAControlPlaneScope, rosaClient *rosa.RosaClient, cluster *cmv1.Cluster) error { +func (r *ROSAControlPlaneReconciler) reconcileClusterVersion(rosaScope *scope.ROSAControlPlaneScope, ocmClient *ocm.Client, cluster *cmv1.Cluster) error { version := rosaScope.ControlPlane.Spec.Version if version == rosa.RawVersionID(cluster.Version()) { conditions.MarkFalse(rosaScope.ControlPlane, rosacontrolplanev1.ROSAControlPlaneUpgradingCondition, "upgraded", clusterv1.ConditionSeverityInfo, "") return nil } - scheduledUpgrade, err := rosaClient.CheckExistingScheduledUpgrade(cluster) + scheduledUpgrade, err := rosa.CheckExistingScheduledUpgrade(ocmClient, cluster) if err != nil { return fmt.Errorf("failed to get existing scheduled upgrades: %w", err) } if scheduledUpgrade == nil { - scheduledUpgrade, err = rosaClient.ScheduleControlPlaneUpgrade(cluster, version, time.Now()) + scheduledUpgrade, err = rosa.ScheduleControlPlaneUpgrade(ocmClient, cluster, version, time.Now()) if err != nil { return fmt.Errorf("failed to schedule control plane upgrade to version %s: %w", version, err) } @@ -447,7 +420,7 @@ func (r *ROSAControlPlaneReconciler) reconcileClusterVersion(rosaScope *scope.RO return nil } -func (r *ROSAControlPlaneReconciler) reconcileKubeconfig(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope, rosaClient *rosa.RosaClient, cluster *cmv1.Cluster) error { +func (r *ROSAControlPlaneReconciler) reconcileKubeconfig(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope, ocmClient *ocm.Client, cluster *cmv1.Cluster) error { rosaScope.Debug("Reconciling ROSA kubeconfig for cluster", "cluster-name", rosaScope.RosaClusterName()) clusterRef := client.ObjectKeyFromObject(rosaScope.Cluster) @@ -469,7 +442,7 @@ func (r *ROSAControlPlaneReconciler) reconcileKubeconfig(ctx context.Context, ro apiServerURL := cluster.API().URL() // create new user with admin privileges in the ROSA cluster if 'userName' doesn't already exist. - err = rosaClient.CreateAdminUserIfNotExist(cluster.ID(), userName, password) + err = rosa.CreateAdminUserIfNotExist(ocmClient, cluster.ID(), userName, password) if err != nil { return err } @@ -543,8 +516,8 @@ func (r *ROSAControlPlaneReconciler) reconcileClusterAdminPassword(ctx context.C } else if !apierrors.IsNotFound(err) { return "", fmt.Errorf("failed to get cluster admin password secret: %w", err) } + password, err := idputils.GenerateRandomPassword() // Generate a new password and create the secret - password, err := rosa.GenerateRandomPassword() if err != nil { return "", err } @@ -563,20 +536,18 @@ func (r *ROSAControlPlaneReconciler) reconcileClusterAdminPassword(ctx context.C return password, nil } -func validateControlPlaneSpec(rosaClient *rosa.RosaClient, rosaScope *scope.ROSAControlPlaneScope) (*string, error) { +func validateControlPlaneSpec(ocmClient *ocm.Client, rosaScope *scope.ROSAControlPlaneScope) (string, error) { version := rosaScope.ControlPlane.Spec.Version - isSupported, err := rosaClient.IsVersionSupported(version) + valid, err := ocmClient.ValidateHypershiftVersion(version, "stable") if err != nil { - return nil, fmt.Errorf("failed to verify if version is supported: %w", err) + return "", fmt.Errorf("failed to check if version is valid: %w", err) } - - if !isSupported { - message := fmt.Sprintf("version %s is not supported", version) - return &message, nil + if !valid { + return fmt.Sprintf("version %s is not supported", version), nil } // TODO: add more input validations - return nil, nil + return "", nil } func (r *ROSAControlPlaneReconciler) rosaClusterToROSAControlPlane(log *logger.Logger) handler.MapFunc { diff --git a/exp/controllers/rosamachinepool_controller.go b/exp/controllers/rosamachinepool_controller.go index cb2cf41ec4..6e837b257a 100644 --- a/exp/controllers/rosamachinepool_controller.go +++ b/exp/controllers/rosamachinepool_controller.go @@ -7,6 +7,7 @@ import ( "github.com/blang/semver" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + "github.com/openshift/rosa/pkg/ocm" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -175,11 +176,11 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, } } - rosaClient, err := rosa.NewRosaClient(ctx, rosaControlPlaneScope) + ocmClient, err := rosa.NewOCMClient(ctx, rosaControlPlaneScope) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to create a rosa client: %w", err) + // TODO: need to expose in status, as likely the credentials are invalid + return ctrl.Result{}, fmt.Errorf("failed to create OCM client: %w", err) } - defer rosaClient.Close() failureMessage, err := validateMachinePoolSpec(machinePoolScope) if err != nil { @@ -197,7 +198,7 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, machinePool := machinePoolScope.MachinePool controlPlane := machinePoolScope.ControlPlane - createdNodePool, found, err := rosaClient.GetNodePool(*controlPlane.Status.ID, rosaMachinePool.Spec.NodePoolName) + createdNodePool, found, err := ocmClient.GetNodePool(*controlPlane.Status.ID, rosaMachinePool.Spec.NodePoolName) if err != nil { return ctrl.Result{}, err } @@ -208,7 +209,7 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, conditions.MarkTrue(rosaMachinePool, expinfrav1.RosaMachinePoolReadyCondition) rosaMachinePool.Status.Ready = true - if err := r.reconcileMachinePoolVersion(machinePoolScope, rosaClient, createdNodePool); err != nil { + if err := r.reconcileMachinePoolVersion(machinePoolScope, ocmClient, createdNodePool); err != nil { return ctrl.Result{}, err } @@ -250,7 +251,7 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, npBuilder.AWSNodePool(cmv1.NewAWSNodePool().InstanceType(rosaMachinePool.Spec.InstanceType)) if rosaMachinePool.Spec.Version != "" { - npBuilder.Version(cmv1.NewVersion().ID(rosa.VersionID(rosaMachinePool.Spec.Version))) + npBuilder.Version(cmv1.NewVersion().ID(ocm.CreateVersionID(rosaMachinePool.Spec.Version, ocm.DefaultChannelGroup))) } nodePoolSpec, err := npBuilder.Build() @@ -258,7 +259,7 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, return ctrl.Result{}, fmt.Errorf("failed to build rosa nodepool: %w", err) } - createdNodePool, err = rosaClient.CreateNodePool(*controlPlane.Status.ID, nodePoolSpec) + createdNodePool, err = ocmClient.CreateNodePool(*controlPlane.Status.ID, nodePoolSpec) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create nodepool: %w", err) } @@ -274,18 +275,18 @@ func (r *ROSAMachinePoolReconciler) reconcileDelete( ) error { machinePoolScope.Info("Reconciling deletion of RosaMachinePool") - rosaClient, err := rosa.NewRosaClient(ctx, rosaControlPlaneScope) + ocmClient, err := rosa.NewOCMClient(ctx, rosaControlPlaneScope) if err != nil { - return fmt.Errorf("failed to create a rosa client: %w", err) + // TODO: need to expose in status, as likely the credentials are invalid + return fmt.Errorf("failed to create OCM client: %w", err) } - defer rosaClient.Close() - nodePool, found, err := rosaClient.GetNodePool(*machinePoolScope.ControlPlane.Status.ID, machinePoolScope.NodePoolName()) + nodePool, found, err := ocmClient.GetNodePool(*machinePoolScope.ControlPlane.Status.ID, machinePoolScope.NodePoolName()) if err != nil { return err } if found { - if err := rosaClient.DeleteNodePool(*machinePoolScope.ControlPlane.Status.ID, nodePool.ID()); err != nil { + if err := ocmClient.DeleteNodePool(*machinePoolScope.ControlPlane.Status.ID, nodePool.ID()); err != nil { return err } } @@ -295,7 +296,7 @@ func (r *ROSAMachinePoolReconciler) reconcileDelete( return nil } -func (r *ROSAMachinePoolReconciler) reconcileMachinePoolVersion(machinePoolScope *scope.RosaMachinePoolScope, rosaClient *rosa.RosaClient, nodePool *cmv1.NodePool) error { +func (r *ROSAMachinePoolReconciler) reconcileMachinePoolVersion(machinePoolScope *scope.RosaMachinePoolScope, ocmClient *ocm.Client, nodePool *cmv1.NodePool) error { version := machinePoolScope.RosaMachinePool.Spec.Version if version == "" { version = machinePoolScope.ControlPlane.Spec.Version @@ -307,13 +308,26 @@ func (r *ROSAMachinePoolReconciler) reconcileMachinePoolVersion(machinePoolScope } clusterID := *machinePoolScope.ControlPlane.Status.ID - scheduledUpgrade, err := rosaClient.CheckNodePoolExistingScheduledUpgrade(clusterID, nodePool) + _, scheduledUpgrade, err := ocmClient.GetHypershiftNodePoolUpgrade(clusterID, machinePoolScope.ControlPlane.Spec.RosaClusterName, nodePool.ID()) if err != nil { return fmt.Errorf("failed to get existing scheduled upgrades: %w", err) } if scheduledUpgrade == nil { - scheduledUpgrade, err = rosaClient.ScheduleNodePoolUpgrade(clusterID, nodePool, version, time.Now()) + policy, err := ocmClient.BuildNodeUpgradePolicy(version, nodePool.ID(), ocm.UpgradeScheduling{ + AutomaticUpgrades: false, + // The OCM API places guardrails around the minimum and maximum delay that a user can request, + // for the next run of the upgrade, which is [5min,6mo]. Set our next run request to something + // slightly longer than 5min to make sure we account for the latency between when we send this + // request and when the server processes it. + // https://gitlab.cee.redhat.com/service/uhc-clusters-service/-/blob/master/cmd/clusters-service/servecmd/apiserver/upgrade_policy_handlers.go + NextRun: time.Now().Add(6 * time.Minute), + }) + if err != nil { + return fmt.Errorf("failed to create nodePool upgrade schedule to version %s: %w", version, err) + } + + scheduledUpgrade, err = ocmClient.ScheduleNodePoolUpgrade(clusterID, nodePool.ID(), policy) if err != nil { return fmt.Errorf("failed to schedule nodePool upgrade to version %s: %w", version, err) } diff --git a/go.mod b/go.mod index 046be3cb9f..1b6d6f5b47 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/apparentlymart/go-cidr v1.1.0 github.com/aws/amazon-vpc-cni-k8s v1.15.4 github.com/aws/aws-lambda-go v1.41.0 - github.com/aws/aws-sdk-go v1.44.332 + github.com/aws/aws-sdk-go v1.45.26 github.com/awslabs/goformation/v4 v4.19.5 github.com/blang/semver v3.5.1+incompatible github.com/coreos/ignition v0.35.0 @@ -26,12 +26,16 @@ require ( github.com/google/gofuzz v1.2.0 github.com/onsi/ginkgo/v2 v2.13.1 github.com/onsi/gomega v1.30.0 - github.com/openshift-online/ocm-sdk-go v0.1.388 + github.com/openshift-online/ocm-common v0.0.0-20240129111424-ff8c6c11d909 + github.com/openshift-online/ocm-sdk-go v0.1.403 + github.com/openshift/rosa v1.2.35-rc1.0.20240221134836-0beb882e5836 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/client_golang v1.18.0 github.com/sergi/go-diff v1.3.1 + github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace + github.com/zgalor/weberr v0.6.0 golang.org/x/crypto v0.18.0 golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 @@ -64,9 +68,13 @@ require ( github.com/adrg/xdg v0.4.0 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect + github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect + github.com/aws/aws-sdk-go-v2/service/iam v1.27.1 // indirect + github.com/aws/smithy-go v1.19.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect + github.com/briandowns/spinner v1.11.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect @@ -93,6 +101,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.4 // indirect @@ -103,7 +112,7 @@ require ( github.com/gobuffalo/flect v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/glog v1.1.2 // indirect + github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect @@ -115,11 +124,12 @@ require ( github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/gorilla/css v1.0.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.13 // indirect @@ -135,8 +145,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/microcosm-cc/bluemonday v1.0.18 // indirect + github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -155,9 +164,9 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.46.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect @@ -165,6 +174,7 @@ require ( github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522 // indirect github.com/shopspring/decimal v1.3.1 // indirect + github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect @@ -174,6 +184,7 @@ require ( github.com/valyala/fastjson v1.6.4 // indirect github.com/vincent-petithory/dataurl v1.0.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect + gitlab.com/c0b/go-ordered-json v0.0.0-20171130231205-49bbdab258c2 // indirect go.etcd.io/etcd/api/v3 v3.5.10 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect go.etcd.io/etcd/client/v3 v3.5.10 // indirect @@ -190,20 +201,20 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/oauth2 v0.14.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.16.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index 95c736aaff..f8c19bbd43 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,14 @@ github.com/aws/amazon-vpc-cni-k8s v1.15.4 h1:eF4YcX+BvQGg73MzCaar5FoZNxe3sTokYhF github.com/aws/amazon-vpc-cni-k8s v1.15.4/go.mod h1:eVzV7+2QctvKc+yyr3kLNHFwb9xZQRKl0C8ki4ObzDw= github.com/aws/aws-lambda-go v1.41.0 h1:l/5fyVb6Ud9uYd411xdHZzSf2n86TakxzpvIoz7l+3Y= github.com/aws/aws-lambda-go v1.41.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM= -github.com/aws/aws-sdk-go v1.44.332 h1:Ze+98F41+LxoJUdsisAFThV+0yYYLYw17/Vt0++nFYM= -github.com/aws/aws-sdk-go v1.44.332/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.26 h1:PJ2NJNY5N/yeobLYe1Y+xLdavBi67ZI8gvph6ftwVCg= +github.com/aws/aws-sdk-go v1.45.26/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= +github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2/service/iam v1.27.1 h1:rPkEOnwPOVop34lpAlA4Dv6x67Ys3moXkPDvBfjgSSo= +github.com/aws/aws-sdk-go-v2/service/iam v1.27.1/go.mod h1:qdQ8NUrhmXE80S54w+LrtHUY+1Fp7cQSRZbJUZKrAcU= +github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= +github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/awslabs/goformation/v4 v4.19.5 h1:Y+Tzh01tWg8gf//AgGKUamaja7Wx9NPiJf1FpZu4/iU= github.com/awslabs/goformation/v4 v4.19.5/go.mod h1:JoNpnVCBOUtEz9bFxc9sjy8uBUCLF5c4D1L7RhRTVM8= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= @@ -95,6 +101,8 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/briandowns/spinner v1.11.1 h1:OixPqDEcX3juo5AjQZAnFPbeUA0jvkp2qzB5gOZJ/L0= +github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= @@ -178,6 +186,7 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwC github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -190,6 +199,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -223,8 +234,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -316,13 +327,13 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= @@ -335,6 +346,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -404,8 +417,10 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -413,10 +428,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo= -github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -466,8 +479,12 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= -github.com/openshift-online/ocm-sdk-go v0.1.388 h1:c8yPCUQwJm3QhcVmnyMPFpeDtxPBaPeYh5hLv1vg9YQ= -github.com/openshift-online/ocm-sdk-go v0.1.388/go.mod h1:/+VFIw1iW2H0jEkFH4GnbL/liWareyzsL0w7mDIudB4= +github.com/openshift-online/ocm-common v0.0.0-20240129111424-ff8c6c11d909 h1:WV67GNazQuGDaLX3kBbz0859NYPOQCsDCY5XUScF85M= +github.com/openshift-online/ocm-common v0.0.0-20240129111424-ff8c6c11d909/go.mod h1:7FaAb07S63RF4sFMLSLtQaJLvPdaRnhAT4dBLD8/5kM= +github.com/openshift-online/ocm-sdk-go v0.1.403 h1:IGp921wwwp/bmAdvTDFJjS0Bqto7yfevPgh5JQI5XFo= +github.com/openshift-online/ocm-sdk-go v0.1.403/go.mod h1:tke8vKcE7eHKyRbkJv6qo4ljo919zhx04uyQTcgF5cQ= +github.com/openshift/rosa v1.2.35-rc1.0.20240221134836-0beb882e5836 h1:gdsNdTF/FuVlfaOTFHYL5+QweYz0R5Ogeu1sWJvAuMQ= +github.com/openshift/rosa v1.2.35-rc1.0.20240221134836-0beb882e5836/go.mod h1:eCjTo7aTeuUNNwo1bPxIJb6hxyi4J1MfSqUTkNx63q0= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -482,15 +499,15 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -513,8 +530,10 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -570,7 +589,11 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zgalor/weberr v0.6.0 h1:k6XSpFcOUNco8qtyAMBqXbCAVUivV7mRxGE5CMqHHdM= +github.com/zgalor/weberr v0.6.0/go.mod h1:cqK89mj84q3PRgqQXQFWJDzCorOd8xOtov/ulOnqDwc= github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4= +gitlab.com/c0b/go-ordered-json v0.0.0-20171130231205-49bbdab258c2 h1:M+r1hdmjZc4L4SCn0ZIq/5YQIRxprV+kOf7n7f04l5o= +gitlab.com/c0b/go-ordered-json v0.0.0-20171130231205-49bbdab258c2/go.mod h1:NREvu3a57BaK0R1+ztrEzHWiZAihohNLQ6trPxlIqZI= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= @@ -709,13 +732,12 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -725,8 +747,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= -golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -744,6 +766,7 @@ golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -814,6 +837,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= @@ -908,8 +932,9 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -982,8 +1007,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/cloud/services/autoscaling/mock_autoscalingiface/autoscalingapi_mock.go b/pkg/cloud/services/autoscaling/mock_autoscalingiface/autoscalingapi_mock.go index 0c1a67496c..58e83111bb 100644 --- a/pkg/cloud/services/autoscaling/mock_autoscalingiface/autoscalingapi_mock.go +++ b/pkg/cloud/services/autoscaling/mock_autoscalingiface/autoscalingapi_mock.go @@ -1333,6 +1333,39 @@ func (mr *MockAutoScalingAPIMockRecorder) DescribeInstanceRefreshes(arg0 interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeInstanceRefreshes", reflect.TypeOf((*MockAutoScalingAPI)(nil).DescribeInstanceRefreshes), arg0) } +// DescribeInstanceRefreshesPages mocks base method. +func (m *MockAutoScalingAPI) DescribeInstanceRefreshesPages(arg0 *autoscaling.DescribeInstanceRefreshesInput, arg1 func(*autoscaling.DescribeInstanceRefreshesOutput, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DescribeInstanceRefreshesPages", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DescribeInstanceRefreshesPages indicates an expected call of DescribeInstanceRefreshesPages. +func (mr *MockAutoScalingAPIMockRecorder) DescribeInstanceRefreshesPages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeInstanceRefreshesPages", reflect.TypeOf((*MockAutoScalingAPI)(nil).DescribeInstanceRefreshesPages), arg0, arg1) +} + +// DescribeInstanceRefreshesPagesWithContext mocks base method. +func (m *MockAutoScalingAPI) DescribeInstanceRefreshesPagesWithContext(arg0 context.Context, arg1 *autoscaling.DescribeInstanceRefreshesInput, arg2 func(*autoscaling.DescribeInstanceRefreshesOutput, bool) bool, arg3 ...request.Option) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DescribeInstanceRefreshesPagesWithContext", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DescribeInstanceRefreshesPagesWithContext indicates an expected call of DescribeInstanceRefreshesPagesWithContext. +func (mr *MockAutoScalingAPIMockRecorder) DescribeInstanceRefreshesPagesWithContext(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeInstanceRefreshesPagesWithContext", reflect.TypeOf((*MockAutoScalingAPI)(nil).DescribeInstanceRefreshesPagesWithContext), varargs...) +} + // DescribeInstanceRefreshesRequest mocks base method. func (m *MockAutoScalingAPI) DescribeInstanceRefreshesRequest(arg0 *autoscaling.DescribeInstanceRefreshesInput) (*request.Request, *autoscaling.DescribeInstanceRefreshesOutput) { m.ctrl.T.Helper() @@ -1566,6 +1599,39 @@ func (mr *MockAutoScalingAPIMockRecorder) DescribeLoadBalancerTargetGroups(arg0 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeLoadBalancerTargetGroups", reflect.TypeOf((*MockAutoScalingAPI)(nil).DescribeLoadBalancerTargetGroups), arg0) } +// DescribeLoadBalancerTargetGroupsPages mocks base method. +func (m *MockAutoScalingAPI) DescribeLoadBalancerTargetGroupsPages(arg0 *autoscaling.DescribeLoadBalancerTargetGroupsInput, arg1 func(*autoscaling.DescribeLoadBalancerTargetGroupsOutput, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DescribeLoadBalancerTargetGroupsPages", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DescribeLoadBalancerTargetGroupsPages indicates an expected call of DescribeLoadBalancerTargetGroupsPages. +func (mr *MockAutoScalingAPIMockRecorder) DescribeLoadBalancerTargetGroupsPages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeLoadBalancerTargetGroupsPages", reflect.TypeOf((*MockAutoScalingAPI)(nil).DescribeLoadBalancerTargetGroupsPages), arg0, arg1) +} + +// DescribeLoadBalancerTargetGroupsPagesWithContext mocks base method. +func (m *MockAutoScalingAPI) DescribeLoadBalancerTargetGroupsPagesWithContext(arg0 context.Context, arg1 *autoscaling.DescribeLoadBalancerTargetGroupsInput, arg2 func(*autoscaling.DescribeLoadBalancerTargetGroupsOutput, bool) bool, arg3 ...request.Option) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DescribeLoadBalancerTargetGroupsPagesWithContext", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DescribeLoadBalancerTargetGroupsPagesWithContext indicates an expected call of DescribeLoadBalancerTargetGroupsPagesWithContext. +func (mr *MockAutoScalingAPIMockRecorder) DescribeLoadBalancerTargetGroupsPagesWithContext(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeLoadBalancerTargetGroupsPagesWithContext", reflect.TypeOf((*MockAutoScalingAPI)(nil).DescribeLoadBalancerTargetGroupsPagesWithContext), varargs...) +} + // DescribeLoadBalancerTargetGroupsRequest mocks base method. func (m *MockAutoScalingAPI) DescribeLoadBalancerTargetGroupsRequest(arg0 *autoscaling.DescribeLoadBalancerTargetGroupsInput) (*request.Request, *autoscaling.DescribeLoadBalancerTargetGroupsOutput) { m.ctrl.T.Helper() @@ -1616,6 +1682,39 @@ func (mr *MockAutoScalingAPIMockRecorder) DescribeLoadBalancers(arg0 interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeLoadBalancers", reflect.TypeOf((*MockAutoScalingAPI)(nil).DescribeLoadBalancers), arg0) } +// DescribeLoadBalancersPages mocks base method. +func (m *MockAutoScalingAPI) DescribeLoadBalancersPages(arg0 *autoscaling.DescribeLoadBalancersInput, arg1 func(*autoscaling.DescribeLoadBalancersOutput, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DescribeLoadBalancersPages", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DescribeLoadBalancersPages indicates an expected call of DescribeLoadBalancersPages. +func (mr *MockAutoScalingAPIMockRecorder) DescribeLoadBalancersPages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeLoadBalancersPages", reflect.TypeOf((*MockAutoScalingAPI)(nil).DescribeLoadBalancersPages), arg0, arg1) +} + +// DescribeLoadBalancersPagesWithContext mocks base method. +func (m *MockAutoScalingAPI) DescribeLoadBalancersPagesWithContext(arg0 context.Context, arg1 *autoscaling.DescribeLoadBalancersInput, arg2 func(*autoscaling.DescribeLoadBalancersOutput, bool) bool, arg3 ...request.Option) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DescribeLoadBalancersPagesWithContext", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DescribeLoadBalancersPagesWithContext indicates an expected call of DescribeLoadBalancersPagesWithContext. +func (mr *MockAutoScalingAPIMockRecorder) DescribeLoadBalancersPagesWithContext(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeLoadBalancersPagesWithContext", reflect.TypeOf((*MockAutoScalingAPI)(nil).DescribeLoadBalancersPagesWithContext), varargs...) +} + // DescribeLoadBalancersRequest mocks base method. func (m *MockAutoScalingAPI) DescribeLoadBalancersRequest(arg0 *autoscaling.DescribeLoadBalancersInput) (*request.Request, *autoscaling.DescribeLoadBalancersOutput) { m.ctrl.T.Helper() diff --git a/pkg/rosa/client.go b/pkg/rosa/client.go index cbb9793d82..8d027f89ce 100644 --- a/pkg/rosa/client.go +++ b/pkg/rosa/client.go @@ -5,7 +5,9 @@ import ( "fmt" "os" - sdk "github.com/openshift-online/ocm-sdk-go" + ocmcfg "github.com/openshift/rosa/pkg/config" + "github.com/openshift/rosa/pkg/ocm" + "github.com/sirupsen/logrus" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" @@ -16,27 +18,25 @@ const ( ocmAPIURLKey = "ocmApiUrl" ) -type RosaClient struct { - ocm *sdk.Connection - rosaScope *scope.ROSAControlPlaneScope -} - -// NewRosaClientWithConnection creates a client with a preexisting connection for testing purposes. -func NewRosaClientWithConnection(connection *sdk.Connection, rosaScope *scope.ROSAControlPlaneScope) *RosaClient { - return &RosaClient{ - ocm: connection, - rosaScope: rosaScope, +func NewOCMClient(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (*ocm.Client, error) { + token, url, err := ocmCredentials(ctx, rosaScope) + if err != nil { + return nil, err } + return ocm.NewClient().Logger(logrus.New()).Config(&ocmcfg.Config{ + AccessToken: token, + URL: url, + }).Build() } -func NewRosaClient(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (*RosaClient, error) { +func ocmCredentials(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (string, string, error) { var token string var ocmAPIUrl string secret := rosaScope.CredentialsSecret() if secret != nil { if err := rosaScope.Client.Get(ctx, client.ObjectKeyFromObject(secret), secret); err != nil { - return nil, fmt.Errorf("failed to get credentials secret: %w", err) + return "", "", fmt.Errorf("failed to get credentials secret: %w", err) } token = string(secret.Data[ocmTokenKey]) @@ -50,40 +50,7 @@ func NewRosaClient(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) } if token == "" { - return nil, fmt.Errorf("token is not provided, be sure to set OCM_TOKEN env variable or reference a credentials secret with key %s", ocmTokenKey) - } - - // Create a logger that has the debug level enabled: - logger, err := sdk.NewGoLoggerBuilder(). - Debug(true). - Build() - if err != nil { - return nil, fmt.Errorf("failed to build logger: %w", err) - } - - connection, err := sdk.NewConnectionBuilder(). - Logger(logger). - Tokens(token). - URL(ocmAPIUrl). - Build() - if err != nil { - return nil, fmt.Errorf("failed to create ocm connection: %w", err) + return "", "", fmt.Errorf("token is not provided, be sure to set OCM_TOKEN env variable or reference a credentials secret with key %s", ocmTokenKey) } - - return &RosaClient{ - ocm: connection, - rosaScope: rosaScope, - }, nil -} - -func (c *RosaClient) Close() error { - return c.ocm.Close() -} - -func (c *RosaClient) GetConnectionURL() string { - return c.ocm.URL() -} - -func (c *RosaClient) GetConnectionTokens() (string, string, error) { - return c.ocm.Tokens() + return token, ocmAPIUrl, nil } diff --git a/pkg/rosa/clusters.go b/pkg/rosa/clusters.go deleted file mode 100644 index 98dc0c5d2c..0000000000 --- a/pkg/rosa/clusters.go +++ /dev/null @@ -1,77 +0,0 @@ -package rosa - -import ( - "fmt" - - cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" -) - -const ( - rosaCreatorArnProperty = "rosa_creator_arn" -) - -// CreateCluster creates a new ROSA cluster using the specified spec. -func (c *RosaClient) CreateCluster(spec *cmv1.Cluster) (*cmv1.Cluster, error) { - cluster, err := c.ocm.ClustersMgmt().V1().Clusters(). - Add(). - Body(spec). - Send() - if err != nil { - return nil, handleErr(cluster.Error(), err) - } - - clusterObject := cluster.Body() - return clusterObject, nil -} - -// DeleteCluster deletes the ROSA cluster. -func (c *RosaClient) DeleteCluster(clusterID string) error { - response, err := c.ocm.ClustersMgmt().V1().Clusters(). - Cluster(clusterID). - Delete(). - BestEffort(true). - Send() - if err != nil { - return handleErr(response.Error(), err) - } - - return nil -} - -// GetCluster retrieves the ROSA/OCM cluster object. -func (c *RosaClient) GetCluster() (*cmv1.Cluster, error) { - clusterKey := c.rosaScope.RosaClusterName() - query := fmt.Sprintf("%s AND (id = '%s' OR name = '%s' OR external_id = '%s')", - getClusterFilter(c.rosaScope.Identity.Arn), - clusterKey, clusterKey, clusterKey, - ) - response, err := c.ocm.ClustersMgmt().V1().Clusters().List(). - Search(query). - Page(1). - Size(1). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - - switch response.Total() { - case 0: - return nil, nil - case 1: - return response.Items().Slice()[0], nil - default: - return nil, fmt.Errorf("there are %d clusters with identifier or name '%s'", response.Total(), clusterKey) - } -} - -// Generate a query that filters clusters running on the current AWS session account. -func getClusterFilter(creatorArn *string) string { - filter := "product.id = 'rosa'" - if creatorArn != nil { - filter = fmt.Sprintf("%s AND (properties.%s = '%s')", - filter, - rosaCreatorArnProperty, - *creatorArn) - } - return filter -} diff --git a/pkg/rosa/idps.go b/pkg/rosa/idps.go index 8bd6d01f39..72e0562d92 100644 --- a/pkg/rosa/idps.go +++ b/pkg/rosa/idps.go @@ -2,64 +2,11 @@ package rosa import ( "fmt" - "net/http" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + "github.com/openshift/rosa/pkg/ocm" ) -// ListIdentityProviders retrieves the list of identity providers. -func (c *RosaClient) ListIdentityProviders(clusterID string) ([]*cmv1.IdentityProvider, error) { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - IdentityProviders(). - List().Page(1).Size(-1). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - - return response.Items().Slice(), nil -} - -// CreateIdentityProvider adds a new identity provider to the cluster. -func (c *RosaClient) CreateIdentityProvider(clusterID string, idp *cmv1.IdentityProvider) (*cmv1.IdentityProvider, error) { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - IdentityProviders(). - Add().Body(idp). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - return response.Body(), nil -} - -// GetHTPasswdUserList retrieves the list of users of the provided _HTPasswd_ identity provider. -func (c *RosaClient) GetHTPasswdUserList(clusterID, htpasswdIDPId string) (*cmv1.HTPasswdUserList, error) { - listResponse, err := c.ocm.ClustersMgmt().V1().Clusters().Cluster(clusterID). - IdentityProviders().IdentityProvider(htpasswdIDPId).HtpasswdUsers().List().Send() - if err != nil { - if listResponse.Error().Status() == http.StatusNotFound { - return nil, nil - } - return nil, handleErr(listResponse.Error(), err) - } - - return listResponse.Items(), nil -} - -// AddHTPasswdUser adds a new user to the provided _HTPasswd_ identity provider. -func (c *RosaClient) AddHTPasswdUser(username, password, clusterID, idpID string) error { - htpasswdUser, _ := cmv1.NewHTPasswdUser().Username(username).Password(password).Build() - response, err := c.ocm.ClustersMgmt().V1().Clusters().Cluster(clusterID). - IdentityProviders().IdentityProvider(idpID).HtpasswdUsers().Add().Body(htpasswdUser).Send() - if err != nil { - return handleErr(response.Error(), err) - } - - return nil -} - const ( clusterAdminUserGroup = "cluster-admins" clusterAdminIDPname = "cluster-admin" @@ -67,8 +14,8 @@ const ( // CreateAdminUserIfNotExist creates a new admin user withe username/password in the cluster if username doesn't already exist. // the user is granted admin privileges by being added to a special IDP called `cluster-admin` which will be created if it doesn't already exist. -func (c *RosaClient) CreateAdminUserIfNotExist(clusterID, username, password string) error { - existingClusterAdminIDP, userList, err := c.findExistingClusterAdminIDP(clusterID) +func CreateAdminUserIfNotExist(client *ocm.Client, clusterID, username, password string) error { + existingClusterAdminIDP, userList, err := findExistingClusterAdminIDP(client, clusterID) if err != nil { return fmt.Errorf("failed to find existing cluster admin IDP: %w", err) } @@ -80,7 +27,7 @@ func (c *RosaClient) CreateAdminUserIfNotExist(clusterID, username, password str } // Add admin user to the cluster-admins group: - user, err := c.CreateUserIfNotExist(clusterID, clusterAdminUserGroup, username) + user, err := CreateUserIfNotExist(client, clusterID, clusterAdminUserGroup, username) if err != nil { return fmt.Errorf("failed to add user '%s' to cluster '%s': %s", username, clusterID, err) @@ -88,7 +35,7 @@ func (c *RosaClient) CreateAdminUserIfNotExist(clusterID, username, password str if existingClusterAdminIDP != nil { // add htpasswd user to existing idp - err := c.AddHTPasswdUser(username, password, clusterID, existingClusterAdminIDP.ID()) + err := client.AddHTPasswdUser(username, password, clusterID, existingClusterAdminIDP.ID()) if err != nil { return fmt.Errorf("failed to add htpassawoed user cluster-admin to existing idp: %s", existingClusterAdminIDP.ID()) } @@ -114,10 +61,10 @@ func (c *RosaClient) CreateAdminUserIfNotExist(clusterID, username, password str } // Add HTPasswd IDP to cluster - _, err = c.CreateIdentityProvider(clusterID, clusterAdminIDP) + _, err = client.CreateIdentityProvider(clusterID, clusterAdminIDP) if err != nil { // since we could not add the HTPasswd IDP to the cluster, roll back and remove the cluster admin - if err := c.DeleteUser(clusterID, clusterAdminUserGroup, user.ID()); err != nil { + if err := client.DeleteUser(clusterID, clusterAdminUserGroup, user.ID()); err != nil { return fmt.Errorf("failed to revert the admin user for cluster '%s': %w", clusterID, err) } @@ -127,9 +74,23 @@ func (c *RosaClient) CreateAdminUserIfNotExist(clusterID, username, password str return nil } -func (c *RosaClient) findExistingClusterAdminIDP(clusterID string) ( +// CreateUserIfNotExist creates a new user with `username` and adds it to the group if it doesn't already exist. +func CreateUserIfNotExist(client *ocm.Client, clusterID string, group, username string) (*cmv1.User, error) { + user, err := client.GetUser(clusterID, group, username) + if user != nil || err != nil { + return user, err + } + + userCfg, err := cmv1.NewUser().ID(username).Build() + if err != nil { + return nil, fmt.Errorf("failed to create user '%s' for cluster '%s': %w", username, clusterID, err) + } + return client.CreateUser(clusterID, group, userCfg) +} + +func findExistingClusterAdminIDP(client *ocm.Client, clusterID string) ( htpasswdIDP *cmv1.IdentityProvider, userList *cmv1.HTPasswdUserList, reterr error) { - idps, err := c.ListIdentityProviders(clusterID) + idps, err := client.GetIdentityProviders(clusterID) if err != nil { reterr = fmt.Errorf("failed to get identity providers for cluster '%s': %v", clusterID, err) return @@ -137,7 +98,7 @@ func (c *RosaClient) findExistingClusterAdminIDP(clusterID string) ( for _, idp := range idps { if idp.Name() == clusterAdminIDPname { - itemUserList, err := c.GetHTPasswdUserList(clusterID, idp.ID()) + itemUserList, err := client.GetHTPasswdUserList(clusterID, idp.ID()) if err != nil { reterr = fmt.Errorf("failed to get user list of the HTPasswd IDP of '%s: %s': %v", idp.Name(), clusterID, err) return diff --git a/pkg/rosa/nodepools.go b/pkg/rosa/nodepools.go deleted file mode 100644 index 0b59bb9869..0000000000 --- a/pkg/rosa/nodepools.go +++ /dev/null @@ -1,146 +0,0 @@ -package rosa - -import ( - "time" - - cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" -) - -// CreateNodePool adds a new node pool to the cluster. -func (c *RosaClient) CreateNodePool(clusterID string, nodePool *cmv1.NodePool) (*cmv1.NodePool, error) { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - NodePools(). - Add().Body(nodePool). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - return response.Body(), nil -} - -// GetNodePools retrieves the list of node pools in the cluster. -func (c *RosaClient) GetNodePools(clusterID string) ([]*cmv1.NodePool, error) { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - NodePools(). - List().Page(1).Size(-1). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - return response.Items().Slice(), nil -} - -// GetNodePool retrieves the details of the specified node pool. -func (c *RosaClient) GetNodePool(clusterID string, nodePoolID string) (*cmv1.NodePool, bool, error) { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - NodePools(). - NodePool(nodePoolID). - Get(). - Send() - if response.Status() == 404 { - return nil, false, nil - } - if err != nil { - return nil, false, handleErr(response.Error(), err) - } - return response.Body(), true, nil -} - -// UpdateNodePool updates the specified node pool. -func (c *RosaClient) UpdateNodePool(clusterID string, nodePool *cmv1.NodePool) (*cmv1.NodePool, error) { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - NodePools().NodePool(nodePool.ID()). - Update().Body(nodePool). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - return response.Body(), nil -} - -// DeleteNodePool deletes the specified node pool. -func (c *RosaClient) DeleteNodePool(clusterID string, nodePoolID string) error { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - NodePools().NodePool(nodePoolID). - Delete(). - Send() - if err != nil { - return handleErr(response.Error(), err) - } - return nil -} - -// CheckNodePoolExistingScheduledUpgrade checks and returns the current upgrade schedule for the nodePool if any. -func (c *RosaClient) CheckNodePoolExistingScheduledUpgrade(clusterID string, nodePool *cmv1.NodePool) (*cmv1.NodePoolUpgradePolicy, error) { - upgradePolicies, err := c.getNodePoolUpgradePolicies(clusterID, nodePool.ID()) - if err != nil { - return nil, err - } - for _, upgradePolicy := range upgradePolicies { - if upgradePolicy.UpgradeType() == cmv1.UpgradeTypeNodePool { - return upgradePolicy, nil - } - } - return nil, nil -} - -// ScheduleNodePoolUpgrade schedules a new nodePool upgrade to the specified version at the specified time. -func (c *RosaClient) ScheduleNodePoolUpgrade(clusterID string, nodePool *cmv1.NodePool, version string, nextRun time.Time) (*cmv1.NodePoolUpgradePolicy, error) { - // earliestNextRun is set to at least 5 min from now by the OCM API. - // we set it to 6 min here to account for latencty. - earliestNextRun := time.Now().Add(time.Minute * 6) - if nextRun.Before(earliestNextRun) { - nextRun = earliestNextRun - } - - upgradePolicy, err := cmv1.NewNodePoolUpgradePolicy(). - UpgradeType(cmv1.UpgradeTypeNodePool). - NodePoolID(nodePool.ID()). - ScheduleType(cmv1.ScheduleTypeManual). - Version(version). - NextRun(nextRun). - Build() - if err != nil { - return nil, err - } - - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - NodePools(). - NodePool(nodePool.ID()).UpgradePolicies(). - Add().Body(upgradePolicy). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - - return response.Body(), nil -} - -func (c *RosaClient) getNodePoolUpgradePolicies(clusterID string, nodePoolID string) (nodePoolUpgradePolicies []*cmv1.NodePoolUpgradePolicy, err error) { - collection := c.ocm.ClustersMgmt().V1(). - Clusters(). - Cluster(clusterID).NodePools().NodePool(nodePoolID).UpgradePolicies() - page := 1 - size := 100 - for { - response, err := collection.List(). - Page(page). - Size(size). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - nodePoolUpgradePolicies = append(nodePoolUpgradePolicies, response.Items().Slice()...) - if response.Size() < size { - break - } - page++ - } - return -} diff --git a/pkg/rosa/users.go b/pkg/rosa/users.go deleted file mode 100644 index 38203536f2..0000000000 --- a/pkg/rosa/users.go +++ /dev/null @@ -1,58 +0,0 @@ -package rosa - -import ( - "fmt" - "net/http" - - cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" -) - -// CreateUserIfNotExist creates a new user with `username` and adds it to the group if it doesn't already exist. -func (c *RosaClient) CreateUserIfNotExist(clusterID string, group, username string) (*cmv1.User, error) { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - Groups().Group(group). - Users().User(username). - Get(). - Send() - if err == nil { - return response.Body(), nil - } else if response.Error().Status() != http.StatusNotFound { - return nil, handleErr(response.Error(), err) - } - - user, err := cmv1.NewUser().ID(username).Build() - if err != nil { - return nil, fmt.Errorf("failed to create user '%s' for cluster '%s'", username, clusterID) - } - - return c.CreateUser(clusterID, group, user) -} - -// CreateUser adds a new user to the group. -func (c *RosaClient) CreateUser(clusterID string, group string, user *cmv1.User) (*cmv1.User, error) { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - Groups().Group(group). - Users(). - Add().Body(user). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - return response.Body(), nil -} - -// DeleteUser deletes the user from the cluster. -func (c *RosaClient) DeleteUser(clusterID string, group string, username string) error { - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(clusterID). - Groups().Group(group). - Users().User(username). - Delete(). - Send() - if err != nil { - return handleErr(response.Error(), err) - } - return nil -} diff --git a/pkg/rosa/util.go b/pkg/rosa/util.go deleted file mode 100644 index 37f75bc25e..0000000000 --- a/pkg/rosa/util.go +++ /dev/null @@ -1,60 +0,0 @@ -package rosa - -import ( - "crypto/rand" - "fmt" - "math/big" - - ocmerrors "github.com/openshift-online/ocm-sdk-go/errors" -) - -func handleErr(res *ocmerrors.Error, err error) error { - msg := res.Reason() - if msg == "" { - msg = err.Error() - } - // Hack to always display the correct terms and conditions message - if res.Code() == "CLUSTERS-MGMT-451" { - msg = "You must accept the Terms and Conditions in order to continue.\n" + - "Go to https://www.redhat.com/wapps/tnc/ackrequired?site=ocm&event=register\n" + - "Once you accept the terms, you will need to retry the action that was blocked." - } - return fmt.Errorf(msg) -} - -// GenerateRandomPassword generates a random password which satisfies OCM requiremts for passwords. -func GenerateRandomPassword() (string, error) { - const ( - maxPasswordLength = 23 - lowerLetters = "abcdefghijkmnopqrstuvwxyz" - upperLetters = "ABCDEFGHIJKLMNPQRSTUVWXYZ" - digits = "23456789" - all = lowerLetters + upperLetters + digits - ) - var password string - for i := 0; i < maxPasswordLength; i++ { - n, err := rand.Int(rand.Reader, big.NewInt(int64(len(all)))) - if err != nil { - return "", err - } - newchar := string(all[n.Int64()]) - if password == "" { - password = newchar - } - if i < maxPasswordLength-1 { - n, err = rand.Int(rand.Reader, big.NewInt(int64(len(password)+1))) - if err != nil { - return "", err - } - j := n.Int64() - password = password[0:j] + newchar + password[j:] - } - } - - pw := []rune(password) - for _, replace := range []int{5, 11, 17} { - pw[replace] = '-' - } - - return string(pw), nil -} diff --git a/pkg/rosa/versions.go b/pkg/rosa/versions.go index 1bdeee8033..2949d3b5cf 100644 --- a/pkg/rosa/versions.go +++ b/pkg/rosa/versions.go @@ -1,48 +1,18 @@ package rosa import ( - "fmt" - "strings" "time" "github.com/blang/semver" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + "github.com/openshift/rosa/pkg/ocm" ) var MinSupportedVersion = semver.MustParse("4.14.0") -// IsVersionSupported checks whether the input version is supported for ROSA clusters. -func (c *RosaClient) IsVersionSupported(versionID string) (bool, error) { - parsedVersion, err := semver.Parse(versionID) - if err != nil { - return false, err - } - if parsedVersion.LT(MinSupportedVersion) { - return false, nil - } - - filter := fmt.Sprintf("raw_id='%s' AND channel_group = '%s'", versionID, "stable") - response, err := c.ocm.ClustersMgmt().V1(). - Versions(). - List(). - Search(filter). - Page(1).Size(1). - Parameter("product", "hcp"). - Send() - if err != nil { - return false, handleErr(response.Error(), err) - } - if response.Total() == 0 { - return false, nil - } - - version := response.Items().Get(0) - return version.ROSAEnabled() && version.HostedControlPlaneEnabled(), nil -} - // CheckExistingScheduledUpgrade checks and returns the current upgrade schedule if any. -func (c *RosaClient) CheckExistingScheduledUpgrade(cluster *cmv1.Cluster) (*cmv1.ControlPlaneUpgradePolicy, error) { - upgradePolicies, err := c.getControlPlaneUpgradePolicies(cluster.ID()) +func CheckExistingScheduledUpgrade(client *ocm.Client, cluster *cmv1.Cluster) (*cmv1.ControlPlaneUpgradePolicy, error) { + upgradePolicies, err := client.GetControlPlaneUpgradePolicies(cluster.ID()) if err != nil { return nil, err } @@ -55,7 +25,7 @@ func (c *RosaClient) CheckExistingScheduledUpgrade(cluster *cmv1.Cluster) (*cmv1 } // ScheduleControlPlaneUpgrade schedules a new control plane upgrade to the specified version at the specified time. -func (c *RosaClient) ScheduleControlPlaneUpgrade(cluster *cmv1.Cluster, version string, nextRun time.Time) (*cmv1.ControlPlaneUpgradePolicy, error) { +func ScheduleControlPlaneUpgrade(client *ocm.Client, cluster *cmv1.Cluster, version string, nextRun time.Time) (*cmv1.ControlPlaneUpgradePolicy, error) { // earliestNextRun is set to at least 5 min from now by the OCM API. // we set it to 6 min here to account for latencty. earliestNextRun := time.Now().Add(time.Minute * 6) @@ -72,43 +42,7 @@ func (c *RosaClient) ScheduleControlPlaneUpgrade(cluster *cmv1.Cluster, version if err != nil { return nil, err } - - response, err := c.ocm.ClustersMgmt().V1(). - Clusters().Cluster(cluster.ID()). - ControlPlane(). - UpgradePolicies(). - Add().Body(upgradePolicy). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - - return response.Body(), nil -} - -func (c *RosaClient) getControlPlaneUpgradePolicies(clusterID string) (controlPlaneUpgradePolicies []*cmv1.ControlPlaneUpgradePolicy, err error) { - collection := c.ocm.ClustersMgmt().V1(). - Clusters(). - Cluster(clusterID). - ControlPlane(). - UpgradePolicies() - page := 1 - size := 100 - for { - response, err := collection.List(). - Page(page). - Size(size). - Send() - if err != nil { - return nil, handleErr(response.Error(), err) - } - controlPlaneUpgradePolicies = append(controlPlaneUpgradePolicies, response.Items().Slice()...) - if response.Size() < size { - break - } - page++ - } - return + return client.ScheduleHypershiftControlPlaneUpgrade(cluster.ID(), upgradePolicy) } // machinepools can be created with a minimal of two minor versions from the control plane. @@ -133,8 +67,6 @@ func MachinePoolSupportedVersionsRange(controlPlaneVersion string) (*semver.Vers return &minVersion, &maxVersion, nil } -const versionPrefix = "openshift-v" - // RawVersionID returns the rawID from the provided OCM version object. func RawVersionID(version *cmv1.Version) string { rawID := version.RawID() @@ -142,15 +74,5 @@ func RawVersionID(version *cmv1.Version) string { return rawID } - rawID = strings.TrimPrefix(version.ID(), versionPrefix) - channelSeparator := strings.LastIndex(rawID, "-") - if channelSeparator > 0 { - return rawID[:channelSeparator] - } - return rawID -} - -// VersionID construcuts and returns an OCM versionID from the provided rawVersionID. -func VersionID(rawVersionID string) string { - return fmt.Sprintf("%s%s", versionPrefix, rawVersionID) + return ocm.GetRawVersionId(version.ID()) } diff --git a/test/mocks/aws_ec2api_mock.go b/test/mocks/aws_ec2api_mock.go index f23dc5e004..6071b70149 100644 --- a/test/mocks/aws_ec2api_mock.go +++ b/test/mocks/aws_ec2api_mock.go @@ -22365,6 +22365,71 @@ func (mr *MockEC2APIMockRecorder) DisableFastSnapshotRestoresWithContext(arg0, a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableFastSnapshotRestoresWithContext", reflect.TypeOf((*MockEC2API)(nil).DisableFastSnapshotRestoresWithContext), varargs...) } +// DisableImage mocks base method. +func (m *MockEC2API) DisableImage(arg0 *ec2.DisableImageInput) (*ec2.DisableImageOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DisableImage", arg0) + ret0, _ := ret[0].(*ec2.DisableImageOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DisableImage indicates an expected call of DisableImage. +func (mr *MockEC2APIMockRecorder) DisableImage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableImage", reflect.TypeOf((*MockEC2API)(nil).DisableImage), arg0) +} + +// DisableImageBlockPublicAccess mocks base method. +func (m *MockEC2API) DisableImageBlockPublicAccess(arg0 *ec2.DisableImageBlockPublicAccessInput) (*ec2.DisableImageBlockPublicAccessOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DisableImageBlockPublicAccess", arg0) + ret0, _ := ret[0].(*ec2.DisableImageBlockPublicAccessOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DisableImageBlockPublicAccess indicates an expected call of DisableImageBlockPublicAccess. +func (mr *MockEC2APIMockRecorder) DisableImageBlockPublicAccess(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableImageBlockPublicAccess", reflect.TypeOf((*MockEC2API)(nil).DisableImageBlockPublicAccess), arg0) +} + +// DisableImageBlockPublicAccessRequest mocks base method. +func (m *MockEC2API) DisableImageBlockPublicAccessRequest(arg0 *ec2.DisableImageBlockPublicAccessInput) (*request.Request, *ec2.DisableImageBlockPublicAccessOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DisableImageBlockPublicAccessRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*ec2.DisableImageBlockPublicAccessOutput) + return ret0, ret1 +} + +// DisableImageBlockPublicAccessRequest indicates an expected call of DisableImageBlockPublicAccessRequest. +func (mr *MockEC2APIMockRecorder) DisableImageBlockPublicAccessRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableImageBlockPublicAccessRequest", reflect.TypeOf((*MockEC2API)(nil).DisableImageBlockPublicAccessRequest), arg0) +} + +// DisableImageBlockPublicAccessWithContext mocks base method. +func (m *MockEC2API) DisableImageBlockPublicAccessWithContext(arg0 context.Context, arg1 *ec2.DisableImageBlockPublicAccessInput, arg2 ...request.Option) (*ec2.DisableImageBlockPublicAccessOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DisableImageBlockPublicAccessWithContext", varargs...) + ret0, _ := ret[0].(*ec2.DisableImageBlockPublicAccessOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DisableImageBlockPublicAccessWithContext indicates an expected call of DisableImageBlockPublicAccessWithContext. +func (mr *MockEC2APIMockRecorder) DisableImageBlockPublicAccessWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableImageBlockPublicAccessWithContext", reflect.TypeOf((*MockEC2API)(nil).DisableImageBlockPublicAccessWithContext), varargs...) +} + // DisableImageDeprecation mocks base method. func (m *MockEC2API) DisableImageDeprecation(arg0 *ec2.DisableImageDeprecationInput) (*ec2.DisableImageDeprecationOutput, error) { m.ctrl.T.Helper() @@ -22415,6 +22480,41 @@ func (mr *MockEC2APIMockRecorder) DisableImageDeprecationWithContext(arg0, arg1 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableImageDeprecationWithContext", reflect.TypeOf((*MockEC2API)(nil).DisableImageDeprecationWithContext), varargs...) } +// DisableImageRequest mocks base method. +func (m *MockEC2API) DisableImageRequest(arg0 *ec2.DisableImageInput) (*request.Request, *ec2.DisableImageOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DisableImageRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*ec2.DisableImageOutput) + return ret0, ret1 +} + +// DisableImageRequest indicates an expected call of DisableImageRequest. +func (mr *MockEC2APIMockRecorder) DisableImageRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableImageRequest", reflect.TypeOf((*MockEC2API)(nil).DisableImageRequest), arg0) +} + +// DisableImageWithContext mocks base method. +func (m *MockEC2API) DisableImageWithContext(arg0 context.Context, arg1 *ec2.DisableImageInput, arg2 ...request.Option) (*ec2.DisableImageOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DisableImageWithContext", varargs...) + ret0, _ := ret[0].(*ec2.DisableImageOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DisableImageWithContext indicates an expected call of DisableImageWithContext. +func (mr *MockEC2APIMockRecorder) DisableImageWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableImageWithContext", reflect.TypeOf((*MockEC2API)(nil).DisableImageWithContext), varargs...) +} + // DisableIpamOrganizationAdminAccount mocks base method. func (m *MockEC2API) DisableIpamOrganizationAdminAccount(arg0 *ec2.DisableIpamOrganizationAdminAccountInput) (*ec2.DisableIpamOrganizationAdminAccountOutput, error) { m.ctrl.T.Helper() @@ -23665,6 +23765,71 @@ func (mr *MockEC2APIMockRecorder) EnableFastSnapshotRestoresWithContext(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableFastSnapshotRestoresWithContext", reflect.TypeOf((*MockEC2API)(nil).EnableFastSnapshotRestoresWithContext), varargs...) } +// EnableImage mocks base method. +func (m *MockEC2API) EnableImage(arg0 *ec2.EnableImageInput) (*ec2.EnableImageOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableImage", arg0) + ret0, _ := ret[0].(*ec2.EnableImageOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnableImage indicates an expected call of EnableImage. +func (mr *MockEC2APIMockRecorder) EnableImage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableImage", reflect.TypeOf((*MockEC2API)(nil).EnableImage), arg0) +} + +// EnableImageBlockPublicAccess mocks base method. +func (m *MockEC2API) EnableImageBlockPublicAccess(arg0 *ec2.EnableImageBlockPublicAccessInput) (*ec2.EnableImageBlockPublicAccessOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableImageBlockPublicAccess", arg0) + ret0, _ := ret[0].(*ec2.EnableImageBlockPublicAccessOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnableImageBlockPublicAccess indicates an expected call of EnableImageBlockPublicAccess. +func (mr *MockEC2APIMockRecorder) EnableImageBlockPublicAccess(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableImageBlockPublicAccess", reflect.TypeOf((*MockEC2API)(nil).EnableImageBlockPublicAccess), arg0) +} + +// EnableImageBlockPublicAccessRequest mocks base method. +func (m *MockEC2API) EnableImageBlockPublicAccessRequest(arg0 *ec2.EnableImageBlockPublicAccessInput) (*request.Request, *ec2.EnableImageBlockPublicAccessOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableImageBlockPublicAccessRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*ec2.EnableImageBlockPublicAccessOutput) + return ret0, ret1 +} + +// EnableImageBlockPublicAccessRequest indicates an expected call of EnableImageBlockPublicAccessRequest. +func (mr *MockEC2APIMockRecorder) EnableImageBlockPublicAccessRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableImageBlockPublicAccessRequest", reflect.TypeOf((*MockEC2API)(nil).EnableImageBlockPublicAccessRequest), arg0) +} + +// EnableImageBlockPublicAccessWithContext mocks base method. +func (m *MockEC2API) EnableImageBlockPublicAccessWithContext(arg0 context.Context, arg1 *ec2.EnableImageBlockPublicAccessInput, arg2 ...request.Option) (*ec2.EnableImageBlockPublicAccessOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "EnableImageBlockPublicAccessWithContext", varargs...) + ret0, _ := ret[0].(*ec2.EnableImageBlockPublicAccessOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnableImageBlockPublicAccessWithContext indicates an expected call of EnableImageBlockPublicAccessWithContext. +func (mr *MockEC2APIMockRecorder) EnableImageBlockPublicAccessWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableImageBlockPublicAccessWithContext", reflect.TypeOf((*MockEC2API)(nil).EnableImageBlockPublicAccessWithContext), varargs...) +} + // EnableImageDeprecation mocks base method. func (m *MockEC2API) EnableImageDeprecation(arg0 *ec2.EnableImageDeprecationInput) (*ec2.EnableImageDeprecationOutput, error) { m.ctrl.T.Helper() @@ -23715,6 +23880,41 @@ func (mr *MockEC2APIMockRecorder) EnableImageDeprecationWithContext(arg0, arg1 i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableImageDeprecationWithContext", reflect.TypeOf((*MockEC2API)(nil).EnableImageDeprecationWithContext), varargs...) } +// EnableImageRequest mocks base method. +func (m *MockEC2API) EnableImageRequest(arg0 *ec2.EnableImageInput) (*request.Request, *ec2.EnableImageOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableImageRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*ec2.EnableImageOutput) + return ret0, ret1 +} + +// EnableImageRequest indicates an expected call of EnableImageRequest. +func (mr *MockEC2APIMockRecorder) EnableImageRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableImageRequest", reflect.TypeOf((*MockEC2API)(nil).EnableImageRequest), arg0) +} + +// EnableImageWithContext mocks base method. +func (m *MockEC2API) EnableImageWithContext(arg0 context.Context, arg1 *ec2.EnableImageInput, arg2 ...request.Option) (*ec2.EnableImageOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "EnableImageWithContext", varargs...) + ret0, _ := ret[0].(*ec2.EnableImageOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnableImageWithContext indicates an expected call of EnableImageWithContext. +func (mr *MockEC2APIMockRecorder) EnableImageWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableImageWithContext", reflect.TypeOf((*MockEC2API)(nil).EnableImageWithContext), varargs...) +} + // EnableIpamOrganizationAdminAccount mocks base method. func (m *MockEC2API) EnableIpamOrganizationAdminAccount(arg0 *ec2.EnableIpamOrganizationAdminAccountInput) (*ec2.EnableIpamOrganizationAdminAccountOutput, error) { m.ctrl.T.Helper() @@ -25064,6 +25264,56 @@ func (mr *MockEC2APIMockRecorder) GetHostReservationPurchasePreviewWithContext(a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHostReservationPurchasePreviewWithContext", reflect.TypeOf((*MockEC2API)(nil).GetHostReservationPurchasePreviewWithContext), varargs...) } +// GetImageBlockPublicAccessState mocks base method. +func (m *MockEC2API) GetImageBlockPublicAccessState(arg0 *ec2.GetImageBlockPublicAccessStateInput) (*ec2.GetImageBlockPublicAccessStateOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetImageBlockPublicAccessState", arg0) + ret0, _ := ret[0].(*ec2.GetImageBlockPublicAccessStateOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetImageBlockPublicAccessState indicates an expected call of GetImageBlockPublicAccessState. +func (mr *MockEC2APIMockRecorder) GetImageBlockPublicAccessState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageBlockPublicAccessState", reflect.TypeOf((*MockEC2API)(nil).GetImageBlockPublicAccessState), arg0) +} + +// GetImageBlockPublicAccessStateRequest mocks base method. +func (m *MockEC2API) GetImageBlockPublicAccessStateRequest(arg0 *ec2.GetImageBlockPublicAccessStateInput) (*request.Request, *ec2.GetImageBlockPublicAccessStateOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetImageBlockPublicAccessStateRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*ec2.GetImageBlockPublicAccessStateOutput) + return ret0, ret1 +} + +// GetImageBlockPublicAccessStateRequest indicates an expected call of GetImageBlockPublicAccessStateRequest. +func (mr *MockEC2APIMockRecorder) GetImageBlockPublicAccessStateRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageBlockPublicAccessStateRequest", reflect.TypeOf((*MockEC2API)(nil).GetImageBlockPublicAccessStateRequest), arg0) +} + +// GetImageBlockPublicAccessStateWithContext mocks base method. +func (m *MockEC2API) GetImageBlockPublicAccessStateWithContext(arg0 context.Context, arg1 *ec2.GetImageBlockPublicAccessStateInput, arg2 ...request.Option) (*ec2.GetImageBlockPublicAccessStateOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetImageBlockPublicAccessStateWithContext", varargs...) + ret0, _ := ret[0].(*ec2.GetImageBlockPublicAccessStateOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetImageBlockPublicAccessStateWithContext indicates an expected call of GetImageBlockPublicAccessStateWithContext. +func (mr *MockEC2APIMockRecorder) GetImageBlockPublicAccessStateWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageBlockPublicAccessStateWithContext", reflect.TypeOf((*MockEC2API)(nil).GetImageBlockPublicAccessStateWithContext), varargs...) +} + // GetInstanceTypesFromInstanceRequirements mocks base method. func (m *MockEC2API) GetInstanceTypesFromInstanceRequirements(arg0 *ec2.GetInstanceTypesFromInstanceRequirementsInput) (*ec2.GetInstanceTypesFromInstanceRequirementsOutput, error) { m.ctrl.T.Helper() From ddc306542c625f4ac46079e9d493a455fdeda7b9 Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Fri, 23 Feb 2024 09:20:09 -0700 Subject: [PATCH 2/3] rosa: use CEL validation for versions The webhooks we had were only giving us a nice error message on top of the parsing validation, and we can get all of that benefit by just using a CEL matcher and a custom error message. Not having to deploy and run webhooks simplifies things for us a sizeable amount and makes local testing much easier, as well. Signed-off-by: Steve Kuznetsov --- ...ne.cluster.x-k8s.io_rosacontrolplanes.yaml | 5 +- ...ure.cluster.x-k8s.io_rosamachinepools.yaml | 3 + config/webhook/manifests.yaml | 88 ------------------- .../api/v1beta2/rosacontrolplane_types.go | 3 +- .../api/v1beta2/rosacontrolplane_webhook.go | 81 ----------------- .../rosa/api/v1beta2/zz_generated.deepcopy.go | 2 +- exp/api/v1beta2/rosamachinepool_types.go | 1 + exp/api/v1beta2/rosamachinepool_webhook.go | 83 ----------------- main.go | 10 --- 9 files changed, 11 insertions(+), 265 deletions(-) delete mode 100644 controlplane/rosa/api/v1beta2/rosacontrolplane_webhook.go delete mode 100644 exp/api/v1beta2/rosamachinepool_webhook.go diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml index 245d9d72a6..a0ab9da904 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml @@ -286,8 +286,11 @@ spec: supportRoleARN: type: string version: - description: Openshift version, for example "4.14.5". + description: OpenShift semantic version, for example "4.14.5". type: string + x-kubernetes-validations: + - message: version must be a valid semantic version + rule: self.matches('^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$') workerRoleARN: type: string required: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml index a964cad45e..d94ef3e30d 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml @@ -98,6 +98,9 @@ spec: description: Version specifies the penshift version of the nodes associated with this machinepool. ROSAControlPlane version is used if not set. type: string + x-kubernetes-validations: + - message: version must be a valid semantic version + rule: self.matches('^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$') required: - nodePoolName type: object diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 5eebfae968..9dd74404c2 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -201,28 +201,6 @@ webhooks: resources: - awsmanagedmachinepools sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-infrastructure-cluster-x-k8s-io-v1beta2-rosamachinepool - failurePolicy: Fail - matchPolicy: Equivalent - name: default.rosamachinepool.infrastructure.cluster.x-k8s.io - rules: - - apiGroups: - - infrastructure.cluster.x-k8s.io - apiVersions: - - v1beta2 - operations: - - CREATE - - UPDATE - resources: - - rosamachinepools - sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -289,28 +267,6 @@ webhooks: resources: - awsmanagedcontrolplanes sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-controlplane-cluster-x-k8s-io-v1beta2-rosacontrolplane - failurePolicy: Fail - matchPolicy: Equivalent - name: default.rosacontrolplanes.controlplane.cluster.x-k8s.io - rules: - - apiGroups: - - controlplane.cluster.x-k8s.io - apiVersions: - - v1beta2 - operations: - - CREATE - - UPDATE - resources: - - rosacontrolplanes - sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration @@ -537,28 +493,6 @@ webhooks: resources: - awsmanagedmachinepools sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-infrastructure-cluster-x-k8s-io-v1beta2-rosamachinepool - failurePolicy: Fail - matchPolicy: Equivalent - name: validation.rosamachinepool.infrastructure.cluster.x-k8s.io - rules: - - apiGroups: - - infrastructure.cluster.x-k8s.io - apiVersions: - - v1beta2 - operations: - - CREATE - - UPDATE - resources: - - rosamachinepools - sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -625,25 +559,3 @@ webhooks: resources: - awsmanagedcontrolplanes sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-controlplane-cluster-x-k8s-io-v1beta2-rosacontrolplane - failurePolicy: Fail - matchPolicy: Equivalent - name: validation.rosacontrolplanes.controlplane.cluster.x-k8s.io - rules: - - apiGroups: - - controlplane.cluster.x-k8s.io - apiVersions: - - v1beta2 - operations: - - CREATE - - UPDATE - resources: - - rosacontrolplanes - sideEffects: None diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go index 91728fc04b..b9df205ca1 100644 --- a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go +++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go @@ -49,7 +49,8 @@ type RosaControlPlaneSpec struct { //nolint: maligned // The AWS Region the cluster lives in. Region *string `json:"region"` - // Openshift version, for example "4.14.5". + // OpenShift semantic version, for example "4.14.5". + // +kubebuilder:validation:XValidation:rule=`self.matches('^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$')`, message="version must be a valid semantic version" Version string `json:"version"` // ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_webhook.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_webhook.go deleted file mode 100644 index 13562167e5..0000000000 --- a/controlplane/rosa/api/v1beta2/rosacontrolplane_webhook.go +++ /dev/null @@ -1,81 +0,0 @@ -package v1beta2 - -import ( - "github.com/blang/semver" - apierrors "k8s.io/apimachinery/pkg/api/errors" - runtime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// SetupWebhookWithManager will setup the webhooks for the ROSAControlPlane. -func (r *ROSAControlPlane) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -// +kubebuilder:webhook:verbs=create;update,path=/validate-controlplane-cluster-x-k8s-io-v1beta2-rosacontrolplane,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes,versions=v1beta2,name=validation.rosacontrolplanes.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 -// +kubebuilder:webhook:verbs=create;update,path=/mutate-controlplane-cluster-x-k8s-io-v1beta2-rosacontrolplane,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes,versions=v1beta2,name=default.rosacontrolplanes.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 - -var _ webhook.Defaulter = &ROSAControlPlane{} -var _ webhook.Validator = &ROSAControlPlane{} - -// ValidateCreate implements admission.Validator. -func (r *ROSAControlPlane) ValidateCreate() (warnings admission.Warnings, err error) { - var allErrs field.ErrorList - - if err := r.validateVersion(); err != nil { - allErrs = append(allErrs, err) - } - - if len(allErrs) == 0 { - return nil, nil - } - - return nil, apierrors.NewInvalid( - r.GroupVersionKind().GroupKind(), - r.Name, - allErrs, - ) -} - -// ValidateUpdate implements admission.Validator. -func (r *ROSAControlPlane) ValidateUpdate(old runtime.Object) (warnings admission.Warnings, err error) { - var allErrs field.ErrorList - - if err := r.validateVersion(); err != nil { - allErrs = append(allErrs, err) - } - - if len(allErrs) == 0 { - return nil, nil - } - - return nil, apierrors.NewInvalid( - r.GroupVersionKind().GroupKind(), - r.Name, - allErrs, - ) -} - -// ValidateDelete implements admission.Validator. -func (r *ROSAControlPlane) ValidateDelete() (warnings admission.Warnings, err error) { - return nil, nil -} - -func (r *ROSAControlPlane) validateVersion() *field.Error { - _, err := semver.Parse(r.Spec.Version) - if err != nil { - return field.Invalid(field.NewPath("spec.version"), r.Spec.Version, "version must be a valid semantic version") - } - - return nil -} - -// Default implements admission.Defaulter. -func (r *ROSAControlPlane) Default() { - SetObjectDefaults_ROSAControlPlane(r) -} diff --git a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go index 68ab8bae6c..bfb980ac14 100644 --- a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1beta2 import ( "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" apiv1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" "sigs.k8s.io/cluster-api/api/v1beta1" ) diff --git a/exp/api/v1beta2/rosamachinepool_types.go b/exp/api/v1beta2/rosamachinepool_types.go index a94738a944..9ac13ac7ce 100644 --- a/exp/api/v1beta2/rosamachinepool_types.go +++ b/exp/api/v1beta2/rosamachinepool_types.go @@ -37,6 +37,7 @@ type RosaMachinePoolSpec struct { // ROSAControlPlane version is used if not set. // // +optional + // +kubebuilder:validation:XValidation:rule=`self.matches('^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$')`, message="version must be a valid semantic version" Version string `json:"version,omitempty"` // AvailabilityZone is an optinal field specifying the availability zone where instances of this machine pool should run diff --git a/exp/api/v1beta2/rosamachinepool_webhook.go b/exp/api/v1beta2/rosamachinepool_webhook.go deleted file mode 100644 index a3d0b9d227..0000000000 --- a/exp/api/v1beta2/rosamachinepool_webhook.go +++ /dev/null @@ -1,83 +0,0 @@ -package v1beta2 - -import ( - "github.com/blang/semver" - apierrors "k8s.io/apimachinery/pkg/api/errors" - runtime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// SetupWebhookWithManager will setup the webhooks for the ROSAMachinePool. -func (r *ROSAMachinePool) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta2-rosamachinepool,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=rosamachinepools,versions=v1beta2,name=validation.rosamachinepool.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 -// +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta2-rosamachinepool,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=infrastructure.cluster.x-k8s.io,resources=rosamachinepools,versions=v1beta2,name=default.rosamachinepool.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 - -var _ webhook.Defaulter = &ROSAMachinePool{} -var _ webhook.Validator = &ROSAMachinePool{} - -// ValidateCreate implements admission.Validator. -func (r *ROSAMachinePool) ValidateCreate() (warnings admission.Warnings, err error) { - var allErrs field.ErrorList - - if err := r.validateVersion(); err != nil { - allErrs = append(allErrs, err) - } - - if len(allErrs) == 0 { - return nil, nil - } - - return nil, apierrors.NewInvalid( - r.GroupVersionKind().GroupKind(), - r.Name, - allErrs, - ) -} - -// ValidateUpdate implements admission.Validator. -func (r *ROSAMachinePool) ValidateUpdate(old runtime.Object) (warnings admission.Warnings, err error) { - var allErrs field.ErrorList - - if err := r.validateVersion(); err != nil { - allErrs = append(allErrs, err) - } - - if len(allErrs) == 0 { - return nil, nil - } - - return nil, apierrors.NewInvalid( - r.GroupVersionKind().GroupKind(), - r.Name, - allErrs, - ) -} - -// ValidateDelete implements admission.Validator. -func (r *ROSAMachinePool) ValidateDelete() (warnings admission.Warnings, err error) { - return nil, nil -} - -func (r *ROSAMachinePool) validateVersion() *field.Error { - if r.Spec.Version == "" { - return nil - } - _, err := semver.Parse(r.Spec.Version) - if err != nil { - return field.Invalid(field.NewPath("spec.version"), r.Spec.Version, "version must be a valid semantic version") - } - - return nil -} - -// Default implements admission.Defaulter. -func (r *ROSAMachinePool) Default() { -} diff --git a/main.go b/main.go index 8f38b3f49f..32954e6dfd 100644 --- a/main.go +++ b/main.go @@ -254,16 +254,6 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "ROSAMachinePool") os.Exit(1) } - - if err := (&rosacontrolplanev1.ROSAControlPlane{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "ROSAControlPlane") - os.Exit(1) - } - - if err := (&expinfrav1.ROSAMachinePool{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "ROSAMachinePool") - os.Exit(1) - } } // +kubebuilder:scaffold:builder From bf714d8e4d60f86fd79393b694b6c0c7375d9b1c Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Fri, 23 Feb 2024 09:29:21 -0700 Subject: [PATCH 3/3] rosacontrolplane: support a separate billing account Signed-off-by: Steve Kuznetsov --- ...ntrolplane.cluster.x-k8s.io_rosacontrolplanes.yaml | 11 +++++++++++ .../rosa/api/v1beta2/rosacontrolplane_types.go | 10 ++++++++++ .../rosa/controllers/rosacontrolplane_controller.go | 11 +++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml index a0ab9da904..057f6647c7 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml @@ -51,6 +51,17 @@ spec: items: type: string type: array + billingAccount: + description: BillingAccount is an optional AWS account to use for + billing the subscription fees for ROSA clusters. The cost of running + each ROSA cluster will be billed to the infrastructure account in + which the cluster is running. + type: string + x-kubernetes-validations: + - message: billingAccount is immutable + rule: self == oldSelf + - message: billingAccount must be a valid AWS account ID + rule: self.matches('^[0-9]{12}$') controlPlaneEndpoint: description: ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go index b9df205ca1..dfb8688b66 100644 --- a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go +++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go @@ -68,6 +68,16 @@ type RosaControlPlaneSpec struct { //nolint: maligned SupportRoleARN *string `json:"supportRoleARN"` WorkerRoleARN *string `json:"workerRoleARN"` + // +immutable + // +kubebuilder:validation:Optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="billingAccount is immutable" + // +kubebuilder:validation:XValidation:rule="self.matches('^[0-9]{12}$')", message="billingAccount must be a valid AWS account ID" + + // BillingAccount is an optional AWS account to use for billing the subscription fees for ROSA clusters. + // The cost of running each ROSA cluster will be billed to the infrastructure account in which the cluster + // is running. + BillingAccount string `json:"billingAccount,omitempty"` + // CredentialsSecretRef references a secret with necessary credentials to connect to the OCM API. // The secret should contain the following data keys: // - ocmToken: eyJhbGciOiJIUzI1NiIsI.... diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 5b00a49be8..f7ad335d1f 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -200,7 +200,7 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc } if validationMessage, validationError := validateControlPlaneSpec(ocmClient, rosaScope); validationError != nil { - return ctrl.Result{}, fmt.Errorf("validate ROSAControlPlane.spec: %w", err) + return ctrl.Result{}, fmt.Errorf("validate ROSAControlPlane.spec: %w", validationError) } else if validationMessage != "" { rosaScope.ControlPlane.Status.FailureMessage = ptr.To(validationMessage) // dont' requeue because input is invalid and manual intervention is needed. @@ -268,7 +268,13 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc rosaScope.Error(err, "rosacontrolplane.spec.machineCIDR invalid") } + billingAccount := *rosaScope.Identity.Account + if rosaScope.ControlPlane.Spec.BillingAccount != "" { + billingAccount = rosaScope.ControlPlane.Spec.BillingAccount + } + spec := ocm.Spec{ + DryRun: ptr.To(false), Name: rosaScope.RosaClusterName(), Region: *rosaScope.ControlPlane.Spec.Region, MultiAZ: true, @@ -276,6 +282,7 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc ChannelGroup: "stable", Expiration: time.Now().Add(1 * time.Hour), DisableWorkloadMonitoring: ptr.To(true), + DefaultIngress: ocm.NewDefaultIngressSpec(), // n.b. this is a no-op when it's set to the default value SubnetIds: rosaScope.ControlPlane.Spec.Subnets, AvailabilityZones: rosaScope.ControlPlane.Spec.AvailabilityZones, @@ -332,7 +339,7 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc Hypershift: ocm.Hypershift{ Enabled: true, }, - BillingAccount: *rosaScope.Identity.Account, + BillingAccount: billingAccount, AWSCreator: creator, }