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 dbea42fb03cd0a4c180be3d5c285060af5bd2573
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Wed Jan 10 17:23:39 2024 +0100

    feat(pipeline): publishing user task
    
    Assuming the last task of a pipeline is a publishing task and if it 
provides the digest of a published image, then, the build execution succeed
---
 config/crd/bases/camel.apache.org_builds.yaml      |  3 +
 .../bases/camel.apache.org_integrationkits.yaml    | 10 +--
 .../camel.apache.org_integrationplatforms.yaml     | 20 ++---
 .../crd/bases/camel.apache.org_integrations.yaml   | 10 +--
 .../bases/camel.apache.org_kameletbindings.yaml    |  9 +--
 config/crd/bases/camel.apache.org_pipes.yaml       |  9 +--
 docs/modules/ROOT/partials/apis/camel-k-crds.adoc  | 11 ++-
 docs/modules/traits/pages/builder.adoc             |  4 +-
 helm/camel-k/crds/crd-build.yaml                   |  3 +
 helm/camel-k/crds/crd-integration-kit.yaml         | 10 +--
 helm/camel-k/crds/crd-integration-platform.yaml    | 20 ++---
 helm/camel-k/crds/crd-integration.yaml             | 10 +--
 helm/camel-k/crds/crd-kamelet-binding.yaml         |  9 +--
 helm/camel-k/crds/crd-pipe.yaml                    |  9 +--
 pkg/apis/camel/v1/build_types.go                   |  2 +
 .../camel/applyconfiguration/camel/v1/usertask.go  |  9 +++
 pkg/controller/build/build_pod.go                  |  1 +
 pkg/controller/build/monitor_pod.go                | 94 +++++++++++++++++-----
 pkg/resources/resources.go                         | 45 +++++++----
 pkg/trait/builder.go                               | 75 +++++++++++++----
 pkg/trait/builder_test.go                          | 53 +++++++-----
 pkg/util/test/client.go                            |  2 +
 resources/traits.yaml                              |  8 +-
 23 files changed, 293 insertions(+), 133 deletions(-)

diff --git a/config/crd/bases/camel.apache.org_builds.yaml 
b/config/crd/bases/camel.apache.org_builds.yaml
index cb486ef73..02965a6bc 100644
--- a/config/crd/bases/camel.apache.org_builds.yaml
+++ b/config/crd/bases/camel.apache.org_builds.yaml
@@ -818,6 +818,9 @@ spec:
                         name:
                           description: name of the task
                           type: string
+                        publishingImage:
+                          description: the desired image build name
+                          type: string
                       type: object
                     jib:
                       description: a JibTask, for Jib strategy
diff --git a/config/crd/bases/camel.apache.org_integrationkits.yaml 
b/config/crd/bases/camel.apache.org_integrationkits.yaml
index 3d4714344..6ec3bd07d 100644
--- a/config/crd/bases/camel.apache.org_integrationkits.yaml
+++ b/config/crd/bases/camel.apache.org_integrationkits.yaml
@@ -285,11 +285,11 @@ spec:
                           type: string
                         type: array
                       tasksFilter:
-                        description: A list of tasks (available only when 
using `pod`
-                          strategy), sorted by the order of execution in a csv 
format,
-                          ie, `<taskName1>,<taskName2>,...`. Mind that you 
must include
-                          also the operator tasks (`builder`, 
`quarkus-native`, `package`,
-                          `jib`, `spectrum`, `s2i`) if you need to execute 
them.
+                        description: A list of tasks sorted by the order of 
execution
+                          in a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
+                          that you must include also the operator tasks 
(`builder`,
+                          `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`) if
+                          you need to execute them. Useful only wih `pod` 
strategy.
                         type: string
                       tasksLimitCPU:
                         description: A list of limit cpu configuration for the 
specific
diff --git a/config/crd/bases/camel.apache.org_integrationplatforms.yaml 
b/config/crd/bases/camel.apache.org_integrationplatforms.yaml
index 240fb4143..9a7c238c9 100644
--- a/config/crd/bases/camel.apache.org_integrationplatforms.yaml
+++ b/config/crd/bases/camel.apache.org_integrationplatforms.yaml
@@ -590,11 +590,11 @@ spec:
                           type: string
                         type: array
                       tasksFilter:
-                        description: A list of tasks (available only when 
using `pod`
-                          strategy), sorted by the order of execution in a csv 
format,
-                          ie, `<taskName1>,<taskName2>,...`. Mind that you 
must include
-                          also the operator tasks (`builder`, 
`quarkus-native`, `package`,
-                          `jib`, `spectrum`, `s2i`) if you need to execute 
them.
+                        description: A list of tasks sorted by the order of 
execution
+                          in a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
+                          that you must include also the operator tasks 
(`builder`,
+                          `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`) if
+                          you need to execute them. Useful only wih `pod` 
strategy.
                         type: string
                       tasksLimitCPU:
                         description: A list of limit cpu configuration for the 
specific
@@ -2480,11 +2480,11 @@ spec:
                           type: string
                         type: array
                       tasksFilter:
-                        description: A list of tasks (available only when 
using `pod`
-                          strategy), sorted by the order of execution in a csv 
format,
-                          ie, `<taskName1>,<taskName2>,...`. Mind that you 
must include
-                          also the operator tasks (`builder`, 
`quarkus-native`, `package`,
-                          `jib`, `spectrum`, `s2i`) if you need to execute 
them.
+                        description: A list of tasks sorted by the order of 
execution
+                          in a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
+                          that you must include also the operator tasks 
(`builder`,
+                          `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`) if
+                          you need to execute them. Useful only wih `pod` 
strategy.
                         type: string
                       tasksLimitCPU:
                         description: A list of limit cpu configuration for the 
specific
diff --git a/config/crd/bases/camel.apache.org_integrations.yaml 
b/config/crd/bases/camel.apache.org_integrations.yaml
index fa9fd5c0c..f0018b106 100644
--- a/config/crd/bases/camel.apache.org_integrations.yaml
+++ b/config/crd/bases/camel.apache.org_integrations.yaml
@@ -6503,11 +6503,11 @@ spec:
                           type: string
                         type: array
                       tasksFilter:
-                        description: A list of tasks (available only when 
using `pod`
-                          strategy), sorted by the order of execution in a csv 
format,
-                          ie, `<taskName1>,<taskName2>,...`. Mind that you 
must include
-                          also the operator tasks (`builder`, 
`quarkus-native`, `package`,
-                          `jib`, `spectrum`, `s2i`) if you need to execute 
them.
+                        description: A list of tasks sorted by the order of 
execution
+                          in a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
+                          that you must include also the operator tasks 
(`builder`,
+                          `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`) if
+                          you need to execute them. Useful only wih `pod` 
strategy.
                         type: string
                       tasksLimitCPU:
                         description: A list of limit cpu configuration for the 
specific
diff --git a/config/crd/bases/camel.apache.org_kameletbindings.yaml 
b/config/crd/bases/camel.apache.org_kameletbindings.yaml
index ad82cf8c1..cc0b8e10e 100644
--- a/config/crd/bases/camel.apache.org_kameletbindings.yaml
+++ b/config/crd/bases/camel.apache.org_kameletbindings.yaml
@@ -6787,12 +6787,11 @@ spec:
                               type: string
                             type: array
                           tasksFilter:
-                            description: A list of tasks (available only when 
using
-                              `pod` strategy), sorted by the order of 
execution in
-                              a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
-                              that you must include also the operator tasks 
(`builder`,
+                            description: A list of tasks sorted by the order 
of execution
+                              in a csv format, ie, 
`<taskName1>,<taskName2>,...`.
+                              Mind that you must include also the operator 
tasks (`builder`,
                               `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`)
-                              if you need to execute them.
+                              if you need to execute them. Useful only wih 
`pod` strategy.
                             type: string
                           tasksLimitCPU:
                             description: A list of limit cpu configuration for 
the
diff --git a/config/crd/bases/camel.apache.org_pipes.yaml 
b/config/crd/bases/camel.apache.org_pipes.yaml
index d018e4349..35e8fdc8c 100644
--- a/config/crd/bases/camel.apache.org_pipes.yaml
+++ b/config/crd/bases/camel.apache.org_pipes.yaml
@@ -6785,12 +6785,11 @@ spec:
                               type: string
                             type: array
                           tasksFilter:
-                            description: A list of tasks (available only when 
using
-                              `pod` strategy), sorted by the order of 
execution in
-                              a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
-                              that you must include also the operator tasks 
(`builder`,
+                            description: A list of tasks sorted by the order 
of execution
+                              in a csv format, ie, 
`<taskName1>,<taskName2>,...`.
+                              Mind that you must include also the operator 
tasks (`builder`,
                               `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`)
-                              if you need to execute them.
+                              if you need to execute them. Useful only wih 
`pod` strategy.
                             type: string
                           tasksLimitCPU:
                             description: A list of limit cpu configuration for 
the
diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc 
b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
index 29cc535a7..f2ec00ef0 100644
--- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
+++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
@@ -5674,6 +5674,13 @@ Deprecated: use ContainerCommands
 
 the command to execute
 
+|`publishingImage` +
+string
+|
+
+
+the desired image build name
+
 
 |===
 
@@ -5892,9 +5899,9 @@ string
 |
 
 
-A list of tasks (available only when using `pod` strategy), sorted by the 
order of execution in a csv format, ie, `<taskName1>,<taskName2>,...`.
+A list of tasks sorted by the order of execution in a csv format, ie, 
`<taskName1>,<taskName2>,...`.
 Mind that you must include also the operator tasks (`builder`, 
`quarkus-native`, `package`, `jib`, `spectrum`, `s2i`)
-if you need to execute them.
+if you need to execute them. Useful only wih `pod` strategy.
 
 |`tasksRequestCPU` +
 []string
diff --git a/docs/modules/traits/pages/builder.adoc 
b/docs/modules/traits/pages/builder.adoc
index 6330dea1e..8560b38f9 100755
--- a/docs/modules/traits/pages/builder.adoc
+++ b/docs/modules/traits/pages/builder.adoc
@@ -84,9 +84,9 @@ Syntax: [configmap\|secret]:name[/key], where name represents 
the resource name,
 
 | builder.tasks-filter
 | string
-| A list of tasks (available only when using `pod` strategy), sorted by the 
order of execution in a csv format, ie, `<taskName1>,<taskName2>,...`.
+| A list of tasks sorted by the order of execution in a csv format, ie, 
`<taskName1>,<taskName2>,...`.
 Mind that you must include also the operator tasks (`builder`, 
`quarkus-native`, `package`, `jib`, `spectrum`, `s2i`)
-if you need to execute them.
+if you need to execute them. Useful only wih `pod` strategy.
 
 | builder.tasks-request-cpu
 | []string
diff --git a/helm/camel-k/crds/crd-build.yaml b/helm/camel-k/crds/crd-build.yaml
index cb486ef73..02965a6bc 100644
--- a/helm/camel-k/crds/crd-build.yaml
+++ b/helm/camel-k/crds/crd-build.yaml
@@ -818,6 +818,9 @@ spec:
                         name:
                           description: name of the task
                           type: string
+                        publishingImage:
+                          description: the desired image build name
+                          type: string
                       type: object
                     jib:
                       description: a JibTask, for Jib strategy
diff --git a/helm/camel-k/crds/crd-integration-kit.yaml 
b/helm/camel-k/crds/crd-integration-kit.yaml
index 3d4714344..6ec3bd07d 100644
--- a/helm/camel-k/crds/crd-integration-kit.yaml
+++ b/helm/camel-k/crds/crd-integration-kit.yaml
@@ -285,11 +285,11 @@ spec:
                           type: string
                         type: array
                       tasksFilter:
-                        description: A list of tasks (available only when 
using `pod`
-                          strategy), sorted by the order of execution in a csv 
format,
-                          ie, `<taskName1>,<taskName2>,...`. Mind that you 
must include
-                          also the operator tasks (`builder`, 
`quarkus-native`, `package`,
-                          `jib`, `spectrum`, `s2i`) if you need to execute 
them.
+                        description: A list of tasks sorted by the order of 
execution
+                          in a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
+                          that you must include also the operator tasks 
(`builder`,
+                          `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`) if
+                          you need to execute them. Useful only wih `pod` 
strategy.
                         type: string
                       tasksLimitCPU:
                         description: A list of limit cpu configuration for the 
specific
diff --git a/helm/camel-k/crds/crd-integration-platform.yaml 
b/helm/camel-k/crds/crd-integration-platform.yaml
index 240fb4143..9a7c238c9 100644
--- a/helm/camel-k/crds/crd-integration-platform.yaml
+++ b/helm/camel-k/crds/crd-integration-platform.yaml
@@ -590,11 +590,11 @@ spec:
                           type: string
                         type: array
                       tasksFilter:
-                        description: A list of tasks (available only when 
using `pod`
-                          strategy), sorted by the order of execution in a csv 
format,
-                          ie, `<taskName1>,<taskName2>,...`. Mind that you 
must include
-                          also the operator tasks (`builder`, 
`quarkus-native`, `package`,
-                          `jib`, `spectrum`, `s2i`) if you need to execute 
them.
+                        description: A list of tasks sorted by the order of 
execution
+                          in a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
+                          that you must include also the operator tasks 
(`builder`,
+                          `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`) if
+                          you need to execute them. Useful only wih `pod` 
strategy.
                         type: string
                       tasksLimitCPU:
                         description: A list of limit cpu configuration for the 
specific
@@ -2480,11 +2480,11 @@ spec:
                           type: string
                         type: array
                       tasksFilter:
-                        description: A list of tasks (available only when 
using `pod`
-                          strategy), sorted by the order of execution in a csv 
format,
-                          ie, `<taskName1>,<taskName2>,...`. Mind that you 
must include
-                          also the operator tasks (`builder`, 
`quarkus-native`, `package`,
-                          `jib`, `spectrum`, `s2i`) if you need to execute 
them.
+                        description: A list of tasks sorted by the order of 
execution
+                          in a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
+                          that you must include also the operator tasks 
(`builder`,
+                          `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`) if
+                          you need to execute them. Useful only wih `pod` 
strategy.
                         type: string
                       tasksLimitCPU:
                         description: A list of limit cpu configuration for the 
specific
diff --git a/helm/camel-k/crds/crd-integration.yaml 
b/helm/camel-k/crds/crd-integration.yaml
index fa9fd5c0c..f0018b106 100644
--- a/helm/camel-k/crds/crd-integration.yaml
+++ b/helm/camel-k/crds/crd-integration.yaml
@@ -6503,11 +6503,11 @@ spec:
                           type: string
                         type: array
                       tasksFilter:
-                        description: A list of tasks (available only when 
using `pod`
-                          strategy), sorted by the order of execution in a csv 
format,
-                          ie, `<taskName1>,<taskName2>,...`. Mind that you 
must include
-                          also the operator tasks (`builder`, 
`quarkus-native`, `package`,
-                          `jib`, `spectrum`, `s2i`) if you need to execute 
them.
+                        description: A list of tasks sorted by the order of 
execution
+                          in a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
+                          that you must include also the operator tasks 
(`builder`,
+                          `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`) if
+                          you need to execute them. Useful only wih `pod` 
strategy.
                         type: string
                       tasksLimitCPU:
                         description: A list of limit cpu configuration for the 
specific
diff --git a/helm/camel-k/crds/crd-kamelet-binding.yaml 
b/helm/camel-k/crds/crd-kamelet-binding.yaml
index ad82cf8c1..cc0b8e10e 100644
--- a/helm/camel-k/crds/crd-kamelet-binding.yaml
+++ b/helm/camel-k/crds/crd-kamelet-binding.yaml
@@ -6787,12 +6787,11 @@ spec:
                               type: string
                             type: array
                           tasksFilter:
-                            description: A list of tasks (available only when 
using
-                              `pod` strategy), sorted by the order of 
execution in
-                              a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
-                              that you must include also the operator tasks 
(`builder`,
+                            description: A list of tasks sorted by the order 
of execution
+                              in a csv format, ie, 
`<taskName1>,<taskName2>,...`.
+                              Mind that you must include also the operator 
tasks (`builder`,
                               `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`)
-                              if you need to execute them.
+                              if you need to execute them. Useful only wih 
`pod` strategy.
                             type: string
                           tasksLimitCPU:
                             description: A list of limit cpu configuration for 
the
diff --git a/helm/camel-k/crds/crd-pipe.yaml b/helm/camel-k/crds/crd-pipe.yaml
index d018e4349..35e8fdc8c 100644
--- a/helm/camel-k/crds/crd-pipe.yaml
+++ b/helm/camel-k/crds/crd-pipe.yaml
@@ -6785,12 +6785,11 @@ spec:
                               type: string
                             type: array
                           tasksFilter:
-                            description: A list of tasks (available only when 
using
-                              `pod` strategy), sorted by the order of 
execution in
-                              a csv format, ie, `<taskName1>,<taskName2>,...`. 
Mind
-                              that you must include also the operator tasks 
(`builder`,
+                            description: A list of tasks sorted by the order 
of execution
+                              in a csv format, ie, 
`<taskName1>,<taskName2>,...`.
+                              Mind that you must include also the operator 
tasks (`builder`,
                               `quarkus-native`, `package`, `jib`, `spectrum`, 
`s2i`)
-                              if you need to execute them.
+                              if you need to execute them. Useful only wih 
`pod` strategy.
                             type: string
                           tasksLimitCPU:
                             description: A list of limit cpu configuration for 
the
diff --git a/pkg/apis/camel/v1/build_types.go b/pkg/apis/camel/v1/build_types.go
index 5e2a99681..076cf0426 100644
--- a/pkg/apis/camel/v1/build_types.go
+++ b/pkg/apis/camel/v1/build_types.go
@@ -192,6 +192,8 @@ type UserTask struct {
        ContainerCommand string `json:"command,omitempty"`
        // the command to execute
        ContainerCommands []string `json:"commands,omitempty"`
+       // the desired image build name
+       PublishingImage string `json:"publishingImage,omitempty"`
 }
 
 // BuildStatus defines the observed state of Build.
diff --git a/pkg/client/camel/applyconfiguration/camel/v1/usertask.go 
b/pkg/client/camel/applyconfiguration/camel/v1/usertask.go
index f4fc11c89..7a85991d1 100644
--- a/pkg/client/camel/applyconfiguration/camel/v1/usertask.go
+++ b/pkg/client/camel/applyconfiguration/camel/v1/usertask.go
@@ -26,6 +26,7 @@ type UserTaskApplyConfiguration struct {
        ContainerImage             *string  `json:"image,omitempty"`
        ContainerCommand           *string  `json:"command,omitempty"`
        ContainerCommands          []string `json:"commands,omitempty"`
+       PublishingImage            *string  `json:"publishingImage,omitempty"`
 }
 
 // UserTaskApplyConfiguration constructs an declarative configuration of the 
UserTask type for use with
@@ -75,3 +76,11 @@ func (b *UserTaskApplyConfiguration) 
WithContainerCommands(values ...string) *Us
        }
        return b
 }
+
+// WithPublishingImage sets the PublishingImage 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 PublishingImage field is set to the value of 
the last call.
+func (b *UserTaskApplyConfiguration) WithPublishingImage(value string) 
*UserTaskApplyConfiguration {
+       b.PublishingImage = &value
+       return b
+}
diff --git a/pkg/controller/build/build_pod.go 
b/pkg/controller/build/build_pod.go
index 5396573a7..4914e771d 100644
--- a/pkg/controller/build/build_pod.go
+++ b/pkg/controller/build/build_pod.go
@@ -572,6 +572,7 @@ func addCustomTaskToPod(build *v1.Build, task *v1.UserTask, 
pod *corev1.Pod) {
                WorkingDir:      filepath.Join(builderDir, build.Name),
                Env:             proxyFromEnvironment(),
        }
+       container.Env = append(container.Env, corev1.EnvVar{Name: 
"INTEGRATION_KIT_IMAGE", Value: task.PublishingImage})
 
        configureResources(task.Name, build, &container)
        addContainerToPod(build, container, pod)
diff --git a/pkg/controller/build/monitor_pod.go 
b/pkg/controller/build/monitor_pod.go
index 31fe5c540..0fcf60a72 100644
--- a/pkg/controller/build/monitor_pod.go
+++ b/pkg/controller/build/monitor_pod.go
@@ -146,24 +146,20 @@ func (action *monitorPodAction) Handle(ctx 
context.Context, build *v1.Build) (*v
                // Account for the Build metrics
                observeBuildResult(build, build.Status.Phase, buildCreator, 
duration)
 
-               for _, task := range build.Spec.Tasks {
-                       if t := task.Buildah; t != nil {
-                               build.Status.Image = t.Image
-
-                               break
-                       } else if t := task.Kaniko; t != nil {
-                               build.Status.Image = t.Image
-
-                               break
-                       }
-               }
-               // Reconcile image digest from build container status if 
available
-               for _, container := range pod.Status.ContainerStatuses {
-                       if container.Name == "buildah" {
-                               build.Status.Digest = 
container.State.Terminated.Message
-
-                               break
-                       }
+               build.Status.Image = publishTaskImageName(build.Spec.Tasks)
+               build.Status.Digest = publishTaskDigest(build.Spec.Tasks, 
pod.Status.ContainerStatuses)
+               if build.Status.Digest == "" {
+                       // Likely to happen for users provided publishing tasks 
and not providing the digest image among statuses
+                       build.Status.Phase = v1.BuildPhaseError
+                       build.Status.SetCondition(
+                               "ImageDigestAvailable",
+                               corev1.ConditionFalse,
+                               "ImageDigestAvailable",
+                               fmt.Sprintf(
+                                       "%s publishing task completed but no 
digest is available in container status. Make sure that the process 
successfully push the image to the registry and write its digest to 
/dev/termination-log",
+                                       publishTaskName(build.Spec.Tasks),
+                               ),
+                       )
                }
 
        case corev1.PodFailed:
@@ -341,3 +337,65 @@ func (action *monitorPodAction) 
setConditionsFromTerminationMessages(ctx context
        }
 
 }
+
+// we expect that the last task is any of the supported publishing task
+// or a custom user task
+func publishTask(tasks []v1.Task) *v1.Task {
+       if len(tasks) > 0 {
+               return &tasks[len(tasks)-1]
+
+       }
+
+       return nil
+}
+
+func publishTaskImageName(tasks []v1.Task) string {
+       t := publishTask(tasks)
+       if t == nil {
+               return ""
+       }
+       if t.Custom != nil {
+               return t.Custom.PublishingImage
+       } else if t.Spectrum != nil {
+               return t.Spectrum.Image
+       } else if t.Jib != nil {
+               return t.Jib.Image
+       } else if t.Buildah != nil {
+               return t.Buildah.Image
+       } else if t.Kaniko != nil {
+               return t.Kaniko.Image
+       }
+
+       return ""
+}
+
+func publishTaskName(tasks []v1.Task) string {
+       t := publishTask(tasks)
+       if t == nil {
+               return ""
+       }
+       if t.Custom != nil {
+               return t.Custom.Name
+       } else if t.Spectrum != nil {
+               return t.Spectrum.Name
+       } else if t.Jib != nil {
+               return t.Jib.Name
+       } else if t.Buildah != nil {
+               return t.Buildah.Name
+       } else if t.Kaniko != nil {
+               return t.Kaniko.Name
+       }
+
+       return ""
+}
+
+func publishTaskDigest(tasks []v1.Task, cntStates []corev1.ContainerStatus) 
string {
+       taskName := publishTaskName(tasks)
+       // Reconcile image digest from build container status if available
+       for _, container := range cntStates {
+               if container.Name == taskName {
+                       return container.State.Terminated.Message
+               }
+       }
+       return ""
+}
diff --git a/pkg/resources/resources.go b/pkg/resources/resources.go
index 536db4af3..04bba969b 100644
--- a/pkg/resources/resources.go
+++ b/pkg/resources/resources.go
@@ -117,9 +117,9 @@ var assets = func() http.FileSystem {
                "/crd/bases/camel.apache.org_builds.yaml": 
&vfsgen۰CompressedFileInfo{
                        name:             "camel.apache.org_builds.yaml",
                        modTime:          time.Time{},
-                       uncompressedSize: 95394,
+                       uncompressedSize: 95542,
 
-                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\x6b\x73\x1b\x37\x96\xe8\x77\xfd\x8a\x53\xf1\x07\xcb\x55\x22\x35\xe3\xc9\xce\x66\x75\x6b\xeb\x96\x56\x4e\x66\x35\x4e\x6c\xaf\x29\x7b\x32\xb5\xb5\x55\x02\xbb\x0f\x49\x84\xdd\x40\x5f\x00\x2d\x9a\xb9\x75\xff\xfb\x2d\xbc\xfa\x21\xb2\xbb\x01\x8a\xb4\x9d\x72\xe3\x4b\x62\xaa\x01\x9c\x83\xc7\x79\xe1\x3c\x9e\xc1\xe4\x78\xed\xec\x19\xfc\x4c\x13\x64\x12\x53\x50\x1c\xd4\x0a\xe1\xba\x20\xc9\x0a\x61\xc6\x17\x6a\x43\x04\xc2\x
 [...]
+                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\x6b\x73\x1b\x37\x96\xe8\x77\xfd\x8a\x53\xf1\x07\xcb\x55\x22\x35\xe3\x64\x67\xb3\xba\xb5\x75\x4b\x2b\x27\xb3\x1a\x27\xb6\xd7\x94\x3d\x99\xda\xda\x2a\x81\xdd\x87\x24\xc2\x6e\xa0\x2f\x80\x16\xcd\xdc\xba\xff\xfd\x16\x5e\xfd\x10\xd9\xdd\x00\x45\xda\x9e\x72\xe3\x4b\x62\xaa\x01\x9c\x83\xc7\x79\xe1\x3c\x9e\xc1\xe4\x78\xed\xec\x19\xfc\x42\x13\x64\x12\x53\x50\x1c\xd4\x0a\xe1\xba\x20\xc9\x0a\x61\xc6\x17\x6a\x43\x04\xc2\x
 [...]
                },
                "/crd/bases/camel.apache.org_camelcatalogs.yaml": 
&vfsgen۰CompressedFileInfo{
                        name:             "camel.apache.org_camelcatalogs.yaml",
@@ -131,30 +131,30 @@ var assets = func() http.FileSystem {
                "/crd/bases/camel.apache.org_integrationkits.yaml": 
&vfsgen۰CompressedFileInfo{
                        name:             
"camel.apache.org_integrationkits.yaml",
                        modTime:          time.Time{},
-                       uncompressedSize: 25734,
+                       uncompressedSize: 26227,
 
-                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x3c\x5d\x6f\x1b\xb7\xb2\xef\xfa\x15\x83\xf8\x21\x36\x20\xc9\xed\xb9\x45\x71\xa1\xd3\x5b\xc0\x75\x92\x1e\x9f\x38\x89\xaf\xe5\xb4\x38\x38\x2d\x20\x6a\x77\x24\x31\xda\x25\xb7\x24\x57\xb6\xee\xc7\x7f\xbf\xe0\x90\xdc\x0f\x69\x77\xb5\x96\xe3\xde\xf4\xa0\x7a\x49\xbc\x24\x87\xc3\x99\xe1\x7c\x92\x3c\x81\xd1\xe7\xfb\x0d\x4e\xe0\x9a\x47\x28\x34\xc6\x60\x24\x98\x15\xc2\x45\xc6\xa2\x15\xc2\x54\x2e\xcc\x3d\x53\x08\x6f\x64\x2e\x
 [...]
+                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x3c\x5d\x6f\xe3\xb6\xb2\xef\xfe\x15\x83\xcd\xc3\x26\x80\xed\xb4\xbd\x45\x71\xe1\xd3\xbb\x40\x9a\xdd\xed\xc9\xd9\xec\x6e\x6e\x9c\x6d\x71\x70\x7a\x00\xd3\xd2\xd8\xe6\x5a\x22\x55\x92\x72\xe2\xfb\xf1\xdf\x2f\x38\x24\xf5\x61\x4b\xb2\xe2\x6c\x7a\xdb\x83\xea\x25\xb1\x44\x0e\x87\x33\xc3\xf9\xe2\x90\x27\x30\xfa\x72\xcf\xe0\x04\xae\x79\x84\x42\x63\x0c\x46\x82\x59\x21\x5c\x64\x2c\x5a\x21\x4c\xe5\xc2\xdc\x33\x85\xf0\x56\xe6\x
 [...]
                },
                "/crd/bases/camel.apache.org_integrationplatforms.yaml": 
&vfsgen۰CompressedFileInfo{
                        name:             
"camel.apache.org_integrationplatforms.yaml",
                        modTime:          time.Time{},
-                       uncompressedSize: 202778,
+                       uncompressedSize: 203764,
 
-                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\x6b\x73\xe3\x36\xb2\xe8\xf7\xfc\x0a\x94\x53\xa7\xc6\x93\xb2\xa4\x99\xdd\x93\x6c\x8e\xcf\xe6\xdc\xeb\x78\x26\x89\x33\x0f\xfb\xda\x9e\xd9\xb3\x95\xa4\x22\x88\x6c\x49\x88\x49\x80\x0b\x80\xb2\x95\xda\x1f\x7f\x0b\x0d\x80\x0f\x49\x04\xa9\x87\x1f\x93\x48\xa9\xda\x1d\x4b\x24\xd0\x68\x34\xfa\x85\x7e\x7c\x4e\x7a\xbb\xfb\x7c\xf6\x39\x79\xcb\x22\xe0\x0a\x62\xa2\x05\xd1\x53\x20\x27\x19\x8d\xa6\x40\xae\xc4\x58\xdf\x52\x09\x
 [...]
+                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\xfb\x73\xdb\xb6\xb2\xf0\xef\xfd\x2b\x30\xee\xdc\xb1\xd3\xd1\x23\xe9\xb9\x7d\x5c\xdf\xd3\xf3\x7d\xae\x93\xb6\x6e\xe2\xd8\x9f\xed\xe4\xdc\x33\x6d\xa7\x82\xc8\x95\x84\x98\x04\x78\x01\x50\xb6\x3a\xe7\x8f\xff\x06\x0b\x80\xa4\x1e\x04\xa9\x87\x63\xa7\x95\x3a\xd3\xd8\x16\x09\x2c\x16\x8b\x7d\x61\x1f\x9f\x93\xee\xee\x3e\x9f\x7d\x4e\xde\xb0\x08\xb8\x82\x98\x68\x41\xf4\x04\xc8\x49\x46\xa3\x09\x90\x6b\x31\xd2\x77\x54\x02\x
 [...]
                },
                "/crd/bases/camel.apache.org_integrations.yaml": 
&vfsgen۰CompressedFileInfo{
                        name:             "camel.apache.org_integrations.yaml",
                        modTime:          time.Time{},
-                       uncompressedSize: 519809,
+                       uncompressedSize: 520302,
 
-                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xfd\x7b\x73\x1b\x37\x96\x30\x8c\xff\x9f\x4f\x81\xb2\x53\x8f\xa4\x8d\x48\xd9\x99\xd9\xa9\x1d\xff\xa6\x9e\x94\x56\x96\x13\xfd\x62\xcb\x2a\x49\x49\x9e\x94\x93\x4d\xc0\x6e\x90\xc4\xa3\x6e\xa0\x17\x40\x53\xe2\xbc\x7e\xbf\xfb\x5b\x38\x00\xfa\xc2\x5b\x1f\xb4\x44\xc7\x99\x6d\x4c\x55\xc6\x14\xd9\xa7\x71\x39\x38\xf7\xcb\x73\x32\x7a\xba\xf1\xc5\x73\xf2\x96\x27\x4c\x68\x96\x12\x23\x89\x99\x33\x72\x5a\xd0\x64\xce\xc8\x8d\x9c\x
 [...]
+                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xfd\x7b\x73\x1b\x37\x96\x30\x8c\xff\x9f\x4f\x81\xb2\x53\x8f\xa4\x8d\x48\xd9\x99\xd9\xa9\x1d\xff\xa6\x9e\x94\x56\x96\x13\xfd\x62\xcb\x2a\x49\x49\x9e\x94\x93\x4d\xc0\x6e\x90\xc4\xa3\x6e\xa0\x17\x40\x53\xe2\xbc\x7e\xbf\xfb\x5b\x38\x00\xfa\xc2\x5b\x1f\xb4\x44\xc7\x99\x6d\x4c\x55\xc6\x14\xd9\xa7\x71\x39\x38\xf7\xcb\x73\x32\x7a\xba\xf1\xc5\x73\xf2\x96\x27\x4c\x68\x96\x12\x23\x89\x99\x33\x72\x5a\xd0\x64\xce\xc8\x8d\x9c\x
 [...]
                },
                "/crd/bases/camel.apache.org_kameletbindings.yaml": 
&vfsgen۰CompressedFileInfo{
                        name:             
"camel.apache.org_kameletbindings.yaml",
                        modTime:          time.Time{},
-                       uncompressedSize: 601461,
+                       uncompressedSize: 601982,
 
-                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xfd\x6b\x73\x1b\x37\xb6\x30\x0a\x7f\xf7\xaf\x40\xd9\xa9\x47\xd2\x0e\x49\xd9\x99\x99\xd4\x8c\xdf\xa9\x9d\xd2\xc8\x72\xa2\x37\xb6\xcc\xb2\x94\xe4\x49\x39\xd9\x09\xd8\x0d\x92\xd8\xea\x06\x7a\x00\x34\x25\xe6\xf8\xfc\xf7\x53\x58\x00\xfa\xc2\x9b\xb0\x9a\x92\xc6\x9e\x69\x4c\x55\xc6\xa4\xd8\xab\x71\x59\x58\xf7\xcb\x33\x32\xbc\xbf\xf1\xe4\x19\x79\xc3\x13\x26\x34\x4b\x89\x91\xc4\xcc\x19\x39\x29\x68\x32\x67\xe4\x52\x4e\xcd\x
 [...]
+                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xfd\x6b\x73\x1b\x37\xb6\x30\x0a\x7f\xf7\xaf\x40\xd9\xa9\x47\xd2\x0e\x49\xd9\x99\x99\xd4\x8c\xdf\xa9\x9d\xd2\xc8\x72\xa2\x37\xb6\xcc\xb2\x94\xe4\x49\x39\xd9\x09\xd8\x0d\x92\xd8\xea\x06\x7a\x00\x34\x25\xe6\xf8\xfc\xf7\x53\x58\x00\xfa\xc2\x9b\xb0\x9a\x92\xc6\x9e\x69\x4c\x55\xc6\xa4\xd8\xab\x71\x59\x58\xf7\xcb\x33\x32\xbc\xbf\xf1\xe4\x19\x79\xc3\x13\x26\x34\x4b\x89\x91\xc4\xcc\x19\x39\x29\x68\x32\x67\xe4\x52\x4e\xcd\x
 [...]
                },
                "/crd/bases/camel.apache.org_kamelets.yaml": 
&vfsgen۰CompressedFileInfo{
                        name:             "camel.apache.org_kamelets.yaml",
@@ -166,14 +166,26 @@ var assets = func() http.FileSystem {
                "/crd/bases/camel.apache.org_pipes.yaml": 
&vfsgen۰CompressedFileInfo{
                        name:             "camel.apache.org_pipes.yaml",
                        modTime:          time.Time{},
-                       uncompressedSize: 568839,
+                       uncompressedSize: 569360,
 
-                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xfd\x7b\x73\x1b\x37\xb6\x28\x8a\xff\x9f\x4f\x81\x92\x53\x47\xd2\x8e\x48\xd9\x99\x99\xd4\x1e\xff\xa6\x4e\x4a\x5b\x96\x13\xfd\x62\xcb\x2c\x49\x49\x4e\xca\xc9\x4e\xc0\x6e\x90\xc4\x51\x37\xd0\x1b\x40\x53\x62\xae\xef\x77\xbf\x85\x05\xa0\x1f\x7c\x09\xab\x29\x69\xe4\x99\xc6\x54\x65\x4c\x8a\xbd\x1a\x8f\x85\xf5\x7e\xbc\x20\x83\x87\x1b\x5f\xbc\x20\xef\x78\xc2\x84\x66\x29\x31\x92\x98\x19\x23\x27\x05\x4d\x66\x8c\x5c\xc9\x89\x
 [...]
+                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xfd\x7b\x73\x1b\x37\xb6\x28\x8a\xff\x9f\x4f\x81\x92\x53\x47\xd2\x8e\x48\xd9\x99\x99\xd4\x1e\xff\xa6\x4e\x4a\x5b\x96\x13\xfd\x62\xcb\x2c\x49\x49\x4e\xca\xc9\x4e\xc0\x6e\x90\xc4\x51\x37\xd0\x1b\x40\x53\x62\xae\xef\x77\xbf\x85\x05\xa0\x1f\x7c\x09\xab\x29\x69\xe4\x99\xc6\x54\x65\x4c\x8a\xbd\x1a\x8f\x85\xf5\x7e\xbc\x20\x83\x87\x1b\x5f\xbc\x20\xef\x78\xc2\x84\x66\x29\x31\x92\x98\x19\x23\x27\x05\x4d\x66\x8c\x5c\xc9\x89\x
 [...]
                },
                "/manager": &vfsgen۰DirInfo{
                        name:    "manager",
                        modTime: time.Time{},
                },
+               "/manager/bundle": &vfsgen۰DirInfo{
+                       name:    "bundle",
+                       modTime: time.Time{},
+               },
+               "/manager/bundle/manifests": &vfsgen۰DirInfo{
+                       name:    "manifests",
+                       modTime: time.Time{},
+               },
+               "/manager/bundle/metadata": &vfsgen۰DirInfo{
+                       name:    "metadata",
+                       modTime: time.Time{},
+               },
                "/manager/operator-deployment.yaml": &vfsgen۰CompressedFileInfo{
                        name:             "operator-deployment.yaml",
                        modTime:          time.Time{},
@@ -753,9 +765,9 @@ var assets = func() http.FileSystem {
                "/traits.yaml": &vfsgen۰CompressedFileInfo{
                        name:             "traits.yaml",
                        modTime:          time.Time{},
-                       uncompressedSize: 73382,
+                       uncompressedSize: 73737,
 
-                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xbd\x6b\x73\x1c\xb9\x91\x00\xf8\x5d\xbf\x02\xd1\x73\x1b\x24\x75\xfd\xa0\xc6\x6b\xef\x2c\xd7\xf2\x1e\x47\xa3\xb1\x69\xbd\x78\x22\x67\xbc\x0e\x9d\xc2\x8d\xae\x42\x77\x43\xac\x06\x6a\x00\x14\xa9\x9e\xdb\xfb\xef\x17\xc8\x4c\x3c\xaa\xba\x9a\xdd\x94\x48\xd9\xda\xf5\x6e\x84\x47\x24\x0b\x40\x22\x91\x48\xe4\x3b\xbf\x61\xa3\xfb\xfb\xbf\x47\xdf\xb0\x97\xb2\x10\xca\x8a\x92\x39\xcd\xdc\x52\xb0\xd3\x9a\x17\x4b\xc1\x2e\xf4\xdc\x
 [...]
+                       compressedContent: 
[]byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xbd\xfb\x73\x1c\xb9\x91\x30\xf8\xbb\xfe\x0a\x44\xcf\x7d\x41\x52\xd7\x0f\x4a\xfe\xec\x9d\xe5\x5a\xb3\xc7\xd1\x68\x6c\x7a\xf4\xe0\x89\x9c\xf1\xe7\xd0\x29\xdc\xe8\x2a\x74\x37\xc4\x6a\xa0\x06\x40\x91\xea\xb9\xbd\xff\xfd\x02\x99\x89\x47\x55\x57\xb3\x9b\x12\x29\x5b\xdf\x7a\x37\xc2\x23\x92\x05\x20\x91\x48\x24\xf2\x9d\xdf\xb0\xd1\xfd\xfd\xdf\xa3\x6f\xd8\x4b\x59\x08\x65\x45\xc9\x9c\x66\x6e\x29\xd8\x69\xcd\x8b\xa5\x60\x17\x
 [...]
                },
        }
        fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
@@ -800,6 +812,7 @@ var assets = func() http.FileSystem {
                fs["/crd/bases/camel.apache.org_pipes.yaml"].(os.FileInfo),
        }
        fs["/manager"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
+               fs["/manager/bundle"].(os.FileInfo),
                fs["/manager/operator-deployment.yaml"].(os.FileInfo),
                fs["/manager/operator-service-account.yaml"].(os.FileInfo),
                
fs["/manager/patch-image-pull-policy-always.yaml"].(os.FileInfo),
@@ -811,6 +824,10 @@ var assets = func() http.FileSystem {
                fs["/manager/patch-toleration.yaml"].(os.FileInfo),
                fs["/manager/patch-watch-namespace-global.yaml"].(os.FileInfo),
        }
+       fs["/manager/bundle"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
+               fs["/manager/bundle/manifests"].(os.FileInfo),
+               fs["/manager/bundle/metadata"].(os.FileInfo),
+       }
        fs["/prometheus"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
                fs["/prometheus/operator-pod-monitor.yaml"].(os.FileInfo),
                fs["/prometheus/operator-prometheus-rule.yaml"].(os.FileInfo),
diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go
index 87c3e484d..843deb2e4 100644
--- a/pkg/trait/builder.go
+++ b/pkg/trait/builder.go
@@ -167,13 +167,18 @@ func (t *builderTrait) Apply(e *Environment) error {
        if err != nil {
                return err
        }
+
+       imageName := getImageName(e)
        // Building task
        builderTask, err := t.builderTask(e, taskConfOrDefault(tasksConf, 
"builder"))
        if err != nil {
-               e.IntegrationKit.Status.Phase = v1.IntegrationKitPhaseError
-               
e.IntegrationKit.Status.SetCondition("IntegrationKitPropertiesFormatValid", 
corev1.ConditionFalse,
-                       "IntegrationKitPropertiesFormatValid", fmt.Sprintf("One 
or more properties where not formatted as expected: %s", err.Error()))
-               if err := e.Client.Status().Update(e.Ctx, e.IntegrationKit); 
err != nil {
+               if err := failIntegrationKit(
+                       e,
+                       "IntegrationKitPropertiesFormatValid",
+                       corev1.ConditionFalse,
+                       "IntegrationKitPropertiesFormatValid",
+                       fmt.Sprintf("One or more properties where not formatted 
as expected: %s", err.Error()),
+               ); err != nil {
                        return err
                }
                return nil
@@ -188,21 +193,21 @@ func (t *builderTrait) Apply(e *Environment) error {
                        realBuildStrategy = 
e.Platform.Status.Build.BuildConfiguration.Strategy
                }
                if len(t.Tasks) > 0 && realBuildStrategy != v1.BuildStrategyPod 
{
-                       e.IntegrationKit.Status.Phase = 
v1.IntegrationKitPhaseError
-                       
e.IntegrationKit.Status.SetCondition("IntegrationKitTasksValid",
+                       if err := failIntegrationKit(
+                               e,
+                               "IntegrationKitTasksValid",
                                corev1.ConditionFalse,
                                "IntegrationKitTasksValid",
                                fmt.Sprintf("Pipeline tasks unavailable when 
using `%s` platform build strategy: use `%s` instead.",
                                        realBuildStrategy,
                                        v1.BuildStrategyPod),
-                       )
-                       if err := e.Client.Status().Update(e.Ctx, 
e.IntegrationKit); err != nil {
+                       ); err != nil {
                                return err
                        }
                        return nil
                }
 
-               customTasks, err := t.customTasks(tasksConf)
+               customTasks, err := t.customTasks(tasksConf, imageName)
                if err != nil {
                        return err
                }
@@ -228,7 +233,7 @@ func (t *builderTrait) Apply(e *Environment) error {
                        },
                        PublishTask: v1.PublishTask{
                                BaseImage: t.getBaseImage(e),
-                               Image:     getImageName(e),
+                               Image:     imageName,
                                Registry:  e.Platform.Status.Build.Registry,
                        },
                }})
@@ -241,7 +246,7 @@ func (t *builderTrait) Apply(e *Environment) error {
                        },
                        PublishTask: v1.PublishTask{
                                BaseImage: t.getBaseImage(e),
-                               Image:     getImageName(e),
+                               Image:     imageName,
                                Registry:  e.Platform.Status.Build.Registry,
                        },
                }})
@@ -277,7 +282,7 @@ func (t *builderTrait) Apply(e *Environment) error {
                                Configuration: *taskConfOrDefault(tasksConf, 
"buildah"),
                        },
                        PublishTask: v1.PublishTask{
-                               Image:    getImageName(e),
+                               Image:    imageName,
                                Registry: e.Platform.Status.Build.Registry,
                        },
                        Verbose:       t.Verbose,
@@ -301,7 +306,7 @@ func (t *builderTrait) Apply(e *Environment) error {
                                Configuration: *taskConfOrDefault(tasksConf, 
"kaniko"),
                        },
                        PublishTask: v1.PublishTask{
-                               Image:    getImageName(e),
+                               Image:    imageName,
                                Registry: e.Platform.Status.Build.Registry,
                        },
                        Cache: v1.KanikoTaskCache{
@@ -317,6 +322,15 @@ func (t *builderTrait) Apply(e *Environment) error {
        if t.TasksFilter != "" {
                flt := strings.Split(t.TasksFilter, ",")
                if pipelineTasks, err = filter(pipelineTasks, flt); err != nil {
+                       if err := failIntegrationKit(
+                               e,
+                               "IntegrationKitTasksValid",
+                               corev1.ConditionFalse,
+                               "IntegrationKitTasksValid",
+                               err.Error(),
+                       ); err != nil {
+                               return err
+                       }
                        return err
                }
        }
@@ -325,6 +339,16 @@ func (t *builderTrait) Apply(e *Environment) error {
        return nil
 }
 
+// when this trait fails, we must report the failure into the related 
IntegrationKit if it affects the success of the Build.
+func failIntegrationKit(e *Environment, conditionType 
v1.IntegrationKitConditionType, status corev1.ConditionStatus, reason, message 
string) error {
+       e.IntegrationKit.Status.Phase = v1.IntegrationKitPhaseError
+       e.IntegrationKit.Status.SetCondition(conditionType, status, reason, 
message)
+       if err := e.Client.Status().Update(e.Ctx, e.IntegrationKit); err != nil 
{
+               return err
+       }
+       return nil
+}
+
 func (t *builderTrait) builderTask(e *Environment, taskConf 
*v1.BuildConfiguration) (*v1.BuilderTask, error) {
        maven := v1.MavenBuildSpec{
                MavenSpec: e.Platform.Status.Build.Maven,
@@ -454,7 +478,7 @@ func (t *builderTrait) getBaseImage(e *Environment) string {
        return baseImage
 }
 
-func (t *builderTrait) customTasks(tasksConf 
map[string]*v1.BuildConfiguration) ([]v1.Task, error) {
+func (t *builderTrait) customTasks(tasksConf 
map[string]*v1.BuildConfiguration, imageName string) ([]v1.Task, error) {
        customTasks := make([]v1.Task, len(t.Tasks))
 
        for i, t := range t.Tasks {
@@ -476,6 +500,7 @@ func (t *builderTrait) customTasks(tasksConf 
map[string]*v1.BuildConfiguration)
                                        Name:          splitted[0],
                                        Configuration: 
*taskConfOrDefault(tasksConf, splitted[0]),
                                },
+                               PublishingImage:   imageName,
                                ContainerImage:    splitted[1],
                                ContainerCommands: containerCommands,
                        },
@@ -603,5 +628,27 @@ func filter(tasks []v1.Task, filterTasks []string) 
([]v1.Task, error) {
                        return nil, fmt.Errorf("no task exist for %s name", f)
                }
        }
+       // make sure the last task is either a publishing task or a custom task
+       if len(filteredTasks) == 0 || 
!publishingOrUserTask(filteredTasks[len(filteredTasks)-1]) {
+               return nil, fmt.Errorf("last pipeline task is not a publishing 
or a user task")
+       }
        return filteredTasks, nil
 }
+
+// return true if the task is either a publishing task or a custom user task
+func publishingOrUserTask(t v1.Task) bool {
+       if t.Custom != nil {
+               return true
+       } else if t.Spectrum != nil {
+               return true
+       } else if t.S2i != nil {
+               return true
+       } else if t.Jib != nil {
+               return true
+       } else if t.Buildah != nil {
+               return true
+       } else if t.Kaniko != nil {
+               return true
+       }
+       return false
+}
diff --git a/pkg/trait/builder_test.go b/pkg/trait/builder_test.go
index 343dc5e31..bb0e66b0d 100644
--- a/pkg/trait/builder_test.go
+++ b/pkg/trait/builder_test.go
@@ -112,7 +112,16 @@ func createBuilderTestEnv(cluster 
v1.IntegrationPlatformCluster, strategy v1.Int
        if err != nil {
                panic(err)
        }
-       client, _ := test.NewFakeClient()
+       itk := &v1.IntegrationKit{
+               Status: v1.IntegrationKitStatus{
+                       Phase: v1.IntegrationKitPhaseBuildSubmitted,
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Namespace: "ns",
+                       Name:      "my-kit",
+               },
+       }
+       client, _ := test.NewFakeClient(itk)
        res := &Environment{
                Ctx:          context.TODO(),
                CamelCatalog: c,
@@ -127,14 +136,7 @@ func createBuilderTestEnv(cluster 
v1.IntegrationPlatformCluster, strategy v1.Int
                                Phase: v1.IntegrationPhaseDeploying,
                        },
                },
-               IntegrationKit: &v1.IntegrationKit{
-                       Status: v1.IntegrationKitStatus{
-                               Phase: v1.IntegrationKitPhaseBuildSubmitted,
-                       },
-                       ObjectMeta: metav1.ObjectMeta{
-                               Name: "my-kit",
-                       },
-               },
+               IntegrationKit: itk,
                Platform: &v1.IntegrationPlatform{
                        Spec: v1.IntegrationPlatformSpec{
                                Cluster: cluster,
@@ -231,7 +233,8 @@ func TestCustomTaskBuilderTraitInvalidStrategy(t 
*testing.T) {
 
        err := builderTrait.Apply(env)
 
-       assert.NotNil(t, err)
+       // The error will be reported to IntegrationKits
+       assert.Nil(t, err)
        assert.Equal(t, env.IntegrationKit.Status.Phase, 
v1.IntegrationKitPhaseError)
        assert.Equal(t, env.IntegrationKit.Status.Conditions[0].Status, 
corev1.ConditionFalse)
        assert.Equal(t, env.IntegrationKit.Status.Conditions[0].Type, 
v1.IntegrationKitConditionType("IntegrationKitTasksValid"))
@@ -245,7 +248,8 @@ func TestCustomTaskBuilderTraitInvalidStrategyOverride(t 
*testing.T) {
 
        err := builderTrait.Apply(env)
 
-       assert.NotNil(t, err)
+       // The error will be reported to IntegrationKits
+       assert.Nil(t, err)
        assert.Equal(t, env.IntegrationKit.Status.Phase, 
v1.IntegrationKitPhaseError)
        assert.Equal(t, env.IntegrationKit.Status.Conditions[0].Status, 
corev1.ConditionFalse)
        assert.Equal(t, env.IntegrationKit.Status.Conditions[0].Type, 
v1.IntegrationKitConditionType("IntegrationKitTasksValid"))
@@ -285,7 +289,8 @@ func TestInvalidMavenProfilesBuilderTrait(t *testing.T) {
 
        err := builderTrait.Apply(env)
 
-       assert.NotNil(t, err)
+       // The error will be reported to IntegrationKits
+       assert.Nil(t, err)
        assert.Equal(t, env.IntegrationKit.Status.Phase, 
v1.IntegrationKitPhaseError)
        assert.Equal(t, env.IntegrationKit.Status.Conditions[0].Status, 
corev1.ConditionFalse)
        assert.Contains(t, env.IntegrationKit.Status.Conditions[0].Message, 
"fakeprofile")
@@ -314,7 +319,7 @@ func TestBuilderCustomTasks(t *testing.T) {
        builderTrait.Tasks = append(builderTrait.Tasks, "test;alpine;ls")
        builderTrait.Tasks = append(builderTrait.Tasks, `test;alpine;mvn test`)
 
-       tasks, err := builderTrait.customTasks(nil)
+       tasks, err := builderTrait.customTasks(nil, "my-kit-img")
 
        assert.Nil(t, err)
        assert.Equal(t, 2, len(tasks))
@@ -332,7 +337,7 @@ func TestBuilderCustomTasksFailure(t *testing.T) {
        builderTrait := createNominalBuilderTraitTest()
        builderTrait.Tasks = append(builderTrait.Tasks, "test;alpine")
 
-       _, err := builderTrait.customTasks(nil)
+       _, err := builderTrait.customTasks(nil, "my-kit-img")
 
        assert.NotNil(t, err)
 }
@@ -341,7 +346,7 @@ func TestBuilderCustomTasksScript(t *testing.T) {
        builderTrait := createNominalBuilderTraitTest()
        builderTrait.Tasks = append(builderTrait.Tasks, "test;alpine;/bin/bash 
-c \"cd test && ls; echo 'helooo'\"")
 
-       tasks, err := builderTrait.customTasks(nil)
+       tasks, err := builderTrait.customTasks(nil, "my-kit-img")
 
        assert.Nil(t, err)
        assert.Equal(t, 1, len(tasks))
@@ -499,26 +504,36 @@ func TestBuilderTasksFilterNotExistingTasks(t *testing.T) 
{
        assert.Equal(t, "no task exist for missing-task name", err.Error())
 }
 
-func TestBuilderTasksFilterOperatorTasks(t *testing.T) {
+func TestBuilderTasksFilterMissingPublishTasks(t *testing.T) {
        env := createBuilderTestEnv(v1.IntegrationPlatformClusterKubernetes, 
v1.IntegrationPlatformBuildPublishStrategyJib, v1.BuildStrategyPod)
        builderTrait := createNominalBuilderTraitTest()
        builderTrait.TasksFilter = "builder,package"
 
+       err := builderTrait.Apply(env)
+       assert.NotNil(t, err)
+       assert.Equal(t, "last pipeline task is not a publishing or a user 
task", err.Error())
+}
+
+func TestBuilderTasksFilterOperatorTasks(t *testing.T) {
+       env := createBuilderTestEnv(v1.IntegrationPlatformClusterKubernetes, 
v1.IntegrationPlatformBuildPublishStrategyJib, v1.BuildStrategyPod)
+       builderTrait := createNominalBuilderTraitTest()
+       builderTrait.TasksFilter = "builder,package,jib"
+
        err := builderTrait.Apply(env)
        assert.Nil(t, err)
        pipelineTasks := tasksByName(env.Pipeline)
-       assert.Equal(t, []string{"builder", "package"}, pipelineTasks)
+       assert.Equal(t, []string{"builder", "package", "jib"}, pipelineTasks)
 }
 
 func TestBuilderTasksFilterAndReorderOperatorTasks(t *testing.T) {
        env := createBuilderTestEnv(v1.IntegrationPlatformClusterKubernetes, 
v1.IntegrationPlatformBuildPublishStrategyJib, v1.BuildStrategyPod)
        builderTrait := createNominalBuilderTraitTest()
-       builderTrait.TasksFilter = "package,builder"
+       builderTrait.TasksFilter = "package,builder,jib"
 
        err := builderTrait.Apply(env)
        assert.Nil(t, err)
        pipelineTasks := tasksByName(env.Pipeline)
-       assert.Equal(t, []string{"package", "builder"}, pipelineTasks)
+       assert.Equal(t, []string{"package", "builder", "jib"}, pipelineTasks)
 }
 
 func TestBuilderTasksFilterAndReorderCustomTasks(t *testing.T) {
diff --git a/pkg/util/test/client.go b/pkg/util/test/client.go
index 9105719eb..081a6733a 100644
--- a/pkg/util/test/client.go
+++ b/pkg/util/test/client.go
@@ -23,6 +23,7 @@ import (
        "strings"
 
        "github.com/apache/camel-k/v2/pkg/apis"
+       v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
        "github.com/apache/camel-k/v2/pkg/client"
        camel 
"github.com/apache/camel-k/v2/pkg/client/camel/clientset/versioned"
        fakecamelclientset 
"github.com/apache/camel-k/v2/pkg/client/camel/clientset/versioned/fake"
@@ -68,6 +69,7 @@ func NewFakeClient(initObjs ...runtime.Object) 
(client.Client, error) {
                        },
                ).
                WithRuntimeObjects(initObjs...).
+               WithStatusSubresource(&v1.IntegrationKit{}).
                Build()
 
        camelClientset := 
fakecamelclientset.NewSimpleClientset(filterObjects(scheme, initObjs, func(gvk 
schema.GroupVersionKind) bool {
diff --git a/resources/traits.yaml b/resources/traits.yaml
index dac366094..a63e521e9 100755
--- a/resources/traits.yaml
+++ b/resources/traits.yaml
@@ -285,10 +285,10 @@ traits:
       with format `<name>;<container-image>;<container-command>`.
   - name: tasks-filter
     type: string
-    description: A list of tasks (available only when using `pod` strategy), 
sorted
-      by the order of execution in a csv format, ie, 
`<taskName1>,<taskName2>,...`.
-      Mind that you must include also the operator tasks (`builder`, 
`quarkus-native`,
-      `package`, `jib`, `spectrum`, `s2i`) if you need to execute them.
+    description: A list of tasks sorted by the order of execution in a csv 
format,
+      ie, `<taskName1>,<taskName2>,...`. Mind that you must include also the 
operator
+      tasks (`builder`, `quarkus-native`, `package`, `jib`, `spectrum`, `s2i`) 
if
+      you need to execute them. Useful only wih `pod` strategy.
   - name: tasks-request-cpu
     type: '[]string'
     description: A list of request cpu configuration for the specific task 
with format

Reply via email to