Skip to content

Commit

Permalink
Merge pull request #126 from jkhelil/add_image_dependencies
Browse files Browse the repository at this point in the history
Add dependent images as env vars
  • Loading branch information
openshift-merge-robot authored May 9, 2023
2 parents fb8e9c3 + 8020cb8 commit 89c21aa
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 5 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ The operator will deploy Shipwright Builds in the provided `targetNamespace`.
When `.spec.targetNamespace` is not set, the namespace will default to `shipwright-build`.
Refer to the [ShipwrightBuild documentation](docs/shipwrightbuild.md) for more information about this custom resource.

The operator handles differents environment variables to customize Shiprwright controller installation:
KO_DATA_PATH : defines the shipwright controller manifest to install
IMAGE_SHIPWRIGHT_SHIPWRIGHT_BUILD : defines the Shipwright Build Controller Image to use
IMAGE_SHIPWRIGHT_GIT_CONTAINER_IMAGE: defines the Shipwright Git Container Image to use
IMAGE_SHIPWRIGHT_MUTATE_IMAGE_CONTAINER_IMAGE: defines the Shipwright Mutate Image to use
IMAGE_SHIPWRIGHT_BUNDLE_CONTAINER_IMAGE: defines the Shipwright Bundle Image to use
IMAGE_SHIPWRIGHT_WAITER_CONTAINER_IMAGE: defines the Shipwright Waiter Image to use

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for more information on how to build, test, and submit
Expand Down
3 changes: 2 additions & 1 deletion controllers/shipwrightbuild_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,12 @@ func (r *ShipwrightBuildReconciler) Reconcile(ctx context.Context, req ctrl.Requ
logger.Info("created target namespace")
}

images := toLowerCaseKeys(imagesFromEnv(ShipwrightImagePrefix))
// filtering out namespace resource, so it does not create new namespaces accidentally, and
// transforming object to target the namespace informed on the CRD (.spec.namespace)
manifest, err := r.Manifest.
Filter(manifestival.Not(manifestival.ByKind("Namespace"))).
Transform(manifestival.InjectNamespace(targetNamespace))
Transform(manifestival.InjectNamespace(targetNamespace), deploymentImages(images))
if err != nil {
logger.Error(err, "transforming manifests, injecting namespace")
return RequeueWithError(err)
Expand Down
28 changes: 28 additions & 0 deletions controllers/shipwrightbuild_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ func testShipwrightBuildReconcilerReconcile(t *testing.T, targetNamespace string
crds := []*crdv1.CustomResourceDefinition{crd1, crd2}
c, _, _, r := bootstrapShipwrightBuildReconciler(t, b, nil, crds)

images := []struct {
key, value string
}{
{"IMAGE_SHIPWRIGHT_SHIPWRIGHT_BUILD", "ghcr.io/shipwright-io/build/shipwright-build-controller:nightly-2023-05-05-1683263383"},
{"IMAGE_SHIPWRIGHT_GIT_CONTAINER_IMAGE", "ghcr.io/shipwright-io/build/git:nightly-2023-05-02-1683004171"},
{"IMAGE_SHIPWRIGHT_WAITER_CONTAINER_IMAGE", "ghcr.io/shipwright-io/build/waiter:nightly-2023-05-05-1683263383"},
{"IMAGE_SHIPWRIGHT_MUTATE_IMAGE_CONTAINER_IMAGE", "ghcr.io/shipwright-io/build/mutate-image:nightly-2023-04-18-1681794585"},
{"IMAGE_SHIPWRIGHT_BUNDLE_CONTAINER_IMAGE", "ghcr.io/shipwright-io/build/bundle:nightly-2023-05-05-1683263383"},
}

t.Logf("Deploying Shipwright Controller against '%s' namespace", targetNamespace)

// rolling out all manifests on the desired namespace, making sure the deployment for Shipwright
Expand All @@ -154,6 +164,24 @@ func testShipwrightBuildReconcilerReconcile(t *testing.T, targetNamespace string
g.Expect(b.Status.IsReady()).To(o.BeTrue())
})

t.Run("rollout-manifests-with-images-env-vars", func(t *testing.T) {
ctx := context.TODO()
for _, v := range images {
t.Setenv(v.key, v.value)
}
deployment := &appsv1.Deployment{}
res, err := r.Reconcile(ctx, req)
g.Expect(err).To(o.BeNil())
g.Expect(res.Requeue).To(o.BeFalse())
err = c.Get(ctx, deploymentName, deployment)
g.Expect(err).To(o.BeNil())
containers := deployment.Spec.Template.Spec.Containers
g.Expect(containers[0].Image).To(o.Equal("ghcr.io/shipwright-io/build/shipwright-build-controller:nightly-2023-05-05-1683263383"))
err = c.Get(ctx, namespacedName, b)
g.Expect(err).To(o.BeNil())
g.Expect(b.Status.IsReady()).To(o.BeTrue())
})

// rolling back all changes, making sure the main deployment is also not found afterwards
t.Run("rollback-manifests", func(t *testing.T) {
ctx := context.TODO()
Expand Down
28 changes: 28 additions & 0 deletions controllers/testdata/test-replace-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller
spec:
replicas: 1
selector:
matchLabels:
run: test
template:
metadata:
labels:
run: test
spec:
containers:
- image: busybox
name: SHIPWRIGHT_CONTROLLER
args: [
"-bash-image", "busybox",
"-nop=nop"
]
- image: busybox
name: sidecar
env:
- name: IMAGE_SHPWRIGHT_GIT_CONTAINER_IMAGE
value: ghcr.io/shipwright-io/build/git:v0.11.0@sha256:aecf8bdc01ea00be83e933162a0b6d063846b315fe9dcae60e4be1a34e85d514


12 changes: 12 additions & 0 deletions controllers/testdata/test-replace-kind.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
name: echo-hello-world
spec:
steps:
- name: echo
image: ubuntu
command:
- echo
args:
- "hello world"
93 changes: 92 additions & 1 deletion controllers/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@ package controllers
import (
"fmt"
"os"
"strings"

mf "github.com/manifestival/manifestival"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)

// koDataPathEnv ko data-path environment variable.
const koDataPathEnv = "KO_DATA_PATH"
const (
koDataPathEnv = "KO_DATA_PATH"
ShipwrightImagePrefix = "IMAGE_SHIPWRIGHT_"
)

// koDataPath retrieve the data path environment variable, returning error when not found.
func koDataPath() (string, error) {
Expand All @@ -26,3 +36,84 @@ func contains(slice []string, str string) bool {
}
return false
}

// imagesFromEnv will provide map of key value.
func imagesFromEnv(prefix string) map[string]string {
images := map[string]string{}
for _, env := range os.Environ() {
if !strings.HasPrefix(env, prefix) {
continue
}

keyValue := strings.Split(env, "=")
name := strings.TrimPrefix(keyValue[0], prefix)
url := keyValue[1]
images[name] = url
}

return images
}

// toLowerCaseKeys converts key value to lower cases.
func toLowerCaseKeys(keyValues map[string]string) map[string]string {
newMap := map[string]string{}

for k, v := range keyValues {
key := strings.ToLower(k)
newMap[key] = v
}

return newMap
}

// deploymentImages replaces container and env vars images.
func deploymentImages(images map[string]string) mf.Transformer {
return func(u *unstructured.Unstructured) error {
if u.GetKind() != "Deployment" {
return nil
}

d := &appsv1.Deployment{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, d)
if err != nil {
return err
}

containers := d.Spec.Template.Spec.Containers
replaceContainerImages(containers, images)
unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(d)
if err != nil {
return err
}
u.SetUnstructuredContent(unstrObj)

return nil
}
}

func formKey(prefix, arg string) string {
argument := strings.ToLower(arg)
if prefix != "" {
argument = prefix + argument
}
return strings.ReplaceAll(argument, "-", "_")
}

func replaceContainerImages(containers []corev1.Container, images map[string]string) {
for i, container := range containers {
name := formKey("", container.Name)
if url, exist := images[name]; exist {
containers[i].Image = url
}

replaceContainersEnvImage(container, images)
}
}

func replaceContainersEnvImage(container corev1.Container, images map[string]string) {
for index, env := range container.Env {
if url, exist := images[formKey("", env.Name)]; exist {
container.Env[index].Value = url
}
}
}
108 changes: 108 additions & 0 deletions controllers/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package controllers

import (
"path"
"testing"

mf "github.com/manifestival/manifestival"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)

func TestImagesFromEnv(t *testing.T) {
t.Setenv("IMAGE_SHIPWRIGHT_CONTROLLER", "docker.io/shipwright-controller")
data := imagesFromEnv(ShipwrightImagePrefix)
Expect(data).To(Equal(map[string]string{"CONTROLLER": "docker.io/shipwright-controller"}))
}

func TestDeploymentImages(t *testing.T) {
RegisterFailHandler(Fail)
t.Run("ignore non deployment images", func(t *testing.T) {
testData := path.Join("testdata", "test-replace-kind.yaml")
expected, _ := mf.ManifestFrom(mf.Recursive(testData))

manifest, err := mf.ManifestFrom(mf.Recursive(testData))
Expect(err).NotTo(HaveOccurred())

newManifest, err := manifest.Transform(deploymentImages(map[string]string{}))
Expect(err).NotTo(HaveOccurred())

Expect(expected.Resources()).To(Equal(newManifest.Resources()))
})
t.Run("replace containers image by name", func(t *testing.T) {
image := "foo.bar/image"
images := map[string]string{
"IMAGE_SHIPWRIGHT_SHIPWRIGHT_BUILD": image,
}
testData := path.Join("testdata", "test-replace-image.yaml")

manifest, err := mf.ManifestFrom(mf.Recursive(testData))
Expect(err).NotTo(HaveOccurred())
newManifest, err := manifest.Transform(deploymentImages(images))
Expect(err).NotTo(HaveOccurred())
assertDeployContainersHasImage(t, newManifest.Resources(), "SHIPWRIGHT_BUILD", image)
})
t.Run("replace containers env", func(t *testing.T) {
image := "foo.bar/image/bash"
images := map[string]string{
"IMAGE_SHIPWRIGHT_GIT_CONTAINER_IMAGE": image,
}
testData := path.Join("testdata", "test-replace-image.yaml")

manifest, err := mf.ManifestFrom(mf.Recursive(testData))
Expect(err).NotTo(HaveOccurred())
newManifest, err := manifest.Transform(deploymentImages(images))
Expect(err).NotTo(HaveOccurred())
assertDeployContainerEnvsHasImage(t, newManifest.Resources(), "IMAGE_SHIPWRIGHT_GIT_CONTAINER_IMAGE", image)
})
}

func deploymentFor(t *testing.T, unstr unstructured.Unstructured) *appsv1.Deployment {
deployment := &appsv1.Deployment{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, deployment)
if err != nil {
t.Errorf("failed to load deployment yaml")
}
return deployment
}

func assertDeployContainersHasImage(t *testing.T, resources []unstructured.Unstructured, name string, image string) {
t.Helper()

for _, resource := range resources {
deployment := deploymentFor(t, resource)
containers := deployment.Spec.Template.Spec.Containers
for _, container := range containers {
if container.Name != name {
continue
}
if container.Image != image {
t.Errorf("assertion failed; unexpected image: expected %s and got %s", image, container.Image)
}
}
}
}

func assertDeployContainerEnvsHasImage(t *testing.T, resources []unstructured.Unstructured, env string, image string) {
t.Helper()

for _, resource := range resources {
deployment := deploymentFor(t, resource)
containers := deployment.Spec.Template.Spec.Containers

for _, container := range containers {
if len(container.Env) == 0 {
continue
}

for index, envVar := range container.Env {
if envVar.Name == env && container.Env[index].Value != image {
t.Errorf("not equal: expected %v, got %v", image, container.Env[index].Value)
}
}
}
}
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.27.6
github.com/tektoncd/operator v0.60.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.23.5
k8s.io/apiextensions-apiserver v0.23.4
k8s.io/apimachinery v0.23.5
Expand Down Expand Up @@ -66,6 +67,7 @@ require (
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.4.0 // indirect
golang.org/x/sync v0.1.0 // indirect
Expand All @@ -81,7 +83,6 @@ require (
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.23.4 // indirect
k8s.io/klog/v2 v2.60.1-0.20220317184644-43cc75f9ae89 // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down
2 changes: 1 addition & 1 deletion pkg/tekton/tekton.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func ReconcileTekton(ctx context.Context,
if err != nil {
return nil, true, fmt.Errorf("failed to determine Tekton Operator version: %v", err)
}
if tektonVersion.Minor() < 49 {
if tektonVersion.Major() < 1 && tektonVersion.Minor() < 49 {
return nil, true, fmt.Errorf("insufficient Tekton Operator version - must be greater than v0.49.0")
}
tektonConfigPresent, err := IsTektonConfigPresent(ctx, tektonOperatorClient)
Expand Down
2 changes: 2 additions & 0 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ go.uber.org/zap/zapcore
## explicit; go 1.17
golang.org/x/crypto/pkcs12
golang.org/x/crypto/pkcs12/internal/rc2
# golang.org/x/mod v0.9.0
## explicit; go 1.17
# golang.org/x/net v0.8.0
## explicit; go 1.17
golang.org/x/net/context
Expand Down

0 comments on commit 89c21aa

Please sign in to comment.