Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/AirGapImageConfiguration.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ spec:
Behavior and precedence:

- If `TEKTON_REGISTRY_OVERRIDE` is unset, images are taken from per-image env vars (if set) or from the shipped defaults.
- If `TEKTON_REGISTRY_OVERRIDE` is set, the operator rewrites the registry host for all resolved images (from per-image env vars and defaults). The repository path and tag/digest are preserved.
- If `TEKTON_REGISTRY_OVERRIDE` is set, the operator rewrites the registry host for all resolved images (from per-image env vars and defaults, including images with no matching per-image env var), for every component. The repository path and tag/digest are preserved.
- There is currently no per-image opt-out when the global override is set. To exempt specific images, do not set `TEKTON_REGISTRY_OVERRIDE` and rely solely on per-image env vars.

## Rewrite image one by one
Expand All @@ -56,7 +56,9 @@ example.com/tektoncd/dashboard:v0.48.0

### Tekton instance update

If you update an existing instance of tekton, you will need also to refresh the `TektonInstallerSets` so the new value can be taken into account.
Changing `TEKTON_REGISTRY_OVERRIDE` on an existing installation is picked up automatically: the operator refreshes the affected `TektonInstallerSets` on its next reconcile, no manual action needed.

If you instead change one of the per-image environment variables (`IMAGE_*`) without touching `TEKTON_REGISTRY_OVERRIDE`, you will need to refresh the `TektonInstallerSets` manually so the new value can be taken into account.

```bash
kubectl delete tektoninstallerset <installer-set-name>
Expand Down
53 changes: 39 additions & 14 deletions pkg/reconciler/common/transformers.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,35 @@ func ImageRegistryDomainOverride(images map[string]string) map[string]string {
registry := os.Getenv(ImageRegistryOverride)
if registry == "" {
return images
} else {
for key, imageName := range images {
parts := strings.Split(imageName, "/")
if len(parts) > 1 {
// if image has registry part, replace it
images[key] = registry + "/" + strings.Join(parts[1:], "/")
} else {
// if image does not have registry part, add it
images[key] = registry + "/" + imageName
}
}
return images
}
for key, imageName := range images {
images[key] = overrideImageRegistry(registry, imageName)
}
return images
}

// overrideImageRegistry rewrites the registry domain of a single image
// reference. It is also used as a fallback by the manifest-level image
// transformers (containers, task steps, step actions) so that
// TEKTON_REGISTRY_OVERRIDE alone applies to every image, including the
// defaults baked into component manifests that have no matching per-image
// env var.
func overrideImageRegistry(registry, imageName string) string {
if registry == "" || imageName == "" {
return imageName
}
// Tekton variable substitutions (e.g. "$(params.builder-image)") are not
// literal image references and must be left untouched.
if strings.Contains(imageName, "$(") {
return imageName
}
parts := strings.Split(imageName, "/")
if len(parts) > 1 {
// if image has registry part, replace it
return registry + "/" + strings.Join(parts[1:], "/")
}
// if image does not have registry part, add it
return registry + "/" + imageName
}

// ToLowerCaseKeys converts key value to lower cases.
Expand Down Expand Up @@ -296,10 +312,13 @@ func JobImages(images map[string]string) mf.Transformer {
}

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

replaceContainersArgsImage(&container, images)
Expand Down Expand Up @@ -393,7 +412,10 @@ func replaceStepActionImages(stepActionSpec map[string]interface{}, override map
name = formKey("", name)
image, found := override[name]
if !found || image == "" {
logger.Debugf("Image not found in stepaction %s action skip", name)
logger.Debugf("Image not found in stepaction %s, applying registry override only", name)
if existing, ok := stepActionSpec["image"].(string); ok {
stepActionSpec["image"] = overrideImageRegistry(os.Getenv(ImageRegistryOverride), existing)
}
return
}
// Replace the image in the stepActionSpec if the key exists.
Expand All @@ -415,7 +437,10 @@ func replaceStepsImages(steps []interface{}, override map[string]string, logger
name = formKey("", name)
image, found := override[name]
if !found || image == "" {
logger.Debugf("Image not found step %s action skip", name)
logger.Debugf("Image not found step %s, applying registry override only", name)
if existing, ok := step["image"].(string); ok {
step["image"] = overrideImageRegistry(os.Getenv(ImageRegistryOverride), existing)
}
continue
}
step["image"] = image
Expand Down
42 changes: 42 additions & 0 deletions pkg/reconciler/common/transformers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,48 @@ func TestImageRegistryDomainOverride(t *testing.T) {
}
}

func TestDeploymentImagesRegistryOverrideFallback(t *testing.T) {
t.Setenv("TEKTON_REGISTRY_OVERRIDE", "custom-registry.io/custom-path")
// no per-image env var matches any container in the manifest
images := map[string]string{
"some_other_image": "foo.bar/unrelated",
}
testData := path.Join("testdata", "test-replace-image.yaml")

manifest, err := mf.ManifestFrom(mf.Recursive(testData))
assertNoError(t, err)
newManifest, err := manifest.Transform(DeploymentImages(images))
assertNoError(t, err)
assertDeployContainersHasImage(t, newManifest.Resources(), "controller-deployment", "custom-registry.io/custom-path/busybox")
assertDeployContainersHasImage(t, newManifest.Resources(), "sidecar", "custom-registry.io/custom-path/busybox")
}

func TestTaskImagesRegistryOverrideFallback(t *testing.T) {
t.Setenv("TEKTON_REGISTRY_OVERRIDE", "custom-registry.io/custom-path")
images := map[string]string{}
testData := path.Join("testdata", "test-replace-addon-image.yaml")

manifest, err := mf.ManifestFrom(mf.Recursive(testData))
assertNoError(t, err)
newManifest, err := manifest.Transform(TaskImages(context.TODO(), images))
assertNoError(t, err)
assertTaskImage(t, newManifest.Resources(), "push", "custom-registry.io/custom-path/buildah")
// Tekton variable substitutions must never be rewritten
assertTaskImage(t, newManifest.Resources(), "build", "$(inputs.params.BUILDER_IMAGE)")
}

func TestStepActionImagesRegistryOverrideFallback(t *testing.T) {
t.Setenv("TEKTON_REGISTRY_OVERRIDE", "custom-registry.io/custom-path")
images := map[string]string{}
testData := path.Join("testdata", "test-replace-stepaction-image.yaml")

manifest, err := mf.ManifestFrom(mf.Recursive(testData))
assertNoError(t, err)
newManifest, err := manifest.Transform(StepActionImages(context.TODO(), images))
assertNoError(t, err)
assertStepActionImage(t, newManifest.Resources(), "git-clone", "custom-registry.io/custom-path/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.40.2")
}

func TestImageRegistryDomainWithoutOverride(t *testing.T) {
t.Setenv("TEKTON_REGISTRY_OVERRIDE", "")
// Array of images to be replaced
Expand Down
15 changes: 11 additions & 4 deletions pkg/reconciler/kubernetes/tektoninstallerset/client/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ package client
import (
"context"
"fmt"
"os"
"strings"

"github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
"github.com/tektoncd/operator/pkg/reconciler/common"
"github.com/tektoncd/operator/pkg/reconciler/shared/hash"
"go.uber.org/zap"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -154,12 +156,17 @@ func verifyMeta(resourceKind, isType string, logger *zap.SugaredLogger, set v1al
// Including PlatformDataHashKey means that operator-injected platform config
// (e.g. OpenShift APIServer TLS profile) causes InstallerSets to be refreshed
// when that config changes, without requiring the component's spec to change.
// Including RegistryOverride means that changing TEKTON_REGISTRY_OVERRIDE on
// the operator triggers a refresh of existing InstallerSets on the next
// reconcile, instead of silently keeping the previously applied images.
func specHashInput(comp v1alpha1.TektonComponent) interface{} {
return struct {
Spec interface{}
PlatformData string
Spec interface{}
PlatformData string
RegistryOverride string
}{
Spec: comp.GetSpec(),
PlatformData: comp.GetAnnotations()[v1alpha1.PlatformDataHashKey],
Spec: comp.GetSpec(),
PlatformData: comp.GetAnnotations()[v1alpha1.PlatformDataHashKey],
RegistryOverride: os.Getenv(common.ImageRegistryOverride),
}
}
14 changes: 14 additions & 0 deletions pkg/reconciler/kubernetes/tektoninstallerset/client/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ func computeHash(comp *v1alpha1.TektonTrigger) string {
return h
}

func TestSpecHashInput_ChangesWithRegistryOverride(t *testing.T) {
comp := buildTriggerComponent(false)

t.Setenv("TEKTON_REGISTRY_OVERRIDE", "")
hashWithoutOverride := computeHash(comp)

t.Setenv("TEKTON_REGISTRY_OVERRIDE", "custom-registry.io/custom-path")
hashWithOverride := computeHash(comp)

if hashWithoutOverride == hashWithOverride {
t.Fatalf("expected spec hash to change when TEKTON_REGISTRY_OVERRIDE changes, got same hash %q for both", hashWithOverride)
}
}

func TestInstallerSetClient_Check(t *testing.T) {
releaseVersion := "devel"

Expand Down
Loading