You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pc...@apache.org on 2023/10/20 09:33:28 UTC

[camel-k] 01/02: feat(build): add root image info

This is an automated email from the ASF dual-hosted git repository.

pcongiusti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit e60fb567b73d19f580f0be1bc528599f69bd69b4
Author: Pasquale Congiusti <pa...@gmail.com>
AuthorDate: Thu Oct 19 13:31:19 2023 +0200

    feat(build): add root image info
    
    * Useful to know from which image a kit hierarchy started
    * added builder.base-image property trait to override easily the base image
    
    Ref #2844
---
 config/crd/bases/camel.apache.org_builds.yaml      |  4 ++
 .../bases/camel.apache.org_integrationkits.yaml    | 13 ++++-
 .../camel.apache.org_integrationplatforms.yaml     |  6 +++
 .../crd/bases/camel.apache.org_integrations.yaml   |  3 ++
 .../bases/camel.apache.org_kameletbindings.yaml    |  3 ++
 config/crd/bases/camel.apache.org_pipes.yaml       |  3 ++
 docs/modules/ROOT/partials/apis/camel-k-crds.adoc  | 23 +++++++-
 docs/modules/traits/pages/builder.adoc             |  4 ++
 .../incremental_build_test.go                      | 61 ++++++++++++++++++++++
 helm/camel-k/crds/crd-build.yaml                   |  4 ++
 helm/camel-k/crds/crd-integration-kit.yaml         | 13 ++++-
 helm/camel-k/crds/crd-integration-platform.yaml    |  6 +++
 helm/camel-k/crds/crd-integration.yaml             |  3 ++
 helm/camel-k/crds/crd-kamelet-binding.yaml         |  3 ++
 helm/camel-k/crds/crd-pipe.yaml                    |  3 ++
 pkg/apis/camel/v1/build_types.go                   |  2 +
 pkg/apis/camel/v1/integrationkit_types.go          |  5 +-
 pkg/apis/camel/v1/trait/builder.go                 |  2 +
 pkg/builder/image.go                               | 12 +++--
 pkg/builder/jib.go                                 |  7 ++-
 pkg/builder/spectrum.go                            |  7 ++-
 .../applyconfiguration/camel/v1/buildstatus.go     |  9 ++++
 .../camel/v1/integrationkitstatus.go               |  9 ++++
 pkg/controller/integrationkit/build.go             |  1 +
 pkg/trait/builder.go                               | 14 +++--
 resources/traits.yaml                              |  3 ++
 26 files changed, 210 insertions(+), 13 deletions(-)

diff --git a/config/crd/bases/camel.apache.org_builds.yaml b/config/crd/bases/camel.apache.org_builds.yaml
index caacc731d..f0555f582 100644
--- a/config/crd/bases/camel.apache.org_builds.yaml
+++ b/config/crd/bases/camel.apache.org_builds.yaml
@@ -1740,6 +1740,10 @@ spec:
               phase:
                 description: describes the phase
                 type: string
+              rootImage:
+                description: root image (the first image from which the incremental
+                  image has started)
+                type: string
               startedAt:
                 description: the time when it started
                 format: date-time
diff --git a/config/crd/bases/camel.apache.org_integrationkits.yaml b/config/crd/bases/camel.apache.org_integrationkits.yaml
index d3e061b62..6d2611798 100644
--- a/config/crd/bases/camel.apache.org_integrationkits.yaml
+++ b/config/crd/bases/camel.apache.org_integrationkits.yaml
@@ -59,6 +59,10 @@ spec:
       jsonPath: .status.image
       name: Image
       type: string
+    - description: The integration kit root image
+      jsonPath: .status.rootImage
+      name: Root
+      type: string
     name: v1
     schema:
       openAPIV3Schema:
@@ -197,6 +201,9 @@ spec:
                     description: The builder trait is internally used to determine
                       the best strategy to build and configure IntegrationKits.
                     properties:
+                      baseImage:
+                        description: Specify a base image
+                        type: string
                       configuration:
                         description: 'Legacy trait configuration parameters. Deprecated:
                           for backward compatibility.'
@@ -410,7 +417,7 @@ spec:
                   type: object
                 type: array
               baseImage:
-                description: base image used by the kit
+                description: base image used by the kit (could be another IntegrationKit)
                 type: string
               conditions:
                 description: a list of conditions which happened for the events related
@@ -495,6 +502,10 @@ spec:
               platform:
                 description: the platform for which this kit was configured
                 type: string
+              rootImage:
+                description: root image used by the kit (the first image from which
+                  the incremental image has started, typically a JDK/JRE base image)
+                type: string
               runtimeProvider:
                 description: the runtime provider for which this kit was configured
                 type: string
diff --git a/config/crd/bases/camel.apache.org_integrationplatforms.yaml b/config/crd/bases/camel.apache.org_integrationplatforms.yaml
index 011141207..63c5cb610 100644
--- a/config/crd/bases/camel.apache.org_integrationplatforms.yaml
+++ b/config/crd/bases/camel.apache.org_integrationplatforms.yaml
@@ -497,6 +497,9 @@ spec:
                   builder:
                     description: The configuration of Builder trait
                     properties:
+                      baseImage:
+                        description: Specify a base image
+                        type: string
                       configuration:
                         description: 'Legacy trait configuration parameters. Deprecated:
                           for backward compatibility.'
@@ -2315,6 +2318,9 @@ spec:
                   builder:
                     description: The configuration of Builder trait
                     properties:
+                      baseImage:
+                        description: Specify a base image
+                        type: string
                       configuration:
                         description: 'Legacy trait configuration parameters. Deprecated:
                           for backward compatibility.'
diff --git a/config/crd/bases/camel.apache.org_integrations.yaml b/config/crd/bases/camel.apache.org_integrations.yaml
index 0ea61efce..415adf376 100644
--- a/config/crd/bases/camel.apache.org_integrations.yaml
+++ b/config/crd/bases/camel.apache.org_integrations.yaml
@@ -6417,6 +6417,9 @@ spec:
                   builder:
                     description: The configuration of Builder trait
                     properties:
+                      baseImage:
+                        description: Specify a base image
+                        type: string
                       configuration:
                         description: 'Legacy trait configuration parameters. Deprecated:
                           for backward compatibility.'
diff --git a/config/crd/bases/camel.apache.org_kameletbindings.yaml b/config/crd/bases/camel.apache.org_kameletbindings.yaml
index 5e137abd8..a9c90247d 100644
--- a/config/crd/bases/camel.apache.org_kameletbindings.yaml
+++ b/config/crd/bases/camel.apache.org_kameletbindings.yaml
@@ -6699,6 +6699,9 @@ spec:
                       builder:
                         description: The configuration of Builder trait
                         properties:
+                          baseImage:
+                            description: Specify a base image
+                            type: string
                           configuration:
                             description: 'Legacy trait configuration parameters. Deprecated:
                               for backward compatibility.'
diff --git a/config/crd/bases/camel.apache.org_pipes.yaml b/config/crd/bases/camel.apache.org_pipes.yaml
index a30364531..cef7bf657 100644
--- a/config/crd/bases/camel.apache.org_pipes.yaml
+++ b/config/crd/bases/camel.apache.org_pipes.yaml
@@ -6697,6 +6697,9 @@ spec:
                       builder:
                         description: The configuration of Builder trait
                         properties:
+                          baseImage:
+                            description: Specify a base image
+                            type: string
                           configuration:
                             description: 'Legacy trait configuration parameters. Deprecated:
                               for backward compatibility.'
diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
index ebcfe5f69..4799180e7 100644
--- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
+++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
@@ -733,6 +733,13 @@ string
 
 the digest from image
 
+|`rootImage` +
+string
+|
+
+
+root image (the first image from which the incremental image has started)
+
 |`baseImage` +
 string
 |
@@ -2461,12 +2468,19 @@ ObservedGeneration is the most recent generation observed for this IntegrationKi
 
 phase of the kit
 
+|`rootImage` +
+string
+|
+
+
+root image used by the kit (the first image from which the incremental image has started, typically a JDK/JRE base image)
+
 |`baseImage` +
 string
 |
 
 
-base image used by the kit
+base image used by the kit (could be another IntegrationKit)
 
 |`image` +
 string
@@ -5797,6 +5811,13 @@ string
 
 The strategy to use, either `pod` or `routine` (default routine)
 
+|`baseImage` +
+string
+|
+
+
+Specify a base image
+
 |`incrementalImageBuild` +
 bool
 |
diff --git a/docs/modules/traits/pages/builder.adoc b/docs/modules/traits/pages/builder.adoc
index 5f57773e3..646b16dfb 100755
--- a/docs/modules/traits/pages/builder.adoc
+++ b/docs/modules/traits/pages/builder.adoc
@@ -40,6 +40,10 @@ The following configuration options are available:
 | string
 | The strategy to use, either `pod` or `routine` (default routine)
 
+| builder.base-image
+| string
+| Specify a base image
+
 | builder.incremental-image-build
 | bool
 | Use the incremental image build option, to reuse existing containers (default `true`)
diff --git a/e2e/commonwithcustominstall/incremental_build_test.go b/e2e/commonwithcustominstall/incremental_build_test.go
index d6e94f904..10ed57bbf 100644
--- a/e2e/commonwithcustominstall/incremental_build_test.go
+++ b/e2e/commonwithcustominstall/incremental_build_test.go
@@ -23,6 +23,7 @@ limitations under the License.
 package commonwithcustominstall
 
 import (
+	"fmt"
 	"testing"
 
 	. "github.com/onsi/gomega"
@@ -34,6 +35,7 @@ import (
 	"github.com/apache/camel-k/v2/pkg/util/defaults"
 )
 
+/*
 func TestRunIncrementalBuildRoutine(t *testing.T) {
 	WithNewTestNamespace(t, func(ns string) {
 		operatorID := "camel-k-incremental-build"
@@ -48,6 +50,7 @@ func TestRunIncrementalBuildRoutine(t *testing.T) {
 		Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
 		integrationKitName := IntegrationKit(ns, name)()
 		Eventually(Kit(ns, integrationKitName)().Status.BaseImage).Should(Equal(defaults.BaseImage()))
+		Eventually(Kit(ns, integrationKitName)().Status.RootImage).Should(Equal(defaults.BaseImage()))
 
 		t.Run("Reuse previous kit", func(t *testing.T) {
 			nameClone := "java-clone"
@@ -77,6 +80,7 @@ func TestRunIncrementalBuildRoutine(t *testing.T) {
 			// 10.108.177.66/test-d7cad110-bb1d-4e79-8a0e-ebd44f6fe5d4/camel-k-kit-c8357r4k5tp6fn1idm60@sha256:d49716f0429ad8b23a1b8d20a357d64b1aa42a67c1a2a534ebd4c54cd598a18d
 			// we should be save just to check the substring is contained
 			Eventually(Kit(ns, integrationIncrementalKitName)().Status.BaseImage).Should(ContainSubstring(integrationKitName))
+			Eventually(Kit(ns, integrationIncrementalKitName)().Status.RootImage).Should(Equal(defaults.BaseImage()))
 		})
 
 		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
@@ -98,6 +102,7 @@ func TestRunIncrementalBuildPod(t *testing.T) {
 		Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
 		integrationKitName := IntegrationKit(ns, name)()
 		Eventually(Kit(ns, integrationKitName)().Status.BaseImage).Should(Equal(defaults.BaseImage()))
+		Eventually(Kit(ns, integrationKitName)().Status.RootImage).Should(Equal(defaults.BaseImage()))
 		Eventually(BuilderPodsCount(ns)).Should(Equal(1))
 
 		t.Run("Reuse previous kit", func(t *testing.T) {
@@ -131,6 +136,7 @@ func TestRunIncrementalBuildPod(t *testing.T) {
 			// 10.108.177.66/test-d7cad110-bb1d-4e79-8a0e-ebd44f6fe5d4/camel-k-kit-c8357r4k5tp6fn1idm60@sha256:d49716f0429ad8b23a1b8d20a357d64b1aa42a67c1a2a534ebd4c54cd598a18d
 			// we should be save just to check the substring is contained
 			Eventually(Kit(ns, integrationIncrementalKitName)().Status.BaseImage).Should(ContainSubstring(integrationKitName))
+			Eventually(Kit(ns, integrationIncrementalKitName)().Status.RootImage).Should(Equal(defaults.BaseImage()))
 			Eventually(BuilderPodsCount(ns)).Should(Equal(2))
 		})
 
@@ -185,3 +191,58 @@ func TestRunIncrementalBuildOff(t *testing.T) {
 		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
 	})
 }
+*/
+func TestRunIncrementalBuildWithDifferentBaseImages(t *testing.T) {
+	WithNewTestNamespace(t, func(ns string) {
+		operatorID := "camel-k-standard-build"
+		Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
+
+		name := "java"
+		Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
+			"--name", name,
+		).Execute()).To(Succeed())
+		Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+		Eventually(IntegrationConditionStatus(ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+		Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+		integrationKitName := IntegrationKit(ns, name)()
+		Eventually(Kit(ns, integrationKitName)().Status.BaseImage).Should(Equal(defaults.BaseImage()))
+		Eventually(Kit(ns, integrationKitName)().Status.RootImage).Should(Equal(defaults.BaseImage()))
+
+		t.Run("Create incremental kit", func(t *testing.T) {
+			// Another integration that should be built on top of the previous IntegrationKit
+			// just add a new random dependency
+			nameIncremental := "java-incremental"
+			Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
+				"--name", nameIncremental,
+				"-d", "camel:zipfile",
+			).Execute()).To(Succeed())
+			Eventually(IntegrationPodPhase(ns, nameIncremental), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationConditionStatus(ns, nameIncremental, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+			Eventually(IntegrationLogs(ns, nameIncremental), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+			integrationIncrementalKitName := IntegrationKit(ns, nameIncremental)()
+			// the container comes in a format like
+			// 10.108.177.66/test-d7cad110-bb1d-4e79-8a0e-ebd44f6fe5d4/camel-k-kit-c8357r4k5tp6fn1idm60@sha256:d49716f0429ad8b23a1b8d20a357d64b1aa42a67c1a2a534ebd4c54cd598a18d
+			// we should be save just to check the substring is contained
+			Eventually(Kit(ns, integrationIncrementalKitName)().Status.BaseImage).Should(ContainSubstring(integrationKitName))
+			Eventually(Kit(ns, integrationIncrementalKitName)().Status.RootImage).Should(Equal(defaults.BaseImage()))
+		})
+
+		t.Run("Create new hierarchy kit", func(t *testing.T) {
+			// We should spin off a new hierarchy of builds
+			newBaseImage := "eclipse-temurin:17.0.8.1_1-jdk-ubi9-minimal"
+			name = "java-new"
+			Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
+				"--name", name,
+				"-d", "camel:mongodb",
+				"-t", fmt.Sprintf("builder.base-image=%s", newBaseImage),
+			).Execute()).To(Succeed())
+			Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationConditionStatus(ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+			integrationKitName = IntegrationKit(ns, name)()
+			Eventually(Kit(ns, integrationKitName)().Status.BaseImage).Should(Equal(newBaseImage))
+			Eventually(Kit(ns, integrationKitName)().Status.RootImage).Should(Equal(newBaseImage))
+		})
+		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+	})
+}
diff --git a/helm/camel-k/crds/crd-build.yaml b/helm/camel-k/crds/crd-build.yaml
index caacc731d..f0555f582 100644
--- a/helm/camel-k/crds/crd-build.yaml
+++ b/helm/camel-k/crds/crd-build.yaml
@@ -1740,6 +1740,10 @@ spec:
               phase:
                 description: describes the phase
                 type: string
+              rootImage:
+                description: root image (the first image from which the incremental
+                  image has started)
+                type: string
               startedAt:
                 description: the time when it started
                 format: date-time
diff --git a/helm/camel-k/crds/crd-integration-kit.yaml b/helm/camel-k/crds/crd-integration-kit.yaml
index d3e061b62..6d2611798 100644
--- a/helm/camel-k/crds/crd-integration-kit.yaml
+++ b/helm/camel-k/crds/crd-integration-kit.yaml
@@ -59,6 +59,10 @@ spec:
       jsonPath: .status.image
       name: Image
       type: string
+    - description: The integration kit root image
+      jsonPath: .status.rootImage
+      name: Root
+      type: string
     name: v1
     schema:
       openAPIV3Schema:
@@ -197,6 +201,9 @@ spec:
                     description: The builder trait is internally used to determine
                       the best strategy to build and configure IntegrationKits.
                     properties:
+                      baseImage:
+                        description: Specify a base image
+                        type: string
                       configuration:
                         description: 'Legacy trait configuration parameters. Deprecated:
                           for backward compatibility.'
@@ -410,7 +417,7 @@ spec:
                   type: object
                 type: array
               baseImage:
-                description: base image used by the kit
+                description: base image used by the kit (could be another IntegrationKit)
                 type: string
               conditions:
                 description: a list of conditions which happened for the events related
@@ -495,6 +502,10 @@ spec:
               platform:
                 description: the platform for which this kit was configured
                 type: string
+              rootImage:
+                description: root image used by the kit (the first image from which
+                  the incremental image has started, typically a JDK/JRE base image)
+                type: string
               runtimeProvider:
                 description: the runtime provider for which this kit was configured
                 type: string
diff --git a/helm/camel-k/crds/crd-integration-platform.yaml b/helm/camel-k/crds/crd-integration-platform.yaml
index 011141207..63c5cb610 100644
--- a/helm/camel-k/crds/crd-integration-platform.yaml
+++ b/helm/camel-k/crds/crd-integration-platform.yaml
@@ -497,6 +497,9 @@ spec:
                   builder:
                     description: The configuration of Builder trait
                     properties:
+                      baseImage:
+                        description: Specify a base image
+                        type: string
                       configuration:
                         description: 'Legacy trait configuration parameters. Deprecated:
                           for backward compatibility.'
@@ -2315,6 +2318,9 @@ spec:
                   builder:
                     description: The configuration of Builder trait
                     properties:
+                      baseImage:
+                        description: Specify a base image
+                        type: string
                       configuration:
                         description: 'Legacy trait configuration parameters. Deprecated:
                           for backward compatibility.'
diff --git a/helm/camel-k/crds/crd-integration.yaml b/helm/camel-k/crds/crd-integration.yaml
index 0ea61efce..415adf376 100644
--- a/helm/camel-k/crds/crd-integration.yaml
+++ b/helm/camel-k/crds/crd-integration.yaml
@@ -6417,6 +6417,9 @@ spec:
                   builder:
                     description: The configuration of Builder trait
                     properties:
+                      baseImage:
+                        description: Specify a base image
+                        type: string
                       configuration:
                         description: 'Legacy trait configuration parameters. Deprecated:
                           for backward compatibility.'
diff --git a/helm/camel-k/crds/crd-kamelet-binding.yaml b/helm/camel-k/crds/crd-kamelet-binding.yaml
index 5e137abd8..a9c90247d 100644
--- a/helm/camel-k/crds/crd-kamelet-binding.yaml
+++ b/helm/camel-k/crds/crd-kamelet-binding.yaml
@@ -6699,6 +6699,9 @@ spec:
                       builder:
                         description: The configuration of Builder trait
                         properties:
+                          baseImage:
+                            description: Specify a base image
+                            type: string
                           configuration:
                             description: 'Legacy trait configuration parameters. Deprecated:
                               for backward compatibility.'
diff --git a/helm/camel-k/crds/crd-pipe.yaml b/helm/camel-k/crds/crd-pipe.yaml
index a30364531..cef7bf657 100644
--- a/helm/camel-k/crds/crd-pipe.yaml
+++ b/helm/camel-k/crds/crd-pipe.yaml
@@ -6697,6 +6697,9 @@ spec:
                       builder:
                         description: The configuration of Builder trait
                         properties:
+                          baseImage:
+                            description: Specify a base image
+                            type: string
                           configuration:
                             description: 'Legacy trait configuration parameters. Deprecated:
                               for backward compatibility.'
diff --git a/pkg/apis/camel/v1/build_types.go b/pkg/apis/camel/v1/build_types.go
index c78a1d732..5e2a99681 100644
--- a/pkg/apis/camel/v1/build_types.go
+++ b/pkg/apis/camel/v1/build_types.go
@@ -204,6 +204,8 @@ type BuildStatus struct {
 	Image string `json:"image,omitempty"`
 	// the digest from image
 	Digest string `json:"digest,omitempty"`
+	// root image (the first image from which the incremental image has started)
+	RootImage string `json:"rootImage,omitempty"`
 	// the base image used for this build
 	BaseImage string `json:"baseImage,omitempty"`
 	// a list of artifacts contained in the build
diff --git a/pkg/apis/camel/v1/integrationkit_types.go b/pkg/apis/camel/v1/integrationkit_types.go
index f5b7ad1d5..2ddca5179 100644
--- a/pkg/apis/camel/v1/integrationkit_types.go
+++ b/pkg/apis/camel/v1/integrationkit_types.go
@@ -37,6 +37,7 @@ import (
 // +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.metadata.labels.camel\.apache\.org\/kit\.type`,description="The integration kit type"
 // +kubebuilder:printcolumn:name="Layout",type=string,JSONPath=`.metadata.labels.camel\.apache\.org\/kit\.layout`,description="The integration kit layout"
 // +kubebuilder:printcolumn:name="Image",type=string,JSONPath=`.status.image`,description="The integration kit image"
+// +kubebuilder:printcolumn:name="Root",type=string,JSONPath=`.status.rootImage`,description="The integration kit root image"
 
 // IntegrationKit defines a container image and additional configuration needed to run an `Integration`.
 // An `IntegrationKit` is a generic image generally built from the requirements of an `Integration`, but agnostic to it,
@@ -97,7 +98,9 @@ type IntegrationKitStatus struct {
 	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
 	// phase of the kit
 	Phase IntegrationKitPhase `json:"phase,omitempty"`
-	// base image used by the kit
+	// root image used by the kit (the first image from which the incremental image has started, typically a JDK/JRE base image)
+	RootImage string `json:"rootImage,omitempty"`
+	// base image used by the kit (could be another IntegrationKit)
 	BaseImage string `json:"baseImage,omitempty"`
 	// actual image name of the kit
 	Image string `json:"image,omitempty"`
diff --git a/pkg/apis/camel/v1/trait/builder.go b/pkg/apis/camel/v1/trait/builder.go
index 3b32cf53b..47d2278e0 100644
--- a/pkg/apis/camel/v1/trait/builder.go
+++ b/pkg/apis/camel/v1/trait/builder.go
@@ -29,6 +29,8 @@ type BuilderTrait struct {
 	Properties []string `property:"properties" json:"properties,omitempty"`
 	// The strategy to use, either `pod` or `routine` (default routine)
 	Strategy string `property:"strategy" json:"strategy,omitempty"`
+	// Specify a base image
+	BaseImage string `property:"base-image" json:"baseImage,omitempty"`
 	// Use the incremental image build option, to reuse existing containers (default `true`)
 	IncrementalImageBuild *bool `property:"incremental-image-build" json:"incrementalImageBuild,omitempty"`
 	// The build order strategy to use, either `dependencies`, `fifo` or `sequential` (default sequential)
diff --git a/pkg/builder/image.go b/pkg/builder/image.go
index c1b2ef6c2..a3abe1757 100644
--- a/pkg/builder/image.go
+++ b/pkg/builder/image.go
@@ -131,6 +131,7 @@ func incrementalImageContext(ctx *builderContext) error {
 
 		bestImage, commonLibs := findBestImage(images, ctx.Artifacts)
 		if bestImage.Image != "" {
+
 			ctx.BaseImage = bestImage.Image
 			ctx.SelectedArtifacts = make([]v1.Artifact, 0)
 
@@ -203,13 +204,16 @@ func listPublishedImages(context *builderContext) ([]v1.IntegrationKitStatus, er
 	}
 
 	images := make([]v1.IntegrationKitStatus, 0)
-	for _, item := range list.Items {
-		kit := item
-
+	for _, kit := range list.Items {
+		// Discard non ready kits
 		if kit.Status.Phase != v1.IntegrationKitPhaseReady {
 			continue
 		}
-
+		// Discard kits with a different root hierarchy
+		// context.BaseImage should still contain the root base image at this stage
+		if kit.Status.RootImage != context.BaseImage {
+			continue
+		}
 		images = append(images, kit.Status)
 	}
 	return images, nil
diff --git a/pkg/builder/jib.go b/pkg/builder/jib.go
index 75a8c26f8..264c95a08 100644
--- a/pkg/builder/jib.go
+++ b/pkg/builder/jib.go
@@ -46,8 +46,13 @@ func (t *jibTask) Do(ctx context.Context) v1.BuildStatus {
 	baseImage := t.build.Status.BaseImage
 	if baseImage == "" {
 		baseImage = t.task.BaseImage
-		status.BaseImage = baseImage
 	}
+	status.BaseImage = baseImage
+	rootImage := t.build.Status.RootImage
+	if rootImage == "" {
+		rootImage = t.task.BaseImage
+	}
+	status.RootImage = rootImage
 
 	contextDir := t.task.ContextDir
 	if contextDir == "" {
diff --git a/pkg/builder/spectrum.go b/pkg/builder/spectrum.go
index 2bc3b2673..c30f6f743 100644
--- a/pkg/builder/spectrum.go
+++ b/pkg/builder/spectrum.go
@@ -52,8 +52,13 @@ func (t *spectrumTask) Do(ctx context.Context) v1.BuildStatus {
 	baseImage := t.build.Status.BaseImage
 	if baseImage == "" {
 		baseImage = t.task.BaseImage
-		status.BaseImage = baseImage
 	}
+	status.BaseImage = baseImage
+	rootImage := t.build.Status.RootImage
+	if rootImage == "" {
+		rootImage = t.task.BaseImage
+	}
+	status.RootImage = rootImage
 
 	contextDir := t.task.ContextDir
 	if contextDir == "" {
diff --git a/pkg/client/camel/applyconfiguration/camel/v1/buildstatus.go b/pkg/client/camel/applyconfiguration/camel/v1/buildstatus.go
index 8158601fd..172865f2d 100644
--- a/pkg/client/camel/applyconfiguration/camel/v1/buildstatus.go
+++ b/pkg/client/camel/applyconfiguration/camel/v1/buildstatus.go
@@ -31,6 +31,7 @@ type BuildStatusApplyConfiguration struct {
 	Phase              *v1.BuildPhase                     `json:"phase,omitempty"`
 	Image              *string                            `json:"image,omitempty"`
 	Digest             *string                            `json:"digest,omitempty"`
+	RootImage          *string                            `json:"rootImage,omitempty"`
 	BaseImage          *string                            `json:"baseImage,omitempty"`
 	Artifacts          []ArtifactApplyConfiguration       `json:"artifacts,omitempty"`
 	Error              *string                            `json:"error,omitempty"`
@@ -78,6 +79,14 @@ func (b *BuildStatusApplyConfiguration) WithDigest(value string) *BuildStatusApp
 	return b
 }
 
+// WithRootImage sets the RootImage field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the RootImage field is set to the value of the last call.
+func (b *BuildStatusApplyConfiguration) WithRootImage(value string) *BuildStatusApplyConfiguration {
+	b.RootImage = &value
+	return b
+}
+
 // WithBaseImage sets the BaseImage field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the BaseImage field is set to the value of the last call.
diff --git a/pkg/client/camel/applyconfiguration/camel/v1/integrationkitstatus.go b/pkg/client/camel/applyconfiguration/camel/v1/integrationkitstatus.go
index 26791a21b..12650227a 100644
--- a/pkg/client/camel/applyconfiguration/camel/v1/integrationkitstatus.go
+++ b/pkg/client/camel/applyconfiguration/camel/v1/integrationkitstatus.go
@@ -28,6 +28,7 @@ import (
 type IntegrationKitStatusApplyConfiguration struct {
 	ObservedGeneration *int64                                      `json:"observedGeneration,omitempty"`
 	Phase              *v1.IntegrationKitPhase                     `json:"phase,omitempty"`
+	RootImage          *string                                     `json:"rootImage,omitempty"`
 	BaseImage          *string                                     `json:"baseImage,omitempty"`
 	Image              *string                                     `json:"image,omitempty"`
 	Digest             *string                                     `json:"digest,omitempty"`
@@ -62,6 +63,14 @@ func (b *IntegrationKitStatusApplyConfiguration) WithPhase(value v1.IntegrationK
 	return b
 }
 
+// WithRootImage sets the RootImage field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the RootImage field is set to the value of the last call.
+func (b *IntegrationKitStatusApplyConfiguration) WithRootImage(value string) *IntegrationKitStatusApplyConfiguration {
+	b.RootImage = &value
+	return b
+}
+
 // WithBaseImage sets the BaseImage field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the BaseImage field is set to the value of the last call.
diff --git a/pkg/controller/integrationkit/build.go b/pkg/controller/integrationkit/build.go
index 202b10bdf..4880ad365 100644
--- a/pkg/controller/integrationkit/build.go
+++ b/pkg/controller/integrationkit/build.go
@@ -199,6 +199,7 @@ func (action *buildAction) handleBuildRunning(ctx context.Context, kit *v1.Integ
 			)
 		}
 
+		kit.Status.RootImage = build.Status.RootImage
 		kit.Status.BaseImage = build.Status.BaseImage
 		kit.Status.Image = build.Status.Image
 
diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go
index b9dc0c0a3..58d5790d5 100644
--- a/pkg/trait/builder.go
+++ b/pkg/trait/builder.go
@@ -187,7 +187,7 @@ func (t *builderTrait) Apply(e *Environment) error {
 				Configuration: *taskConfOrDefault(tasksConf, "spectrum"),
 			},
 			PublishTask: v1.PublishTask{
-				BaseImage: e.Platform.Status.Build.BaseImage,
+				BaseImage: t.getBaseImage(e),
 				Image:     getImageName(e),
 				Registry:  e.Platform.Status.Build.Registry,
 			},
@@ -200,7 +200,7 @@ func (t *builderTrait) Apply(e *Environment) error {
 				Configuration: *taskConfOrDefault(tasksConf, "jib"),
 			},
 			PublishTask: v1.PublishTask{
-				BaseImage: e.Platform.Status.Build.BaseImage,
+				BaseImage: t.getBaseImage(e),
 				Image:     getImageName(e),
 				Registry:  e.Platform.Status.Build.Registry,
 			},
@@ -330,7 +330,7 @@ func (t *builderTrait) builderTask(e *Environment, taskConf *v1.BuildConfigurati
 			Name:          "builder",
 			Configuration: *taskConf,
 		},
-		BaseImage:    e.Platform.Status.Build.BaseImage,
+		BaseImage:    t.getBaseImage(e),
 		Runtime:      e.CamelCatalog.Runtime,
 		Dependencies: e.IntegrationKit.Spec.Dependencies,
 		Maven:        maven,
@@ -399,6 +399,14 @@ func getImageName(e *Environment) string {
 	return e.Platform.Status.Build.Registry.Address + "/" + organization + "/camel-k-" + e.IntegrationKit.Name + ":" + e.IntegrationKit.ResourceVersion
 }
 
+func (t *builderTrait) getBaseImage(e *Environment) string {
+	baseImage := t.BaseImage
+	if baseImage == "" {
+		baseImage = e.Platform.Status.Build.BaseImage
+	}
+	return baseImage
+}
+
 func (t *builderTrait) customTasks(tasksConf map[string]*v1.BuildConfiguration) ([]v1.Task, error) {
 	customTasks := make([]v1.Task, len(t.Tasks))
 
diff --git a/resources/traits.yaml b/resources/traits.yaml
index 6b9357beb..dee0be418 100755
--- a/resources/traits.yaml
+++ b/resources/traits.yaml
@@ -244,6 +244,9 @@ traits:
   - name: strategy
     type: string
     description: The strategy to use, either `pod` or `routine` (default routine)
+  - name: base-image
+    type: string
+    description: Specify a base image
   - name: incremental-image-build
     type: bool
     description: Use the incremental image build option, to reuse existing containers