Skip to content

Commit

Permalink
✨ load operator image from operator instead of hardcoded (#1027)
Browse files Browse the repository at this point in the history
* load operator image from operator instead of hardcoded

Signed-off-by: Ivan Milchev <[email protected]>

* manually resolve image

Signed-off-by: Ivan Milchev <[email protected]>

* fix unit tests

Signed-off-by: Ivan Milchev <[email protected]>

* fix container resolution flag for mondoo operator images

Signed-off-by: Ivan Milchev <[email protected]>

* refactor operator image resolving

Signed-off-by: Ivan Milchev <[email protected]>

---------

Signed-off-by: Ivan Milchev <[email protected]>
  • Loading branch information
imilchev authored Feb 27, 2024
1 parent 0d6409b commit fca6dfd
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 43 deletions.
2 changes: 1 addition & 1 deletion cmd/mondoo-operator/operator/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func init() {
if err = (&controllers.MondooAuditConfigReconciler{
Client: mgr.GetClient(),
MondooClientBuilder: controllers.MondooClientBuilder,
ContainerImageResolver: mondoo.NewContainerImageResolver(isOpenShift),
ContainerImageResolver: mondoo.NewContainerImageResolver(mgr.GetClient(), isOpenShift),
StatusReporter: status.NewStatusReporter(mgr.GetClient(), controllers.MondooClientBuilder, v),
RunningOnOpenShift: isOpenShift,
ScanApiStore: scanApiStore,
Expand Down
9 changes: 9 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ spec:
args:
- operator
- --leader-elect
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: controller:latest
imagePullPolicy: IfNotPresent
name: manager
Expand Down
2 changes: 1 addition & 1 deletion controllers/admission/deployment_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func (n *DeploymentHandler) syncWebhookDeployment(ctx context.Context) error {
return err
}

mondooOperatorImage, err := n.ContainerImageResolver.MondooOperatorImage(
mondooOperatorImage, err := n.ContainerImageResolver.MondooOperatorImage(ctx,
n.Mondoo.Spec.Admission.Image.Name, n.Mondoo.Spec.Admission.Image.Tag, n.MondooOperatorConfig.Spec.SkipContainerResolution)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion controllers/admission/deployment_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ func TestReconcile(t *testing.T) {
require.NoError(
t, kubeClient.Get(context.TODO(), client.ObjectKeyFromObject(auditConfig), auditConfig), "failed to retrieve mondoo audit config")

img, err := containerImageResolver.MondooOperatorImage("", "", false)
img, err := containerImageResolver.MondooOperatorImage(context.Background(), "", "", false)
require.NoErrorf(t, err, "failed to get mondoo operator image.")
expectedDeployment := WebhookDeployment(testNamespace, img, *auditConfig, "", testClusterID)
require.NoError(t, ctrl.SetControllerReference(auditConfig, expectedDeployment, kubeClient.Scheme()))
Expand Down
4 changes: 1 addition & 3 deletions controllers/k8s_scan/deployment_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ func (n *DeploymentHandler) Reconcile(ctx context.Context) (ctrl.Result, error)
}

func (n *DeploymentHandler) syncCronJob(ctx context.Context) error {
// TODO: think about overriding these images
mondooOperatorImage, err := n.ContainerImageResolver.MondooOperatorImage(
"", "", n.MondooOperatorConfig.Spec.SkipContainerResolution)
mondooOperatorImage, err := n.ContainerImageResolver.MondooOperatorImage(ctx, "", "", n.MondooOperatorConfig.Spec.SkipContainerResolution)
if err != nil {
logger.Error(err, "Failed to resolve mondoo-operator container image")
return err
Expand Down
6 changes: 3 additions & 3 deletions controllers/k8s_scan/deployment_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create() {
nodes := &corev1.NodeList{}
s.NoError(d.KubeClient.List(s.ctx, nodes))

image, err := s.containerImageResolver.MondooOperatorImage("", "", false)
image, err := s.containerImageResolver.MondooOperatorImage(s.ctx, "", "", false)
s.NoError(err)

expected := CronJob(image, "", test.KubeSystemNamespaceUid, &s.auditConfig)
Expand Down Expand Up @@ -145,7 +145,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_ConsoleIntegration() {
nodes := &corev1.NodeList{}
s.NoError(d.KubeClient.List(s.ctx, nodes))

image, err := s.containerImageResolver.MondooOperatorImage("", "", false)
image, err := s.containerImageResolver.MondooOperatorImage(s.ctx, "", "", false)
s.NoError(err)

expected := CronJob(image, integrationMrn, test.KubeSystemNamespaceUid, &s.auditConfig)
Expand Down Expand Up @@ -174,7 +174,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Update() {
Token: "token",
}).Times(1)

image, err := s.containerImageResolver.MondooOperatorImage("", "", false)
image, err := s.containerImageResolver.MondooOperatorImage(s.ctx, "", "", false)
s.NoError(err)

// Make sure a cron job exists with different container command
Expand Down
3 changes: 1 addition & 2 deletions controllers/nodes/deployment_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error {
return err
}

mondooOperatorImage, err := n.ContainerImageResolver.MondooOperatorImage(
"", "", n.MondooOperatorConfig.Spec.SkipContainerResolution)
mondooOperatorImage, err := n.ContainerImageResolver.MondooOperatorImage(ctx, "", "", n.MondooOperatorConfig.Spec.SkipContainerResolution)
if err != nil {
logger.Error(err, "Failed to resolve mondoo-operator container image")
return err
Expand Down
3 changes: 1 addition & 2 deletions controllers/nodes/deployment_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateCronJobs() {
s.Equal(expected, created)
}

operatorImage, err := s.containerImageResolver.MondooOperatorImage(
s.auditConfig.Spec.Scanner.Image.Name, s.auditConfig.Spec.Scanner.Image.Tag, false)
operatorImage, err := s.containerImageResolver.MondooOperatorImage(s.ctx, "", "", false)
s.NoError(err)

// Verify node garbage collection cronjob
Expand Down
67 changes: 57 additions & 10 deletions pkg/utils/mondoo/container_image_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
package mondoo

import (
"context"
"fmt"
"os"

"github.com/go-logr/logr"

corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

"go.mondoo.com/mondoo-operator/pkg/imagecache"
"go.mondoo.com/mondoo-operator/pkg/version"
Expand All @@ -19,6 +23,8 @@ const (
CnspecTag = "10-rootless"
OpenShiftMondooClientTag = "10-ubi-rootless"
MondooOperatorImage = "ghcr.io/mondoohq/mondoo-operator"
PodNameEnvVar = "POD_NAME"
PodNamespaceEnvVar = "POD_NAMESPACE"
)

// On a normal mondoo-operator build, the Version variable will be set at build time to match
Expand All @@ -34,20 +40,35 @@ type ContainerImageResolver interface {

// MondooOperatorImage return the Mondoo operator image. If skipResolveImage is false, then the image tag is replaced
// by a digest. If userImage or userTag are empty strings, default values are used.
MondooOperatorImage(userImage, userTag string, skipImageResolution bool) (string, error)
MondooOperatorImage(ctx context.Context, userImage, userTag string, skipImageResolution bool) (string, error)
}

type containerImageResolver struct {
logger logr.Logger
resolveForOpenShift bool
imageCacher imagecache.ImageCacher
logger logr.Logger
resolveForOpenShift bool
imageCacher imagecache.ImageCacher
kubeClient client.Client
operatorPodName string
operatorPodNamespace string
}

func NewContainerImageResolver(isOpenShift bool) ContainerImageResolver {
func NewContainerImageResolver(kubeClient client.Client, isOpenShift bool) ContainerImageResolver {
podName := os.Getenv(PodNameEnvVar)
if podName == "" {
podName = "mondoo-operator-controller-manager"
}
podNamespace := os.Getenv(PodNamespaceEnvVar)
if podNamespace == "" {
podNamespace = "mondoo-operator"
}

return &containerImageResolver{
logger: ctrl.Log.WithName("container-image-resolver"),
imageCacher: imagecache.NewImageCacher(),
resolveForOpenShift: isOpenShift,
logger: ctrl.Log.WithName("container-image-resolver"),
imageCacher: imagecache.NewImageCacher(),
resolveForOpenShift: isOpenShift,
kubeClient: kubeClient,
operatorPodName: podName,
operatorPodNamespace: podNamespace,
}
}

Expand All @@ -63,8 +84,34 @@ func (c *containerImageResolver) CnspecImage(userImage, userTag string, skipImag
return c.resolveImage(image, skipImageResolution)
}

func (c *containerImageResolver) MondooOperatorImage(userImage, userTag string, skipImageResolution bool) (string, error) {
image := userImageOrDefault(MondooOperatorImage, MondooOperatorTag, userImage, userTag)
func (c *containerImageResolver) MondooOperatorImage(ctx context.Context, userImage, userTag string, skipImageResolution bool) (string, error) {
image := ""

// If we have no user image or tag, we read the image from the operator pod
if userImage == "" || userTag == "" {
operatorPod := &corev1.Pod{}
if err := c.kubeClient.Get(ctx, client.ObjectKey{Namespace: c.operatorPodNamespace, Name: c.operatorPodName}, operatorPod); err != nil {
return "", err
}

for _, container := range operatorPod.Spec.Containers {
if container.Name == "manager" {
image = container.Image
break
}
}

// If at this point we don't have an image, then something went wrong
if image == "" {
return "", fmt.Errorf("failed to get mondoo-operator image from operator pod")
}
}

// If still no image, then load the image user-specified image or use the defaults as last resort
if image == "" {
image = userImageOrDefault(MondooOperatorImage, MondooOperatorTag, userImage, userTag)
}

return c.resolveImage(image, skipImageResolution)
}

Expand Down
74 changes: 55 additions & 19 deletions pkg/utils/mondoo/container_image_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@
package mondoo

import (
"context"
"fmt"
"strings"
"testing"

"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/stretchr/testify/suite"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

type ContainerImageResolverSuite struct {
suite.Suite
resolver containerImageResolver
remoteCallsCount int
testHex string
remoteCallsCount int
testHex string
fakeClientBuilder *fake.ClientBuilder
}

type fakeCacher struct {
Expand All @@ -38,19 +42,43 @@ func NewFakeCacher(f func(string) (string, error)) *fakeCacher {
func (s *ContainerImageResolverSuite) BeforeTest(suiteName, testName string) {
s.remoteCallsCount = 0
s.testHex = "test"
s.resolver = containerImageResolver{
s.fakeClientBuilder = fake.NewClientBuilder().WithObjects(&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "mondoo-operator-controller-manager",
Namespace: "mondoo-operator",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "random-container",
Image: "ghcr.io/mondoohq/test:random",
},
{
Name: "manager",
Image: "ghcr.io/mondoohq/mondoo-operator:testtag",
},
},
},
})
}

func (s *ContainerImageResolverSuite) containerImageResolver() containerImageResolver {
return containerImageResolver{
logger: ctrl.Log.WithName("container-image-resolver"),
imageCacher: NewFakeCacher(func(image string) (string, error) {
s.remoteCallsCount++

imageParts := strings.Split(image, ":")
return imageParts[0] + "@sha256:" + s.testHex, nil
}),
kubeClient: s.fakeClientBuilder.Build(),
operatorPodName: "mondoo-operator-controller-manager",
operatorPodNamespace: "mondoo-operator",
}
}

func (s *ContainerImageResolverSuite) TestNewContainerImageResolver() {
resolver := NewContainerImageResolver(false)
resolver := NewContainerImageResolver(s.fakeClientBuilder.Build(), false)

ref, err := name.ParseReference(fmt.Sprintf("%s:%s", CnspecImage, CnspecTag))
s.NoError(err)
Expand All @@ -71,69 +99,77 @@ func (s *ContainerImageResolverSuite) TestNewContainerImageResolver() {
}

func (s *ContainerImageResolverSuite) TestCnspecImage() {
resolver := s.containerImageResolver()
image := "ghcr.io/mondoo/testimage"
res, err := s.resolver.CnspecImage(image, "testtag", false)
res, err := resolver.CnspecImage(image, "testtag", false)
s.NoError(err)

s.Equal(fmt.Sprintf("%s@sha256:%s", image, s.testHex), res)
s.Equalf(1, s.remoteCallsCount, "remote call has not been performed")
}

func (s *ContainerImageResolverSuite) TestCnspecImage_Defaults() {
res, err := s.resolver.CnspecImage("", "", true)
resolver := s.containerImageResolver()
res, err := resolver.CnspecImage("", "", true)
s.NoError(err)

s.Equal(fmt.Sprintf("%s:%s", CnspecImage, CnspecTag), res)
s.Equalf(0, s.remoteCallsCount, "remote call has been performed")
}

func (s *ContainerImageResolverSuite) TestCnspecImage_SkipImageResolution() {
resolver := s.containerImageResolver()
image := "ghcr.io/mondoo/testimage"
tag := "testtag"

res, err := s.resolver.CnspecImage(image, tag, true)
res, err := resolver.CnspecImage(image, tag, true)
s.NoError(err)

s.Equal(fmt.Sprintf("%s:%s", image, tag), res)
s.Equalf(0, s.remoteCallsCount, "remote call has been performed")
}

func (s *ContainerImageResolverSuite) TestCnspecImage_OpenShift() {
s.resolver.resolveForOpenShift = true
resolver := s.containerImageResolver()
resolver.resolveForOpenShift = true

res, err := s.resolver.CnspecImage("", "", true)
res, err := resolver.CnspecImage("", "", true)
s.NoError(err)

s.Equal(fmt.Sprintf("%s:%s", CnspecImage, OpenShiftMondooClientTag), res)
s.Equalf(0, s.remoteCallsCount, "remote call has been performed")
}

func (s *ContainerImageResolverSuite) TestMondooOperatorImage() {
image := "ghcr.io/mondoo/testimage"
res, err := s.resolver.MondooOperatorImage(image, "testtag", false)
resolver := s.containerImageResolver()
res, err := resolver.MondooOperatorImage(context.Background(), "", "", false)
s.NoError(err)

s.Equal(fmt.Sprintf("%s@sha256:%s", image, s.testHex), res)
s.Equalf(1, s.remoteCallsCount, "remote call has not been performed")
s.Equal("ghcr.io/mondoohq/mondoo-operator@sha256:test", res)
}

func (s *ContainerImageResolverSuite) TestMondooOperatorImage_Defaults() {
res, err := s.resolver.MondooOperatorImage("", "", true)
func (s *ContainerImageResolverSuite) TestMondooOperatorImage_CustomImage() {
image := "ghcr.io/mondoo/testimage"
tag := "testtag"

resolver := s.containerImageResolver()
res, err := resolver.MondooOperatorImage(context.Background(), image, tag, false)
s.NoError(err)

s.Equal(fmt.Sprintf("%s:%s", MondooOperatorImage, MondooOperatorTag), res)
s.Equalf(0, s.remoteCallsCount, "remote call has been performed")
s.Equal("ghcr.io/mondoo/testimage@sha256:test", res)
}

func (s *ContainerImageResolverSuite) TestMondooOperatorImage_SkipImageResolution() {
image := "ghcr.io/mondoo/testimage"
tag := "testtag"

res, err := s.resolver.MondooOperatorImage(image, tag, true)
resolver := s.containerImageResolver()
res, err := resolver.MondooOperatorImage(context.Background(), image, tag, true)
s.NoError(err)

s.Equal(fmt.Sprintf("%s:%s", image, tag), res)
s.Equalf(0, s.remoteCallsCount, "remote call has been performed")
s.Equal("ghcr.io/mondoo/testimage:testtag", res)
}

func TestContainerImageResolverSuite(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion pkg/utils/mondoo/fake/container_image_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package fake

import (
"context"
"fmt"

"go.mondoo.com/mondoo-operator/pkg/utils/mondoo"
Expand All @@ -19,6 +20,6 @@ func (c *noOpContainerImageResolver) CnspecImage(userImage, userTag string, skip
return fmt.Sprintf("%s:%s", mondoo.CnspecImage, mondoo.CnspecTag), nil
}

func (c *noOpContainerImageResolver) MondooOperatorImage(userImage, userTag string, skipResolveImage bool) (string, error) {
func (c *noOpContainerImageResolver) MondooOperatorImage(ctx context.Context, userImage, userTag string, skipResolveImage bool) (string, error) {
return fmt.Sprintf("%s:%s", mondoo.MondooOperatorImage, mondoo.MondooOperatorTag), nil
}

0 comments on commit fca6dfd

Please sign in to comment.