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/04 10:53:00 UTC
[camel-k] 02/09: chore(ctrl): get rid of dynamic builder image
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 084b771dbb53f26733dccd6e3498a2f3c9e5804e
Author: Pasquale Congiusti <pa...@gmail.com>
AuthorDate: Mon Sep 25 12:43:38 2023 +0200
chore(ctrl): get rid of dynamic builder image
It was introduced to decouple the operator from the runtime. No longer needed as the native phase is executed using catalog tool image directly.
---
.../catalog_builder_test.go | 7 -
e2e/install/cli/global_test.go | 7 -
pkg/apis/camel/v1/camelcatalog_types.go | 2 +-
pkg/controller/catalog/initialize.go | 539 +--------------------
pkg/controller/integrationkit/build.go | 4 +-
5 files changed, 9 insertions(+), 550 deletions(-)
diff --git a/e2e/commonwithcustominstall/catalog_builder_test.go b/e2e/commonwithcustominstall/catalog_builder_test.go
index 0fe1061ed..436856c82 100644
--- a/e2e/commonwithcustominstall/catalog_builder_test.go
+++ b/e2e/commonwithcustominstall/catalog_builder_test.go
@@ -48,14 +48,7 @@ func TestCamelCatalogBuilder(t *testing.T) {
Should(Equal(corev1.ConditionTrue))
catalogName := fmt.Sprintf("camel-catalog-%s", strings.ToLower(defaults.DefaultRuntimeVersion))
Eventually(CamelCatalog(ns, catalogName)).ShouldNot(BeNil())
- catalog := CamelCatalog(ns, catalogName)()
- imageName := fmt.Sprintf("camel-k-runtime-%s-builder:%s", catalog.Spec.Runtime.Provider, strings.ToLower(catalog.Spec.Runtime.Version))
Eventually(CamelCatalogPhase(ns, catalogName), TestTimeoutMedium).Should(Equal(v1.CamelCatalogPhaseReady))
- Eventually(CamelCatalogImage(ns, catalogName), TestTimeoutMedium).Should(ContainSubstring(imageName))
- // The container may have been created by previous test
- Eventually(CamelCatalogCondition(ns, catalogName, v1.CamelCatalogConditionReady)().Message).Should(
- Or(Equal("Container image successfully built"), Equal("Container image exists on registry")),
- )
// Run an integration with a catalog not compatible
// The operator should create the catalog, but fail on reconciliation as it is not compatible
diff --git a/e2e/install/cli/global_test.go b/e2e/install/cli/global_test.go
index 479264255..27ad16159 100644
--- a/e2e/install/cli/global_test.go
+++ b/e2e/install/cli/global_test.go
@@ -66,14 +66,7 @@ func TestRunGlobalInstall(t *testing.T) {
Should(Equal(corev1.ConditionTrue))
catalogName := fmt.Sprintf("camel-catalog-%s", strings.ToLower(defaults.DefaultRuntimeVersion))
Eventually(CamelCatalog(operatorNamespace, catalogName)).ShouldNot(BeNil())
- catalog := CamelCatalog(operatorNamespace, catalogName)()
- imageName := fmt.Sprintf("camel-k-runtime-%s-builder:%s", catalog.Spec.Runtime.Provider, strings.ToLower(catalog.Spec.Runtime.Version))
Eventually(CamelCatalogPhase(operatorNamespace, catalogName), TestTimeoutMedium).Should(Equal(v1.CamelCatalogPhaseReady))
- Eventually(CamelCatalogImage(operatorNamespace, catalogName), TestTimeoutMedium).Should(ContainSubstring(imageName))
- // The container may have been created by previous test
- Eventually(CamelCatalogCondition(operatorNamespace, catalogName, v1.CamelCatalogConditionReady)().Message).Should(
- Or(Equal("Container image successfully built"), Equal("Container image exists on registry")),
- )
})
t.Run("Global test on namespace with platform", func(t *testing.T) {
diff --git a/pkg/apis/camel/v1/camelcatalog_types.go b/pkg/apis/camel/v1/camelcatalog_types.go
index 9b38eb98b..5b83f378c 100644
--- a/pkg/apis/camel/v1/camelcatalog_types.go
+++ b/pkg/apis/camel/v1/camelcatalog_types.go
@@ -67,7 +67,7 @@ type CamelCatalogSpec struct {
Loaders map[string]CamelLoader `json:"loaders" yaml:"loaders"`
}
-// CamelCatalogStatus defines the observed state of CamelCatalog. As the catalog is a static resource, we expect it to be empty.
+// CamelCatalogStatus defines the observed state of CamelCatalog.
type CamelCatalogStatus struct {
// ObservedGeneration is the most recent generation observed for this Catalog.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
diff --git a/pkg/controller/catalog/initialize.go b/pkg/controller/catalog/initialize.go
index a80712e65..20cd365d7 100644
--- a/pkg/controller/catalog/initialize.go
+++ b/pkg/controller/catalog/initialize.go
@@ -18,45 +18,10 @@ limitations under the License.
package catalog
import (
- "archive/tar"
- "bufio"
- "compress/gzip"
"context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "time"
v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
- "github.com/apache/camel-k/v2/pkg/builder"
- "github.com/apache/camel-k/v2/pkg/client"
platformutil "github.com/apache/camel-k/v2/pkg/platform"
- "github.com/apache/camel-k/v2/pkg/util"
- "github.com/apache/camel-k/v2/pkg/util/defaults"
- "github.com/apache/camel-k/v2/pkg/util/kubernetes"
- "github.com/apache/camel-k/v2/pkg/util/openshift"
- "github.com/apache/camel-k/v2/pkg/util/s2i"
-
- spectrum "github.com/container-tools/spectrum/pkg/builder"
-
- buildv1 "github.com/openshift/api/build/v1"
- imagev1 "github.com/openshift/api/image/v1"
-
- corev1 "k8s.io/api/core/v1"
- k8serrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/runtime/serializer"
- "k8s.io/client-go/rest"
-
- ctrl "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)
// NewInitializeAction returns a action that initializes the catalog configuration when not provided by the user.
@@ -90,509 +55,15 @@ func (action *initializeAction) Handle(ctx context.Context, catalog *v1.CamelCat
return catalog, nil
}
- if platform.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyS2I {
- return initializeS2i(ctx, action.client, platform, catalog)
- }
- // Default to spectrum
- // Make basic options for building image in the registry
- options, err := makeSpectrumOptions(ctx, action.client, platform.Namespace, platform.Status.Build.Registry)
- if err != nil {
- return catalog, err
- }
- return initializeSpectrum(options, platform, catalog)
+ return initialize(catalog)
}
-func initializeSpectrum(options spectrum.Options, ip *v1.IntegrationPlatform, catalog *v1.CamelCatalog) (*v1.CamelCatalog, error) {
+func initialize(catalog *v1.CamelCatalog) (*v1.CamelCatalog, error) {
target := catalog.DeepCopy()
- organization := ip.Status.Build.Registry.Organization
- if organization == "" {
- organization = catalog.Namespace
- }
- imageName := fmt.Sprintf(
- "%s/%s/camel-k-runtime-%s-builder:%s",
- ip.Status.Build.Registry.Address,
- organization,
- catalog.Spec.Runtime.Provider,
- strings.ToLower(catalog.Spec.Runtime.Version),
- )
-
- newStdR, newStdW, pipeErr := os.Pipe()
- defer util.CloseQuietly(newStdW)
-
- if pipeErr != nil {
- // In the unlikely case of an error, use stdout instead of aborting
- Log.Errorf(pipeErr, "Unable to remap I/O. Spectrum messages will be displayed on the stdout")
- newStdW = os.Stdout
- }
- go readSpectrumLogs(newStdR)
-
- // We use the future target image as a base just for the sake of pulling and verify it exists
- options.Base = imageName
- options.Stderr = newStdW
- options.Stdout = newStdW
-
- if !imageSnapshot(options.Base) && imageExistsSpectrum(options) {
- target.Status.Phase = v1.CamelCatalogPhaseReady
- target.Status.SetCondition(
- v1.CamelCatalogConditionReady,
- corev1.ConditionTrue,
- "Builder Image",
- "Container image exists on registry",
- )
- target.Status.Image = imageName
-
- return target, nil
- }
-
- // Now we properly set the base and the target image
- options.Base = catalog.Spec.GetQuarkusToolingImage()
- options.Target = imageName
-
- err := buildRuntimeBuilderWithTimeoutSpectrum(options, ip.Status.Build.GetBuildCatalogToolTimeout().Duration)
-
- if err != nil {
- Log.Error(err, "Error trying to build Camel K builder container")
- target.Status.Phase = v1.CamelCatalogPhaseError
- target.Status.SetErrorCondition(
- v1.CamelCatalogConditionReady,
- "Builder Image",
- err,
- )
- } else {
- target.Status.Phase = v1.CamelCatalogPhaseReady
- target.Status.SetCondition(
- v1.CamelCatalogConditionReady,
- corev1.ConditionTrue,
- "Builder Image",
- "Container image successfully built",
- )
- target.Status.Image = imageName
- }
-
- return target, nil
-}
-
-// nolint: maintidx // TODO: refactor the code
-func initializeS2i(ctx context.Context, c client.Client, ip *v1.IntegrationPlatform, catalog *v1.CamelCatalog) (*v1.CamelCatalog, error) {
- target := catalog.DeepCopy()
- // No registry in s2i
- imageName := fmt.Sprintf(
- "camel-k-runtime-%s-builder",
- catalog.Spec.Runtime.Provider,
- )
- imageTag := strings.ToLower(catalog.Spec.Runtime.Version)
-
- uidStr := getS2iUserID(ctx, c, ip, catalog)
-
- // Dockerfile
- dockerfile := `
- FROM ` + catalog.Spec.GetQuarkusToolingImage() + `
- USER ` + uidStr + `:0
- ADD --chown=` + uidStr + `:0 /usr/local/bin/kamel /usr/local/bin/kamel
- ADD --chown=` + uidStr + `:0 /usr/share/maven/mvnw/ /usr/share/maven/mvnw/
- `
- if imageSnapshot(imageName + ":" + imageTag) {
- dockerfile = dockerfile + `
- ADD --chown=` + uidStr + `:0 ` + defaults.LocalRepository + ` ` + defaults.LocalRepository + `
- `
- }
-
- owner := catalogReference(catalog)
-
- // BuildConfig
- bc := &buildv1.BuildConfig{
- TypeMeta: metav1.TypeMeta{
- APIVersion: buildv1.GroupVersion.String(),
- Kind: "BuildConfig",
- },
- ObjectMeta: metav1.ObjectMeta{
- Name: imageName,
- Namespace: ip.Namespace,
- Labels: map[string]string{
- kubernetes.CamelCreatorLabelKind: v1.CamelCatalogKind,
- kubernetes.CamelCreatorLabelName: catalog.Name,
- kubernetes.CamelCreatorLabelNamespace: catalog.Namespace,
- kubernetes.CamelCreatorLabelVersion: catalog.ResourceVersion,
- "camel.apache.org/runtime.version": catalog.Spec.Runtime.Version,
- "camel.apache.org/runtime.provider": string(catalog.Spec.Runtime.Provider),
- },
- },
- Spec: buildv1.BuildConfigSpec{
- CommonSpec: buildv1.CommonSpec{
- Source: buildv1.BuildSource{
- Type: buildv1.BuildSourceBinary,
- Dockerfile: &dockerfile,
- },
- Strategy: buildv1.BuildStrategy{
- DockerStrategy: &buildv1.DockerBuildStrategy{},
- },
- Output: buildv1.BuildOutput{
- To: &corev1.ObjectReference{
- Kind: "ImageStreamTag",
- Name: imageName + ":" + imageTag,
- },
- },
- },
- },
- }
-
- // ImageStream
- is := &imagev1.ImageStream{
- TypeMeta: metav1.TypeMeta{
- APIVersion: imagev1.GroupVersion.String(),
- Kind: "ImageStream",
- },
- ObjectMeta: metav1.ObjectMeta{
- Name: bc.Name,
- Namespace: bc.Namespace,
- Labels: map[string]string{
- kubernetes.CamelCreatorLabelKind: v1.CamelCatalogKind,
- kubernetes.CamelCreatorLabelName: catalog.Name,
- kubernetes.CamelCreatorLabelNamespace: catalog.Namespace,
- kubernetes.CamelCreatorLabelVersion: catalog.ResourceVersion,
- "camel.apache.org/runtime.provider": string(catalog.Spec.Runtime.Provider),
- },
- },
- Spec: imagev1.ImageStreamSpec{
- LookupPolicy: imagev1.ImageLookupPolicy{
- Local: true,
- },
- },
- }
-
- if !imageSnapshot(imageName+":"+imageTag) && imageExistsS2i(ctx, c, is) {
- target.Status.Phase = v1.CamelCatalogPhaseReady
- target.Status.SetCondition(
- v1.CamelCatalogConditionReady,
- corev1.ConditionTrue,
- "Builder Image",
- "Container image exists on registry (later)",
- )
- target.Status.Image = imageName
- return target, nil
- }
-
- err := s2i.BuildConfig(ctx, c, bc, owner)
- if err != nil {
- target.Status.Phase = v1.CamelCatalogPhaseError
- target.Status.SetErrorCondition(
- v1.CamelCatalogConditionReady,
- "Builder Image",
- err,
- )
- return target, err
- }
-
- err = s2i.ImageStream(ctx, c, is, owner)
- if err != nil {
- target.Status.Phase = v1.CamelCatalogPhaseError
- target.Status.SetErrorCondition(
- v1.CamelCatalogConditionReady,
- "Builder Image",
- err,
- )
- return target, err
- }
-
- err = util.WithTempDir(imageName+"-s2i-", func(tmpDir string) error {
- archive := filepath.Join(tmpDir, "archive.tar.gz")
-
- archiveFile, err := os.Create(archive)
- if err != nil {
- return fmt.Errorf("cannot create tar archive: %w", err)
- }
-
- directories := []string{
- "/usr/local/bin/kamel:/usr/local/bin/kamel",
- "/usr/share/maven/mvnw/:/usr/share/maven/mvnw/",
- }
- if imageSnapshot(imageName + ":" + imageTag) {
- directories = append(directories, defaults.LocalRepository+":"+defaults.LocalRepository)
- }
-
- err = tarEntries(archiveFile, directories...)
- if err != nil {
- return fmt.Errorf("cannot tar path entry: %w", err)
- }
-
- f, err := util.Open(archive)
- if err != nil {
- return err
- }
- httpCli, err := rest.HTTPClientFor(c.GetConfig())
- if err != nil {
- return err
- }
- restClient, err := apiutil.RESTClientForGVK(
- schema.GroupVersionKind{Group: "build.openshift.io", Version: "v1"}, false,
- c.GetConfig(), serializer.NewCodecFactory(c.GetScheme()), httpCli)
- if err != nil {
- return err
- }
-
- r := restClient.Post().
- Namespace(bc.Namespace).
- Body(bufio.NewReader(f)).
- Resource("buildconfigs").
- Name(bc.Name).
- SubResource("instantiatebinary").
- Do(ctx)
-
- if r.Error() != nil {
- return fmt.Errorf("cannot instantiate binary: %w", err)
- }
-
- data, err := r.Raw()
- if err != nil {
- return fmt.Errorf("no raw data retrieved: %w", err)
- }
-
- s2iBuild := buildv1.Build{}
- err = json.Unmarshal(data, &s2iBuild)
- if err != nil {
- return fmt.Errorf("cannot unmarshal instantiated binary response: %w", err)
- }
-
- err = s2i.WaitForS2iBuildCompletion(ctx, c, &s2iBuild)
- if err != nil {
- if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
- //nolint: contextcheck
- if err := s2i.CancelBuild(context.Background(), c, &s2iBuild); err != nil {
- return fmt.Errorf("cannot cancel s2i Build: %s/%s", s2iBuild.Namespace, s2iBuild.Name)
- }
- }
- return err
- }
- if s2iBuild.Status.Output.To != nil {
- Log.Infof("Camel K builder container image %s:%s@%s created", imageName, imageTag, s2iBuild.Status.Output.To.ImageDigest)
- }
-
- err = c.Get(ctx, ctrl.ObjectKeyFromObject(is), is)
- if err != nil {
- return err
- }
-
- if is.Status.DockerImageRepository == "" {
- return errors.New("dockerImageRepository not available in ImageStream")
- }
-
- target.Status.Phase = v1.CamelCatalogPhaseReady
- target.Status.SetCondition(
- v1.CamelCatalogConditionReady,
- corev1.ConditionTrue,
- "Builder Image",
- "Container image successfully built",
- )
- target.Status.Image = is.Status.DockerImageRepository + ":" + imageTag
-
- return f.Close()
- })
-
- if err != nil {
- target.Status.Phase = v1.CamelCatalogPhaseError
- target.Status.SetErrorCondition(
- v1.CamelCatalogConditionReady,
- "Builder Image",
- err,
- )
- return target, err
- }
+ // TODO - we may verify the existence of the catalog image (required by native build)
+ // or any other condition that may make a CamelCatalog to fail.
+ target.Status.Phase = v1.CamelCatalogPhaseReady
return target, nil
}
-
-func imageExistsSpectrum(options spectrum.Options) bool {
- Log.Infof("Checking if Camel K builder container %s already exists...", options.Base)
- ctrImg, err := spectrum.Pull(options)
- // Ignore the error-indent-flow as we save the need to declare a dependency explicitly
- // nolint: revive
- if ctrImg != nil && err == nil {
- if hash, err := ctrImg.Digest(); err != nil {
- Log.Errorf(err, "Cannot calculate digest")
- return false
- } else {
- Log.Infof("Found Camel K builder container with digest %s", hash.String())
- return true
- }
- }
-
- Log.Infof("Couldn't pull image due to %s", err.Error())
- return false
-}
-
-func imageExistsS2i(ctx context.Context, c client.Client, is *imagev1.ImageStream) bool {
- Log.Infof("Checking if Camel K builder container %s already exists...", is.Name)
- key := ctrl.ObjectKey{
- Namespace: is.Namespace,
- Name: is.Name,
- }
-
- err := c.Get(ctx, key, is)
-
- if err != nil {
- if !k8serrors.IsNotFound(err) {
- Log.Infof("Couldn't pull image due to %s", err.Error())
- }
- Log.Info("Could not find Camel K builder container")
- return false
- }
- Log.Info("Found Camel K builder container ")
- return true
-}
-
-func imageSnapshot(imageName string) bool {
- return strings.HasSuffix(imageName, "snapshot")
-}
-
-func buildRuntimeBuilderWithTimeoutSpectrum(options spectrum.Options, timeout time.Duration) error {
- // Backward compatibility with IP which had not a timeout field
- if timeout == 0 {
- return buildRuntimeBuilderImageSpectrum(options)
- }
- result := make(chan error, 1)
- go func() {
- result <- buildRuntimeBuilderImageSpectrum(options)
- }()
- select {
- case <-time.After(timeout):
- return fmt.Errorf("build timeout: %s", timeout.String())
- case result := <-result:
- return result
- }
-}
-
-// This func will take care to dynamically build an image that will contain the tools required
-// by the catalog build plus kamel binary and a maven wrapper required for the build.
-func buildRuntimeBuilderImageSpectrum(options spectrum.Options) error {
- if options.Base == "" {
- return fmt.Errorf("missing base image, likely catalog is not compatible with this Camel K version")
- }
- Log.Infof("Making up Camel K builder container %s", options.Target)
-
- if jobs := runtime.GOMAXPROCS(0); jobs > 1 {
- options.Jobs = jobs
- }
-
- directories := []string{
- "/usr/local/bin/kamel:/usr/local/bin/",
- "/usr/share/maven/mvnw/:/usr/share/maven/mvnw/",
- }
- if imageSnapshot(options.Target) {
- directories = append(directories, defaults.LocalRepository+":"+defaults.LocalRepository)
- }
-
- _, err := spectrum.Build(options, directories...)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func readSpectrumLogs(newStdOut io.Reader) {
- scanner := bufio.NewScanner(newStdOut)
-
- for scanner.Scan() {
- line := scanner.Text()
- Log.Infof(line)
- }
-}
-
-func makeSpectrumOptions(ctx context.Context, c client.Client, platformNamespace string, registry v1.RegistrySpec) (spectrum.Options, error) {
- options := spectrum.Options{}
- var err error
- registryConfigDir := ""
- if registry.Secret != "" {
- registryConfigDir, err = builder.MountSecret(ctx, c, platformNamespace, registry.Secret)
- if err != nil {
- return options, err
- }
- }
- options.PullInsecure = registry.Insecure
- options.PushInsecure = registry.Insecure
- options.PullConfigDir = registryConfigDir
- options.PushConfigDir = registryConfigDir
- options.Recursive = true
-
- return options, nil
-}
-
-// Add entries (files or folders) into tar with the possibility to change its path.
-func tarEntries(writer io.Writer, files ...string) error {
-
- gzw := gzip.NewWriter(writer)
- defer util.CloseQuietly(gzw)
-
- tw := tar.NewWriter(gzw)
- defer util.CloseQuietly(tw)
-
- // Iterate over files and add them to the tar archive
- for _, fileDetail := range files {
- fileSource := strings.Split(fileDetail, ":")[0]
- fileTarget := strings.Split(fileDetail, ":")[1]
- // ensure the src actually exists before trying to tar it
- if _, err := os.Stat(fileSource); err != nil {
- return fmt.Errorf("unable to tar files: %w", err)
- }
-
- if err := filepath.Walk(fileSource, func(file string, fi os.FileInfo, err error) error {
- if err != nil {
- return err
- }
-
- if !fi.Mode().IsRegular() {
- return nil
- }
-
- header, err := tar.FileInfoHeader(fi, fi.Name())
- if err != nil {
- return err
- }
-
- // update the name to correctly reflect the desired destination when un-taring
- header.Name = strings.TrimPrefix(strings.ReplaceAll(file, fileSource, fileTarget), string(filepath.Separator))
-
- if err := tw.WriteHeader(header); err != nil {
- return err
- }
-
- f, err := util.Open(file)
- if err != nil {
- return err
- }
-
- if _, err := io.Copy(tw, f); err != nil {
- return err
- }
-
- return f.Close()
- }); err != nil {
- return fmt.Errorf("unable to tar: %w", err)
- }
-
- }
- return nil
-}
-
-func catalogReference(catalog *v1.CamelCatalog) *unstructured.Unstructured {
- owner := &unstructured.Unstructured{}
- owner.SetName(catalog.Name)
- owner.SetUID(catalog.UID)
- owner.SetAPIVersion(catalog.APIVersion)
- owner.SetKind(catalog.Kind)
- return owner
-}
-
-// get user id from security context constraint configuration in namespace if present.
-func getS2iUserID(ctx context.Context, c client.Client, ip *v1.IntegrationPlatform, catalog *v1.CamelCatalog) string {
- ugfidStr := "1001"
- if ip.Status.Cluster == v1.IntegrationPlatformClusterOpenShift {
- uidStr, err := openshift.GetOpenshiftUser(ctx, c, catalog.GetNamespace())
- if err != nil {
- Log.Error(err, "Unable to retieve an Openshift user and group Ids.")
- }
- return uidStr
- }
- return ugfidStr
-}
diff --git a/pkg/controller/integrationkit/build.go b/pkg/controller/integrationkit/build.go
index d4b49fa97..faa32c72d 100644
--- a/pkg/controller/integrationkit/build.go
+++ b/pkg/controller/integrationkit/build.go
@@ -132,7 +132,9 @@ func (action *buildAction) handleBuildSubmitted(ctx context.Context, kit *v1.Int
return nil, fmt.Errorf("error while creating Camel K Builder service account: %w", err)
}
}
- buildConfig.ToolImage = env.CamelCatalog.Image
+ // The build operation, when executed as a Pod, should be executed by a container image containing the
+ // `kamel builder` command. Likely the same image running the operator should be fine.
+ buildConfig.ToolImage = defaults.OperatorImage()
buildConfig.BuilderPodNamespace = operatorNamespace
v1.SetBuilderConfigurationTasks(env.Pipeline, buildConfig)