Skip to content

Commit 89c21aa

Browse files
Merge pull request #126 from jkhelil/add_image_dependencies
Add dependent images as env vars
2 parents fb8e9c3 + 8020cb8 commit 89c21aa

File tree

11 files changed

+285
-5
lines changed

11 files changed

+285
-5
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ The operator will deploy Shipwright Builds in the provided `targetNamespace`.
3434
When `.spec.targetNamespace` is not set, the namespace will default to `shipwright-build`.
3535
Refer to the [ShipwrightBuild documentation](docs/shipwrightbuild.md) for more information about this custom resource.
3636

37+
The operator handles differents environment variables to customize Shiprwright controller installation:
38+
KO_DATA_PATH : defines the shipwright controller manifest to install
39+
IMAGE_SHIPWRIGHT_SHIPWRIGHT_BUILD : defines the Shipwright Build Controller Image to use
40+
IMAGE_SHIPWRIGHT_GIT_CONTAINER_IMAGE: defines the Shipwright Git Container Image to use
41+
IMAGE_SHIPWRIGHT_MUTATE_IMAGE_CONTAINER_IMAGE: defines the Shipwright Mutate Image to use
42+
IMAGE_SHIPWRIGHT_BUNDLE_CONTAINER_IMAGE: defines the Shipwright Bundle Image to use
43+
IMAGE_SHIPWRIGHT_WAITER_CONTAINER_IMAGE: defines the Shipwright Waiter Image to use
44+
3745
## Contributing
3846

3947
See [CONTRIBUTING.md](CONTRIBUTING.md) for more information on how to build, test, and submit

controllers/shipwrightbuild_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,12 @@ func (r *ShipwrightBuildReconciler) Reconcile(ctx context.Context, req ctrl.Requ
142142
logger.Info("created target namespace")
143143
}
144144

145+
images := toLowerCaseKeys(imagesFromEnv(ShipwrightImagePrefix))
145146
// filtering out namespace resource, so it does not create new namespaces accidentally, and
146147
// transforming object to target the namespace informed on the CRD (.spec.namespace)
147148
manifest, err := r.Manifest.
148149
Filter(manifestival.Not(manifestival.ByKind("Namespace"))).
149-
Transform(manifestival.InjectNamespace(targetNamespace))
150+
Transform(manifestival.InjectNamespace(targetNamespace), deploymentImages(images))
150151
if err != nil {
151152
logger.Error(err, "transforming manifests, injecting namespace")
152153
return RequeueWithError(err)

controllers/shipwrightbuild_controller_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,16 @@ func testShipwrightBuildReconcilerReconcile(t *testing.T, targetNamespace string
138138
crds := []*crdv1.CustomResourceDefinition{crd1, crd2}
139139
c, _, _, r := bootstrapShipwrightBuildReconciler(t, b, nil, crds)
140140

141+
images := []struct {
142+
key, value string
143+
}{
144+
{"IMAGE_SHIPWRIGHT_SHIPWRIGHT_BUILD", "ghcr.io/shipwright-io/build/shipwright-build-controller:nightly-2023-05-05-1683263383"},
145+
{"IMAGE_SHIPWRIGHT_GIT_CONTAINER_IMAGE", "ghcr.io/shipwright-io/build/git:nightly-2023-05-02-1683004171"},
146+
{"IMAGE_SHIPWRIGHT_WAITER_CONTAINER_IMAGE", "ghcr.io/shipwright-io/build/waiter:nightly-2023-05-05-1683263383"},
147+
{"IMAGE_SHIPWRIGHT_MUTATE_IMAGE_CONTAINER_IMAGE", "ghcr.io/shipwright-io/build/mutate-image:nightly-2023-04-18-1681794585"},
148+
{"IMAGE_SHIPWRIGHT_BUNDLE_CONTAINER_IMAGE", "ghcr.io/shipwright-io/build/bundle:nightly-2023-05-05-1683263383"},
149+
}
150+
141151
t.Logf("Deploying Shipwright Controller against '%s' namespace", targetNamespace)
142152

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

167+
t.Run("rollout-manifests-with-images-env-vars", func(t *testing.T) {
168+
ctx := context.TODO()
169+
for _, v := range images {
170+
t.Setenv(v.key, v.value)
171+
}
172+
deployment := &appsv1.Deployment{}
173+
res, err := r.Reconcile(ctx, req)
174+
g.Expect(err).To(o.BeNil())
175+
g.Expect(res.Requeue).To(o.BeFalse())
176+
err = c.Get(ctx, deploymentName, deployment)
177+
g.Expect(err).To(o.BeNil())
178+
containers := deployment.Spec.Template.Spec.Containers
179+
g.Expect(containers[0].Image).To(o.Equal("ghcr.io/shipwright-io/build/shipwright-build-controller:nightly-2023-05-05-1683263383"))
180+
err = c.Get(ctx, namespacedName, b)
181+
g.Expect(err).To(o.BeNil())
182+
g.Expect(b.Status.IsReady()).To(o.BeTrue())
183+
})
184+
157185
// rolling back all changes, making sure the main deployment is also not found afterwards
158186
t.Run("rollback-manifests", func(t *testing.T) {
159187
ctx := context.TODO()
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: controller
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
run: test
10+
template:
11+
metadata:
12+
labels:
13+
run: test
14+
spec:
15+
containers:
16+
- image: busybox
17+
name: SHIPWRIGHT_CONTROLLER
18+
args: [
19+
"-bash-image", "busybox",
20+
"-nop=nop"
21+
]
22+
- image: busybox
23+
name: sidecar
24+
env:
25+
- name: IMAGE_SHPWRIGHT_GIT_CONTAINER_IMAGE
26+
value: ghcr.io/shipwright-io/build/git:v0.11.0@sha256:aecf8bdc01ea00be83e933162a0b6d063846b315fe9dcae60e4be1a34e85d514
27+
28+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: tekton.dev/v1alpha1
2+
kind: Task
3+
metadata:
4+
name: echo-hello-world
5+
spec:
6+
steps:
7+
- name: echo
8+
image: ubuntu
9+
command:
10+
- echo
11+
args:
12+
- "hello world"

controllers/util.go

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,20 @@ package controllers
33
import (
44
"fmt"
55
"os"
6+
"strings"
7+
8+
mf "github.com/manifestival/manifestival"
9+
appsv1 "k8s.io/api/apps/v1"
10+
corev1 "k8s.io/api/core/v1"
11+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12+
"k8s.io/apimachinery/pkg/runtime"
613
)
714

815
// koDataPathEnv ko data-path environment variable.
9-
const koDataPathEnv = "KO_DATA_PATH"
16+
const (
17+
koDataPathEnv = "KO_DATA_PATH"
18+
ShipwrightImagePrefix = "IMAGE_SHIPWRIGHT_"
19+
)
1020

1121
// koDataPath retrieve the data path environment variable, returning error when not found.
1222
func koDataPath() (string, error) {
@@ -26,3 +36,84 @@ func contains(slice []string, str string) bool {
2636
}
2737
return false
2838
}
39+
40+
// imagesFromEnv will provide map of key value.
41+
func imagesFromEnv(prefix string) map[string]string {
42+
images := map[string]string{}
43+
for _, env := range os.Environ() {
44+
if !strings.HasPrefix(env, prefix) {
45+
continue
46+
}
47+
48+
keyValue := strings.Split(env, "=")
49+
name := strings.TrimPrefix(keyValue[0], prefix)
50+
url := keyValue[1]
51+
images[name] = url
52+
}
53+
54+
return images
55+
}
56+
57+
// toLowerCaseKeys converts key value to lower cases.
58+
func toLowerCaseKeys(keyValues map[string]string) map[string]string {
59+
newMap := map[string]string{}
60+
61+
for k, v := range keyValues {
62+
key := strings.ToLower(k)
63+
newMap[key] = v
64+
}
65+
66+
return newMap
67+
}
68+
69+
// deploymentImages replaces container and env vars images.
70+
func deploymentImages(images map[string]string) mf.Transformer {
71+
return func(u *unstructured.Unstructured) error {
72+
if u.GetKind() != "Deployment" {
73+
return nil
74+
}
75+
76+
d := &appsv1.Deployment{}
77+
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, d)
78+
if err != nil {
79+
return err
80+
}
81+
82+
containers := d.Spec.Template.Spec.Containers
83+
replaceContainerImages(containers, images)
84+
unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(d)
85+
if err != nil {
86+
return err
87+
}
88+
u.SetUnstructuredContent(unstrObj)
89+
90+
return nil
91+
}
92+
}
93+
94+
func formKey(prefix, arg string) string {
95+
argument := strings.ToLower(arg)
96+
if prefix != "" {
97+
argument = prefix + argument
98+
}
99+
return strings.ReplaceAll(argument, "-", "_")
100+
}
101+
102+
func replaceContainerImages(containers []corev1.Container, images map[string]string) {
103+
for i, container := range containers {
104+
name := formKey("", container.Name)
105+
if url, exist := images[name]; exist {
106+
containers[i].Image = url
107+
}
108+
109+
replaceContainersEnvImage(container, images)
110+
}
111+
}
112+
113+
func replaceContainersEnvImage(container corev1.Container, images map[string]string) {
114+
for index, env := range container.Env {
115+
if url, exist := images[formKey("", env.Name)]; exist {
116+
container.Env[index].Value = url
117+
}
118+
}
119+
}

controllers/util_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package controllers
2+
3+
import (
4+
"path"
5+
"testing"
6+
7+
mf "github.com/manifestival/manifestival"
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
10+
appsv1 "k8s.io/api/apps/v1"
11+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12+
"k8s.io/apimachinery/pkg/runtime"
13+
)
14+
15+
func TestImagesFromEnv(t *testing.T) {
16+
t.Setenv("IMAGE_SHIPWRIGHT_CONTROLLER", "docker.io/shipwright-controller")
17+
data := imagesFromEnv(ShipwrightImagePrefix)
18+
Expect(data).To(Equal(map[string]string{"CONTROLLER": "docker.io/shipwright-controller"}))
19+
}
20+
21+
func TestDeploymentImages(t *testing.T) {
22+
RegisterFailHandler(Fail)
23+
t.Run("ignore non deployment images", func(t *testing.T) {
24+
testData := path.Join("testdata", "test-replace-kind.yaml")
25+
expected, _ := mf.ManifestFrom(mf.Recursive(testData))
26+
27+
manifest, err := mf.ManifestFrom(mf.Recursive(testData))
28+
Expect(err).NotTo(HaveOccurred())
29+
30+
newManifest, err := manifest.Transform(deploymentImages(map[string]string{}))
31+
Expect(err).NotTo(HaveOccurred())
32+
33+
Expect(expected.Resources()).To(Equal(newManifest.Resources()))
34+
})
35+
t.Run("replace containers image by name", func(t *testing.T) {
36+
image := "foo.bar/image"
37+
images := map[string]string{
38+
"IMAGE_SHIPWRIGHT_SHIPWRIGHT_BUILD": image,
39+
}
40+
testData := path.Join("testdata", "test-replace-image.yaml")
41+
42+
manifest, err := mf.ManifestFrom(mf.Recursive(testData))
43+
Expect(err).NotTo(HaveOccurred())
44+
newManifest, err := manifest.Transform(deploymentImages(images))
45+
Expect(err).NotTo(HaveOccurred())
46+
assertDeployContainersHasImage(t, newManifest.Resources(), "SHIPWRIGHT_BUILD", image)
47+
})
48+
t.Run("replace containers env", func(t *testing.T) {
49+
image := "foo.bar/image/bash"
50+
images := map[string]string{
51+
"IMAGE_SHIPWRIGHT_GIT_CONTAINER_IMAGE": image,
52+
}
53+
testData := path.Join("testdata", "test-replace-image.yaml")
54+
55+
manifest, err := mf.ManifestFrom(mf.Recursive(testData))
56+
Expect(err).NotTo(HaveOccurred())
57+
newManifest, err := manifest.Transform(deploymentImages(images))
58+
Expect(err).NotTo(HaveOccurred())
59+
assertDeployContainerEnvsHasImage(t, newManifest.Resources(), "IMAGE_SHIPWRIGHT_GIT_CONTAINER_IMAGE", image)
60+
})
61+
}
62+
63+
func deploymentFor(t *testing.T, unstr unstructured.Unstructured) *appsv1.Deployment {
64+
deployment := &appsv1.Deployment{}
65+
err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, deployment)
66+
if err != nil {
67+
t.Errorf("failed to load deployment yaml")
68+
}
69+
return deployment
70+
}
71+
72+
func assertDeployContainersHasImage(t *testing.T, resources []unstructured.Unstructured, name string, image string) {
73+
t.Helper()
74+
75+
for _, resource := range resources {
76+
deployment := deploymentFor(t, resource)
77+
containers := deployment.Spec.Template.Spec.Containers
78+
for _, container := range containers {
79+
if container.Name != name {
80+
continue
81+
}
82+
if container.Image != image {
83+
t.Errorf("assertion failed; unexpected image: expected %s and got %s", image, container.Image)
84+
}
85+
}
86+
}
87+
}
88+
89+
func assertDeployContainerEnvsHasImage(t *testing.T, resources []unstructured.Unstructured, env string, image string) {
90+
t.Helper()
91+
92+
for _, resource := range resources {
93+
deployment := deploymentFor(t, resource)
94+
containers := deployment.Spec.Template.Spec.Containers
95+
96+
for _, container := range containers {
97+
if len(container.Env) == 0 {
98+
continue
99+
}
100+
101+
for index, envVar := range container.Env {
102+
if envVar.Name == env && container.Env[index].Value != image {
103+
t.Errorf("not equal: expected %v, got %v", image, container.Env[index].Value)
104+
}
105+
}
106+
}
107+
}
108+
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/onsi/ginkgo v1.16.5
1010
github.com/onsi/gomega v1.27.6
1111
github.com/tektoncd/operator v0.60.0
12+
gopkg.in/yaml.v2 v2.4.0
1213
k8s.io/api v0.23.5
1314
k8s.io/apiextensions-apiserver v0.23.4
1415
k8s.io/apimachinery v0.23.5
@@ -66,6 +67,7 @@ require (
6667
go.uber.org/multierr v1.7.0 // indirect
6768
go.uber.org/zap v1.21.0 // indirect
6869
golang.org/x/crypto v0.5.0 // indirect
70+
golang.org/x/mod v0.9.0 // indirect
6971
golang.org/x/net v0.8.0 // indirect
7072
golang.org/x/oauth2 v0.4.0 // indirect
7173
golang.org/x/sync v0.1.0 // indirect
@@ -81,7 +83,6 @@ require (
8183
google.golang.org/protobuf v1.28.0 // indirect
8284
gopkg.in/inf.v0 v0.9.1 // indirect
8385
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
84-
gopkg.in/yaml.v2 v2.4.0 // indirect
8586
gopkg.in/yaml.v3 v3.0.1 // indirect
8687
k8s.io/component-base v0.23.4 // indirect
8788
k8s.io/klog/v2 v2.60.1-0.20220317184644-43cc75f9ae89 // indirect

go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
757757
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
758758
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
759759
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
760-
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
760+
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
761+
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
761762
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
762763
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
763764
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

pkg/tekton/tekton.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func ReconcileTekton(ctx context.Context,
3636
if err != nil {
3737
return nil, true, fmt.Errorf("failed to determine Tekton Operator version: %v", err)
3838
}
39-
if tektonVersion.Minor() < 49 {
39+
if tektonVersion.Major() < 1 && tektonVersion.Minor() < 49 {
4040
return nil, true, fmt.Errorf("insufficient Tekton Operator version - must be greater than v0.49.0")
4141
}
4242
tektonConfigPresent, err := IsTektonConfigPresent(ctx, tektonOperatorClient)

0 commit comments

Comments
 (0)