diff --git a/operators/multiclusterobservability/controllers/multiclusterobservability/multiclusterobservability_controller.go b/operators/multiclusterobservability/controllers/multiclusterobservability/multiclusterobservability_controller.go index 0707035ea..5df953d9e 100644 --- a/operators/multiclusterobservability/controllers/multiclusterobservability/multiclusterobservability_controller.go +++ b/operators/multiclusterobservability/controllers/multiclusterobservability/multiclusterobservability_controller.go @@ -91,7 +91,7 @@ type MultiClusterObservabilityReconciler struct { CRDMap map[string]bool APIReader client.Reader RESTMapper meta.RESTMapper - ImageClient *imagev1client.ImageV1Client + ImageClient imagev1client.ImageV1Interface } // +kubebuilder:rbac:groups=observability.open-cluster-management.io,resources=multiclusterobservabilities,verbs=get;list;watch;create;update;patch;delete diff --git a/operators/multiclusterobservability/controllers/multiclusterobservability/multiclusterobservability_controller_test.go b/operators/multiclusterobservability/controllers/multiclusterobservability/multiclusterobservability_controller_test.go index 3522595bf..dc7343a84 100644 --- a/operators/multiclusterobservability/controllers/multiclusterobservability/multiclusterobservability_controller_test.go +++ b/operators/multiclusterobservability/controllers/multiclusterobservability/multiclusterobservability_controller_test.go @@ -47,6 +47,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log/zap" migrationv1alpha1 "sigs.k8s.io/kube-storage-version-migrator/pkg/apis/migration/v1alpha1" + + imagev1 "github.com/openshift/api/image/v1" + fakeimageclient "github.com/openshift/client-go/image/clientset/versioned/fake" + fakeimagev1client "github.com/openshift/client-go/image/clientset/versioned/typed/image/v1/fake" ) func init() { @@ -350,8 +354,32 @@ func TestMultiClusterMonitoringCRUpdate(t *testing.T) { ). Build() + // Create fake imagestream client + imageClient := &fakeimagev1client.FakeImageV1{Fake: &(fakeimageclient.NewSimpleClientset().Fake)} + _, err := imageClient.ImageStreams(config.OauthProxyImageStreamNamespace).Create(context.Background(), + &imagev1.ImageStream{ + ObjectMeta: metav1.ObjectMeta{ + Name: config.OauthProxyImageStreamName, + Namespace: config.OauthProxyImageStreamNamespace, + }, + Spec: imagev1.ImageStreamSpec{ + Tags: []imagev1.TagReference{ + { + Name: "v4.4", + From: &corev1.ObjectReference{ + Kind: "DockerImage", + Name: "quay.io/openshift-release-dev/ocp-v4.0-art-dev", + }, + }, + }, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + // Create a ReconcileMemcached object with the scheme and fake client. - r := &MultiClusterObservabilityReconciler{Client: cl, Scheme: s, CRDMap: map[string]bool{config.IngressControllerCRD: true}} + r := &MultiClusterObservabilityReconciler{Client: cl, Scheme: s, CRDMap: map[string]bool{config.IngressControllerCRD: true}, ImageClient: imageClient} config.SetMonitoringCRName(name) // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -363,7 +391,7 @@ func TestMultiClusterMonitoringCRUpdate(t *testing.T) { // Create empty client. The test secret specified in MCO is not yet created. t.Log("Reconcile empty client") - _, err := r.Reconcile(context.TODO(), req) + _, err = r.Reconcile(context.TODO(), req) if err != nil { t.Fatalf("reconcile: (%v)", err) } @@ -759,8 +787,32 @@ func TestImageReplaceForMCO(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + // Create fake imagestream client + imageClient := &fakeimagev1client.FakeImageV1{Fake: &(fakeimageclient.NewSimpleClientset().Fake)} + _, err := imageClient.ImageStreams(config.OauthProxyImageStreamNamespace).Create(context.Background(), + &imagev1.ImageStream{ + ObjectMeta: metav1.ObjectMeta{ + Name: config.OauthProxyImageStreamName, + Namespace: config.OauthProxyImageStreamNamespace, + }, + Spec: imagev1.ImageStreamSpec{ + Tags: []imagev1.TagReference{ + { + Name: "v4.4", + From: &corev1.ObjectReference{ + Kind: "DockerImage", + Name: "quay.io/openshift-release-dev/ocp-v4.0-art-dev", + }, + }, + }, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + // Create a ReconcileMemcached object with the scheme and fake client. - r := &MultiClusterObservabilityReconciler{Client: cl, Scheme: s, CRDMap: map[string]bool{config.MCHCrdName: true, config.IngressControllerCRD: true}} + r := &MultiClusterObservabilityReconciler{Client: cl, Scheme: s, CRDMap: map[string]bool{config.MCHCrdName: true, config.IngressControllerCRD: true}, ImageClient: imageClient} config.SetMonitoringCRName(name) // Mock request to simulate Reconcile() being called on an event for a watched resource . @@ -775,7 +827,7 @@ func TestImageReplaceForMCO(t *testing.T) { config.SetImageManifests(testImagemanifestsMap) // trigger another reconcile for MCH update event - _, err := r.Reconcile(context.TODO(), req) + _, err = r.Reconcile(context.TODO(), req) if err != nil { t.Fatalf("reconcile: (%v)", err) } diff --git a/operators/multiclusterobservability/pkg/rendering/renderer.go b/operators/multiclusterobservability/pkg/rendering/renderer.go index b61ce6026..ba2b5ce63 100644 --- a/operators/multiclusterobservability/pkg/rendering/renderer.go +++ b/operators/multiclusterobservability/pkg/rendering/renderer.go @@ -10,6 +10,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/discovery" logf "sigs.k8s.io/controller-runtime/pkg/log" obv1beta2 "github.com/stolostron/multicluster-observability-operator/operators/multiclusterobservability/api/v1beta2" @@ -29,7 +30,7 @@ type RendererOptions struct { type MCORenderer struct { kubeClient client.Client - imageClient *imagev1client.ImageV1Client + imageClient imagev1client.ImageV1Interface renderer *rendererutil.Renderer cr *obv1beta2.MultiClusterObservability rendererOptions *RendererOptions @@ -40,7 +41,7 @@ type MCORenderer struct { renderMCOAFns map[string]rendererutil.RenderFn } -func NewMCORenderer(multipleClusterMonitoring *obv1beta2.MultiClusterObservability, kubeClient client.Client, imageClient *imagev1client.ImageV1Client) *MCORenderer { +func NewMCORenderer(multipleClusterMonitoring *obv1beta2.MultiClusterObservability, kubeClient client.Client, imageClient imagev1client.ImageV1Interface) *MCORenderer { mcoRenderer := &MCORenderer{ renderer: rendererutil.NewRenderer(), cr: multipleClusterMonitoring, @@ -193,3 +194,22 @@ func (r *MCORenderer) MCOAResources(namespace string, labels map[string]string) return mcoaResources, nil } + +func (r *MCORenderer) HasImagestream() bool { + dcl := discovery.NewDiscoveryClient(r.imageClient.RESTClient()) + + apiList, err := dcl.ServerGroups() + if err != nil { + log.Error(err, "unable to get ServerGroups from imagestream detection") + return false + } + + apiGroups := apiList.Groups + for i := 0; i < len(apiGroups); i++ { + if apiGroups[i].Name == "image.openshift.io" { + return true + } + } + + return false +} diff --git a/operators/multiclusterobservability/pkg/rendering/renderer_alertmanager.go b/operators/multiclusterobservability/pkg/rendering/renderer_alertmanager.go index da951cb7c..78a8e7ebf 100644 --- a/operators/multiclusterobservability/pkg/rendering/renderer_alertmanager.go +++ b/operators/multiclusterobservability/pkg/rendering/renderer_alertmanager.go @@ -123,9 +123,14 @@ func (r *MCORenderer) renderAlertManagerStatefulSet(res *resource.Resource, name configReloaderContainer.Image = image } + // If we're on OCP and has imagestreams, we always want the oauth image + // from the imagestream, and fail the reconcile if we don't find it. + // If we're on non-OCP (tests) we use the base template image found, image = mcoconfig.GetOauthProxyImage(r.imageClient) if found { oauthProxyContainer.Image = image + } else if r.HasImagestream() { + return nil, fmt.Errorf("failed to get OAuth image for alertmanager") } oauthProxyContainer.ImagePullPolicy = imagePullPolicy diff --git a/operators/multiclusterobservability/pkg/rendering/renderer_alertmanager_test.go b/operators/multiclusterobservability/pkg/rendering/renderer_alertmanager_test.go index db3819cac..eed473b35 100644 --- a/operators/multiclusterobservability/pkg/rendering/renderer_alertmanager_test.go +++ b/operators/multiclusterobservability/pkg/rendering/renderer_alertmanager_test.go @@ -5,6 +5,7 @@ package rendering import ( + "context" "fmt" "os" "path/filepath" @@ -12,6 +13,9 @@ import ( "strings" "testing" + imagev1 "github.com/openshift/api/image/v1" + fakeimageclient "github.com/openshift/client-go/image/clientset/versioned/fake" + fakeimagev1client "github.com/openshift/client-go/image/clientset/versioned/typed/image/v1/fake" mcoshared "github.com/stolostron/multicluster-observability-operator/operators/multiclusterobservability/api/shared" mcov1beta2 "github.com/stolostron/multicluster-observability-operator/operators/multiclusterobservability/api/v1beta2" "github.com/stolostron/multicluster-observability-operator/operators/multiclusterobservability/pkg/config" @@ -288,7 +292,31 @@ func renderTemplates(t *testing.T, kubeClient client.Client, mco *mcov1beta2.Mul defer os.Unsetenv(templatesutil.TemplatesPathEnvVar) config.ReadImageManifestConfigMap(kubeClient, "v1") - renderer := NewMCORenderer(mco, kubeClient, nil) + + imageClient := &fakeimagev1client.FakeImageV1{Fake: &(fakeimageclient.NewSimpleClientset().Fake)} + _, err = imageClient.ImageStreams(config.OauthProxyImageStreamNamespace).Create(context.Background(), + &imagev1.ImageStream{ + ObjectMeta: metav1.ObjectMeta{ + Name: config.OauthProxyImageStreamName, + Namespace: config.OauthProxyImageStreamNamespace, + }, + Spec: imagev1.ImageStreamSpec{ + Tags: []imagev1.TagReference{ + { + Name: "v4.4", + From: &corev1.ObjectReference{ + Kind: "DockerImage", + Name: "quay.io/openshift-release-dev/ocp-v4.0-art-dev", + }, + }, + }, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + renderer := NewMCORenderer(mco, kubeClient, imageClient) //load and render alertmanager templates alertTemplates, err := templates.GetOrLoadAlertManagerTemplates(templatesutil.GetTemplateRenderer()) diff --git a/operators/multiclusterobservability/pkg/rendering/renderer_grafana.go b/operators/multiclusterobservability/pkg/rendering/renderer_grafana.go index d60e8af23..9e4a2d038 100644 --- a/operators/multiclusterobservability/pkg/rendering/renderer_grafana.go +++ b/operators/multiclusterobservability/pkg/rendering/renderer_grafana.go @@ -5,6 +5,8 @@ package rendering import ( + "fmt" + v1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -68,9 +70,14 @@ func (r *MCORenderer) renderGrafanaDeployments(res *resource.Resource, } spec.Containers[1].ImagePullPolicy = imagePullPolicy + // If we're on OCP and has imagestreams, we always want the oauth image + // from the imagestream, and fail the reconcile if we don't find it. + // If we're on non-OCP (tests) we use the base template image found, image = config.GetOauthProxyImage(r.imageClient) if found { spec.Containers[2].Image = image + } else if r.HasImagestream() { + return nil, fmt.Errorf("failed to get OAuth image for alertmanager") } spec.Containers[2].ImagePullPolicy = imagePullPolicy diff --git a/operators/multiclusterobservability/pkg/rendering/renderer_proxy.go b/operators/multiclusterobservability/pkg/rendering/renderer_proxy.go index 4c9ddf3e5..8a9b711ce 100644 --- a/operators/multiclusterobservability/pkg/rendering/renderer_proxy.go +++ b/operators/multiclusterobservability/pkg/rendering/renderer_proxy.go @@ -5,6 +5,7 @@ package rendering import ( + "fmt" "strings" v1 "k8s.io/api/apps/v1" @@ -93,9 +94,14 @@ func (r *MCORenderer) renderProxyDeployment(res *resource.Resource, spec.Containers[0].Image = image } + // If we're on OCP and has imagestreams, we always want the oauth image + // from the imagestream, and fail the reconcile if we don't find it. + // If we're on non-OCP (tests) we use the base template image found, image = mcoconfig.GetOauthProxyImage(r.imageClient) if found { spec.Containers[1].Image = image + } else if r.HasImagestream() { + return nil, fmt.Errorf("failed to get OAuth image for alertmanager") } for idx := range spec.Volumes { diff --git a/operators/multiclusterobservability/pkg/rendering/renderer_test.go b/operators/multiclusterobservability/pkg/rendering/renderer_test.go index 313091832..49379b008 100644 --- a/operators/multiclusterobservability/pkg/rendering/renderer_test.go +++ b/operators/multiclusterobservability/pkg/rendering/renderer_test.go @@ -66,7 +66,30 @@ func TestRender(t *testing.T) { } kubeClient := fake.NewClientBuilder().WithObjects(clientCa).Build() - renderer := NewMCORenderer(mchcr, kubeClient, nil) + imageClient := &fakeimagev1client.FakeImageV1{Fake: &(fakeimageclient.NewSimpleClientset().Fake)} + _, err = imageClient.ImageStreams(config.OauthProxyImageStreamNamespace).Create(context.Background(), + &imagev1.ImageStream{ + ObjectMeta: metav1.ObjectMeta{ + Name: config.OauthProxyImageStreamName, + Namespace: config.OauthProxyImageStreamNamespace, + }, + Spec: imagev1.ImageStreamSpec{ + Tags: []imagev1.TagReference{ + { + Name: "v4.4", + From: &corev1.ObjectReference{ + Kind: "DockerImage", + Name: "quay.io/openshift-release-dev/ocp-v4.0-art-dev", + }, + }, + }, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + renderer := NewMCORenderer(mchcr, kubeClient, imageClient) _, err = renderer.Render() if err != nil { t.Fatalf("failed to render MultiClusterObservability: %v", err)