From 37135719cf82b29be595a7e6f39ff268bdb6bf74 Mon Sep 17 00:00:00 2001 From: Amber Brown Date: Thu, 17 Mar 2022 13:15:54 +1100 Subject: [PATCH] add managed upgrade operator configuration settings and connected MUO if allowed and a pullsecret exists --- pkg/operator/controllers/muo/config/config.go | 10 + pkg/operator/controllers/muo/deploy.go | 77 +++++-- pkg/operator/controllers/muo/deploy_test.go | 152 ++++++++++++-- .../controllers/muo/muo_controller.go | 79 ++++++- .../controllers/muo/muo_controller_test.go | 194 ++++++++++++++---- 5 files changed, 441 insertions(+), 71 deletions(-) create mode 100644 pkg/operator/controllers/muo/config/config.go diff --git a/pkg/operator/controllers/muo/config/config.go b/pkg/operator/controllers/muo/config/config.go new file mode 100644 index 00000000000..f4819502c11 --- /dev/null +++ b/pkg/operator/controllers/muo/config/config.go @@ -0,0 +1,10 @@ +package config + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +type MUODeploymentConfig struct { + Pullspec string + EnableConnected bool + OCMBaseURL string +} diff --git a/pkg/operator/controllers/muo/deploy.go b/pkg/operator/controllers/muo/deploy.go index e4fb08ffbf3..0aa0643b973 100644 --- a/pkg/operator/controllers/muo/deploy.go +++ b/pkg/operator/controllers/muo/deploy.go @@ -8,36 +8,55 @@ import ( "errors" "strings" + "github.com/ghodss/yaml" + "github.com/ugorji/go/codec" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" kruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" + "github.com/Azure/ARO-RP/pkg/api" arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1" + "github.com/Azure/ARO-RP/pkg/operator/controllers/muo/config" "github.com/Azure/ARO-RP/pkg/util/dynamichelper" "github.com/Azure/ARO-RP/pkg/util/ready" ) +type muoConfig struct { + api.MissingFields + ConfigManager struct { + api.MissingFields + Source string `json:"source,omitempty"` + OcmBaseUrl string `json:"ocmBaseUrl,omitempty"` + LocalConfigName string `json:"localConfigName,omitempty"` + } `json:"configManager,omitempty"` +} + type Deployer interface { - CreateOrUpdate(context.Context, *arov1alpha1.Cluster) error + CreateOrUpdate(context.Context, *arov1alpha1.Cluster, *config.MUODeploymentConfig) error Remove(context.Context) error IsReady(ctx context.Context) (bool, error) - Resources(string) ([]kruntime.Object, error) + Resources(*config.MUODeploymentConfig) ([]kruntime.Object, error) } type deployer struct { kubernetescli kubernetes.Interface dh dynamichelper.Interface + + jsonHandle *codec.JsonHandle } func newDeployer(kubernetescli kubernetes.Interface, dh dynamichelper.Interface) Deployer { return &deployer{ kubernetescli: kubernetescli, dh: dh, + + jsonHandle: new(codec.JsonHandle), } } -func (o *deployer) Resources(pullspec string) ([]kruntime.Object, error) { +func (o *deployer) Resources(config *config.MUODeploymentConfig) ([]kruntime.Object, error) { results := []kruntime.Object{} for _, assetName := range AssetNames() { b, err := Asset(assetName) @@ -53,7 +72,46 @@ func (o *deployer) Resources(pullspec string) ([]kruntime.Object, error) { // set the image for the deployments if d, ok := obj.(*appsv1.Deployment); ok { for i := range d.Spec.Template.Spec.Containers { - d.Spec.Template.Spec.Containers[i].Image = pullspec + d.Spec.Template.Spec.Containers[i].Image = config.Pullspec + } + } + + if cm, ok := obj.(*corev1.ConfigMap); ok { + if cm.Name == "managed-upgrade-operator-config" && cm.Namespace == "openshift-managed-upgrade-operator" { + // read the config.yaml from the MUO ConfigMap which stores defaults + configDataJSON, err := yaml.YAMLToJSON([]byte(cm.Data["config.yaml"])) + if err != nil { + return nil, err + } + + var configData muoConfig + err = codec.NewDecoderBytes(configDataJSON, o.jsonHandle).Decode(&configData) + if err != nil { + return nil, err + } + + if config.EnableConnected { + configData.ConfigManager.Source = "OCM" + configData.ConfigManager.OcmBaseUrl = config.OCMBaseURL + configData.ConfigManager.LocalConfigName = "" + } else { + configData.ConfigManager.Source = "LOCAL" + configData.ConfigManager.LocalConfigName = "managed-upgrade-config" + configData.ConfigManager.OcmBaseUrl = "" + } + + // Write the yaml back into the ConfigMap + var b []byte + err = codec.NewEncoderBytes(&b, o.jsonHandle).Encode(configData) + if err != nil { + return nil, err + } + + cmYaml, err := yaml.JSONToYAML(b) + if err != nil { + return nil, err + } + cm.Data["config.yaml"] = string(cmYaml) } } @@ -63,13 +121,8 @@ func (o *deployer) Resources(pullspec string) ([]kruntime.Object, error) { return results, nil } -func (o *deployer) CreateOrUpdate(ctx context.Context, cluster *arov1alpha1.Cluster) error { - imagePullspec, ext := cluster.Spec.OperatorFlags[controllerPullSpec] - if !ext { - return errors.New("missing pullspec") - } - - resources, err := o.Resources(imagePullspec) +func (o *deployer) CreateOrUpdate(ctx context.Context, cluster *arov1alpha1.Cluster, config *config.MUODeploymentConfig) error { + resources, err := o.Resources(config) if err != nil { return err } @@ -88,7 +141,7 @@ func (o *deployer) CreateOrUpdate(ctx context.Context, cluster *arov1alpha1.Clus } func (o *deployer) Remove(ctx context.Context) error { - resources, err := o.Resources("") + resources, err := o.Resources(&config.MUODeploymentConfig{}) if err != nil { return err } diff --git a/pkg/operator/controllers/muo/deploy_test.go b/pkg/operator/controllers/muo/deploy_test.go index 400cfd7e6f4..26cba61550c 100644 --- a/pkg/operator/controllers/muo/deploy_test.go +++ b/pkg/operator/controllers/muo/deploy_test.go @@ -6,17 +6,20 @@ package muo import ( "context" "errors" + "strings" "testing" "github.com/go-test/deep" "github.com/golang/mock/gomock" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1" + "github.com/Azure/ARO-RP/pkg/operator/controllers/muo/config" mock_dynamichelper "github.com/Azure/ARO-RP/pkg/util/mocks/dynamichelper" ) @@ -29,18 +32,13 @@ func TestDeployCreateOrUpdateCorrectKinds(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: arov1alpha1.SingletonClusterName, }, - Spec: arov1alpha1.ClusterSpec{ - OperatorFlags: arov1alpha1.OperatorFlags{ - controllerPullSpec: setPullSpec, - }, - }, } k8scli := fake.NewSimpleClientset() dh := mock_dynamichelper.NewMockInterface(controller) // When the DynamicHelper is called, count the number of objects it creates - // and capture any deployments so that we can check the pull secret + // and capture any deployments so that we can check the pullspec var deployments []*appsv1.Deployment deployedObjects := make(map[string]int) check := func(ctx context.Context, objs ...kruntime.Object) error { @@ -50,6 +48,9 @@ func TestDeployCreateOrUpdateCorrectKinds(t *testing.T) { if err != nil { return err } + if d, ok := i.(*appsv1.Deployment); ok { + deployments = append(deployments, d) + } deployedObjects[kind] = deployedObjects[kind] + 1 } return nil @@ -57,7 +58,7 @@ func TestDeployCreateOrUpdateCorrectKinds(t *testing.T) { dh.EXPECT().Ensure(gomock.Any(), gomock.Any()).Do(check).Return(nil) deployer := newDeployer(k8scli, dh) - err := deployer.CreateOrUpdate(context.Background(), cluster) + err := deployer.CreateOrUpdate(context.Background(), cluster, &config.MUODeploymentConfig{Pullspec: setPullSpec}) if err != nil { t.Error(err) } @@ -65,7 +66,7 @@ func TestDeployCreateOrUpdateCorrectKinds(t *testing.T) { // We expect these numbers of resources to be created expectedKinds := map[string]int{ "ClusterRole": 1, - "ConfigMap": 1, + "ConfigMap": 2, "ClusterRoleBinding": 1, "CustomResourceDefinition": 1, "Deployment": 1, @@ -98,11 +99,6 @@ func TestDeployCreateOrUpdateSetsOwnerReferences(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: arov1alpha1.SingletonClusterName, }, - Spec: arov1alpha1.ClusterSpec{ - OperatorFlags: arov1alpha1.OperatorFlags{ - controllerPullSpec: setPullSpec, - }, - }, } k8scli := fake.NewSimpleClientset() @@ -134,7 +130,7 @@ func TestDeployCreateOrUpdateSetsOwnerReferences(t *testing.T) { dh.EXPECT().Ensure(gomock.Any(), gomock.Any()).Do(check).Return(nil) deployer := newDeployer(k8scli, dh) - err := deployer.CreateOrUpdate(context.Background(), cluster) + err := deployer.CreateOrUpdate(context.Background(), cluster, &config.MUODeploymentConfig{Pullspec: setPullSpec}) if err != nil { t.Error(err) } @@ -224,3 +220,131 @@ func TestDeployIsReadyMissing(t *testing.T) { } } + +func TestDeployConfig(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + cluster := &arov1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: arov1alpha1.SingletonClusterName, + }, + } + + tests := []struct { + name string + deploymentConfig *config.MUODeploymentConfig + expected []string + }{ + { + name: "local", + deploymentConfig: &config.MUODeploymentConfig{EnableConnected: false}, + expected: []string{ + "configManager:", + " localConfigName: managed-upgrade-config", + " source: LOCAL", + " watchInterval: 1", + "healthCheck:", + " ignoredCriticals:", + " - PrometheusRuleFailures", + " - CannotRetrieveUpdates", + " - FluentdNodeDown", + " ignoredNamespaces:", + " - openshift-logging", + " - openshift-redhat-marketplace", + " - openshift-operators", + " - openshift-user-workload-monitoring", + " - openshift-pipelines", + "maintenance:", + " controlPlaneTime: 90", + " ignoredAlerts:", + " controlPlaneCriticals:", + " - ClusterOperatorDown", + " - ClusterOperatorDegraded", + "nodeDrain:", + " expectedNodeDrainTime: 8", + " timeOut: 45", + "scale:", + " timeOut: 30", + "upgradeWindow:", + " delayTrigger: 30", + " timeOut: 120", + "", + }, + }, + { + name: "connected", + deploymentConfig: &config.MUODeploymentConfig{EnableConnected: true, OCMBaseURL: "https://example.com"}, + expected: []string{ + "configManager:", + " ocmBaseUrl: https://example.com", + " source: OCM", + " watchInterval: 1", + "healthCheck:", + " ignoredCriticals:", + " - PrometheusRuleFailures", + " - CannotRetrieveUpdates", + " - FluentdNodeDown", + " ignoredNamespaces:", + " - openshift-logging", + " - openshift-redhat-marketplace", + " - openshift-operators", + " - openshift-user-workload-monitoring", + " - openshift-pipelines", + "maintenance:", + " controlPlaneTime: 90", + " ignoredAlerts:", + " controlPlaneCriticals:", + " - ClusterOperatorDown", + " - ClusterOperatorDegraded", + "nodeDrain:", + " expectedNodeDrainTime: 8", + " timeOut: 45", + "scale:", + " timeOut: 30", + "upgradeWindow:", + " delayTrigger: 30", + " timeOut: 120", + "", + }, + }, + } + for _, tt := range tests { + k8scli := fake.NewSimpleClientset() + dh := mock_dynamichelper.NewMockInterface(controller) + + // When the DynamicHelper is called, capture configmaps to inspect them + var configs []*corev1.ConfigMap + check := func(ctx context.Context, objs ...kruntime.Object) error { + for _, i := range objs { + if cm, ok := i.(*corev1.ConfigMap); ok { + configs = append(configs, cm) + } + + } + return nil + } + dh.EXPECT().Ensure(gomock.Any(), gomock.Any()).Do(check).Return(nil) + + deployer := newDeployer(k8scli, dh) + err := deployer.CreateOrUpdate(context.Background(), cluster, tt.deploymentConfig) + if err != nil { + t.Error(err) + } + + foundConfig := false + for _, cms := range configs { + if cms.Name == "managed-upgrade-operator-config" && cms.Namespace == "openshift-managed-upgrade-operator" { + foundConfig = true + errs := deep.Equal(tt.expected, strings.Split(cms.Data["config.yaml"], "\n")) + for _, e := range errs { + t.Error(e) + } + } + } + + if !foundConfig { + t.Error("MUO config was not found") + } + } +} diff --git a/pkg/operator/controllers/muo/muo_controller.go b/pkg/operator/controllers/muo/muo_controller.go index 0f8ca2531ae..c3190f73cc5 100644 --- a/pkg/operator/controllers/muo/muo_controller.go +++ b/pkg/operator/controllers/muo/muo_controller.go @@ -5,11 +5,14 @@ package muo import ( "context" + "errors" "fmt" "strings" "time" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" @@ -20,20 +23,36 @@ import ( arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1" aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned" + "github.com/Azure/ARO-RP/pkg/operator/controllers/muo/config" "github.com/Azure/ARO-RP/pkg/util/dynamichelper" + "github.com/Azure/ARO-RP/pkg/util/pullsecret" ) const ( ControllerName = "ManagedUpgradeOperator" - controllerEnabled = "rh.srep.muo.enabled" - controllerManaged = "rh.srep.muo.managed" - controllerPullSpec = "rh.srep.muo.deploy.pullspec" + controllerEnabled = "rh.srep.muo.enabled" + controllerManaged = "rh.srep.muo.managed" + controllerPullSpec = "rh.srep.muo.deploy.pullspec" + controllerAllowOCM = "rh.srep.muo.deploy.allowOCM" + controllerOcmBaseURL = "rh.srep.muo.deploy.ocmBaseUrl" + controllerOcmBaseURLDefaultValue = "https://api.openshift.com" + + pullSecretOCMKey = "cloud.redhat.com" ) +var pullSecretName = types.NamespacedName{Name: "pull-secret", Namespace: "openshift-config"} + +type MUODeploymentConfig struct { + Pullspec string + ConnectToOCM bool + OCMBaseURL string +} + type Reconciler struct { - arocli aroclient.Interface - deployer Deployer + arocli aroclient.Interface + kubernetescli kubernetes.Interface + deployer Deployer readinessPollTime time.Duration readinessTimeout time.Duration @@ -41,8 +60,9 @@ type Reconciler struct { func NewReconciler(arocli aroclient.Interface, kubernetescli kubernetes.Interface, dh dynamichelper.Interface) *Reconciler { return &Reconciler{ - arocli: arocli, - deployer: newDeployer(kubernetescli, dh), + arocli: arocli, + kubernetescli: kubernetescli, + deployer: newDeployer(kubernetescli, dh), readinessPollTime: 10 * time.Second, readinessTimeout: 5 * time.Minute, @@ -66,7 +86,48 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl. // If enabled and managed=false, remove the MUO deployment // If enabled and managed is missing, do nothing if strings.EqualFold(managed, "true") { - err = r.deployer.CreateOrUpdate(ctx, instance) + imagePullspec, ext := instance.Spec.OperatorFlags[controllerPullSpec] + if !ext { + return reconcile.Result{}, errors.New("missing pullspec") + } + + config := &config.MUODeploymentConfig{ + Pullspec: imagePullspec, + } + + allowOCM := instance.Spec.OperatorFlags.GetSimpleBoolean(controllerAllowOCM) + if allowOCM { + useOCM := func() bool { + var userSecret *corev1.Secret + + userSecret, err = r.kubernetescli.CoreV1().Secrets(pullSecretName.Namespace).Get(ctx, pullSecretName.Name, metav1.GetOptions{}) + if err != nil { + // if a pullsecret doesn't exist/etc, fallback to local + return false + } + + parsedKeys, err := pullsecret.UnmarshalSecretData(userSecret) + if err != nil { + // if we can't parse the pullsecret, fallback to local + return false + } + + // check for the key that connects the cluster to OCM (since + // clusters may have a RH registry pull secret but not the OCM + // one if they choose) + _, foundKey := parsedKeys[pullSecretOCMKey] + return foundKey + }() + + // if we have a valid pullsecret, enable connected MUO + if useOCM { + config.EnableConnected = true + config.OCMBaseURL = instance.Spec.OperatorFlags.GetWithDefault(controllerOcmBaseURL, controllerOcmBaseURLDefaultValue) + } + } + + // Deploy the MUO manifests and config + err = r.deployer.CreateOrUpdate(ctx, instance, config) if err != nil { return reconcile.Result{}, err } @@ -100,7 +161,7 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { builder := ctrl.NewControllerManagedBy(mgr). For(&arov1alpha1.Cluster{}, builder.WithPredicates(aroClusterPredicate)) - resources, err := r.deployer.Resources("") + resources, err := r.deployer.Resources(&config.MUODeploymentConfig{}) if err != nil { return err } diff --git a/pkg/operator/controllers/muo/muo_controller_test.go b/pkg/operator/controllers/muo/muo_controller_test.go index 404e5323b0c..c5e6d07f918 100644 --- a/pkg/operator/controllers/muo/muo_controller_test.go +++ b/pkg/operator/controllers/muo/muo_controller_test.go @@ -6,16 +6,18 @@ package muo import ( "context" "errors" - "strconv" "testing" "time" "github.com/golang/mock/gomock" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1" arofake "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned/fake" + "github.com/Azure/ARO-RP/pkg/operator/controllers/muo/config" mock_muo "github.com/Azure/ARO-RP/pkg/operator/mocks/muo" ) @@ -23,66 +25,176 @@ func TestMUOReconciler(t *testing.T) { tests := []struct { name string mocks func(*mock_muo.MockDeployer, *arov1alpha1.Cluster) - // feature flag options - enabled bool - managed string + flags arov1alpha1.OperatorFlags + // connected MUO -- cluster pullsecret + pullsecret string // errors wantErr string }{ { - name: "disabled", - enabled: false, - managed: "false", + name: "disabled", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "false", + controllerManaged: "false", + controllerPullSpec: "wonderfulPullspec", + }, + }, + { + name: "managed", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "true", + controllerPullSpec: "wonderfulPullspec", + }, + mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) { + expectedConfig := &config.MUODeploymentConfig{ + Pullspec: "wonderfulPullspec", + } + md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil) + md.EXPECT().IsReady(gomock.Any()).Return(true, nil) + }, + }, + { + name: "managed, no pullspec", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "true", + }, + wantErr: "missing pullspec", + }, + { + name: "managed, OCM allowed but no pull secret", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "true", + controllerAllowOCM: "true", + controllerPullSpec: "wonderfulPullspec", + }, + pullsecret: "{\"auths\": {}}", + mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) { + expectedConfig := &config.MUODeploymentConfig{ + Pullspec: "wonderfulPullspec", + EnableConnected: false, + } + md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil) + md.EXPECT().IsReady(gomock.Any()).Return(true, nil) + }, + }, + { + name: "managed, OCM allowed but mangled pullsecret", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "true", + controllerAllowOCM: "true", + controllerPullSpec: "wonderfulPullspec", + }, + pullsecret: "i'm a little json, short and stout", + mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) { + expectedConfig := &config.MUODeploymentConfig{ + Pullspec: "wonderfulPullspec", + EnableConnected: false, + } + md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil) + md.EXPECT().IsReady(gomock.Any()).Return(true, nil) + }, + }, + { + name: "managed, OCM connected mode", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "true", + controllerAllowOCM: "true", + controllerPullSpec: "wonderfulPullspec", + }, + pullsecret: "{\"auths\": {\"" + pullSecretOCMKey + "\": {\"auth\": \"secret value\"}}}", + mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) { + expectedConfig := &config.MUODeploymentConfig{ + Pullspec: "wonderfulPullspec", + EnableConnected: true, + OCMBaseURL: "https://api.openshift.com", + } + md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil) + md.EXPECT().IsReady(gomock.Any()).Return(true, nil) + }, }, { - name: "managed", - enabled: true, - managed: "true", + name: "managed, OCM connected mode, custom OCM URL", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "true", + controllerAllowOCM: "true", + controllerOcmBaseURL: "https://example.com", + controllerPullSpec: "wonderfulPullspec", + }, + pullsecret: "{\"auths\": {\"" + pullSecretOCMKey + "\": {\"auth\": \"secret value\"}}}", mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) { - md.EXPECT().CreateOrUpdate(gomock.Any(), cluster).Return(nil) + expectedConfig := &config.MUODeploymentConfig{ + Pullspec: "wonderfulPullspec", + EnableConnected: true, + OCMBaseURL: "https://example.com", + } + md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil) md.EXPECT().IsReady(gomock.Any()).Return(true, nil) }, }, { - name: "managed, MUO does not become ready", - enabled: true, - managed: "true", + name: "managed, MUO does not become ready", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "true", + controllerPullSpec: "wonderfulPullspec", + }, mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) { - md.EXPECT().CreateOrUpdate(gomock.Any(), cluster).Return(nil) + expectedConfig := &config.MUODeploymentConfig{ + Pullspec: "wonderfulPullspec", + } + md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil) md.EXPECT().IsReady(gomock.Any()).Return(false, nil) }, wantErr: "Managed Upgrade Operator deployment timed out on Ready: timed out waiting for the condition", }, { - name: "managed, CreateOrUpdate() fails", - enabled: true, - managed: "true", + name: "managed, CreateOrUpdate() fails", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "true", + controllerPullSpec: "wonderfulPullspec", + }, mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) { - md.EXPECT().CreateOrUpdate(gomock.Any(), cluster).Return(errors.New("failed ensure")) + md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, gomock.AssignableToTypeOf(&config.MUODeploymentConfig{})).Return(errors.New("failed ensure")) }, wantErr: "failed ensure", }, { - name: "managed=false (removal)", - enabled: true, - managed: "false", + name: "managed=false (removal)", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "false", + controllerPullSpec: "wonderfulPullspec", + }, mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) { md.EXPECT().Remove(gomock.Any()).Return(nil) }, }, { - name: "managed=false (removal), Remove() fails", - enabled: true, - managed: "false", + name: "managed=false (removal), Remove() fails", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "false", + controllerPullSpec: "wonderfulPullspec", + }, mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) { md.EXPECT().Remove(gomock.Any()).Return(errors.New("failed delete")) }, wantErr: "failed delete", }, { - name: "managed=blank (no action)", - enabled: true, - managed: "", + name: "managed=blank (no action)", + flags: arov1alpha1.OperatorFlags{ + controllerEnabled: "true", + controllerManaged: "", + controllerPullSpec: "wonderfulPullspec", + }, }, } for _, tt := range tests { @@ -95,25 +207,35 @@ func TestMUOReconciler(t *testing.T) { Name: arov1alpha1.SingletonClusterName, }, Spec: arov1alpha1.ClusterSpec{ - OperatorFlags: arov1alpha1.OperatorFlags{ - controllerEnabled: strconv.FormatBool(tt.enabled), - controllerPullSpec: "wonderfulPullspec", - }, + OperatorFlags: tt.flags, }, } - // nil and empty string are valid values, be careful to preserve them - if tt.managed != "" { - cluster.Spec.OperatorFlags[controllerManaged] = tt.managed - } arocli := arofake.NewSimpleClientset(cluster) + kubecli := fake.NewSimpleClientset() deployer := mock_muo.NewMockDeployer(controller) + if tt.pullsecret != "" { + _, err := kubecli.CoreV1().Secrets(pullSecretName.Namespace).Create(context.Background(), + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: pullSecretName.Name, + Namespace: pullSecretName.Namespace, + }, + Data: map[string][]byte{corev1.DockerConfigJsonKey: []byte(tt.pullsecret)}, + }, + metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + } + if tt.mocks != nil { tt.mocks(deployer, cluster) } r := &Reconciler{ arocli: arocli, + kubernetescli: kubecli, deployer: deployer, readinessTimeout: 0 * time.Second, readinessPollTime: 1 * time.Second,