Repository: apache/camel-k
Pull request #2544 (2021-08-03 11:27:54)

(backport) Forcelly set QUARKUS_LOG_CONSOLE_JSON to false if the trait is not provided (GH issue #2539)
<!-- Description -->
Ensures that QUARKUS_LOG_CONSOLE_JSON is set to false if the `logging.json` trait is not provided



<!--
Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". 

You can (optionally) mark this PR with labels "kind/bug" or "kind/feature" to make sure
the text is added to the right section of the release notes. 
-->

**Release Note**
```release-note
Ensures that QUARKUS_LOG_CONSOLE_JSON is set to false if the `logging.json` trait is not provided
```


Patch:
diff --git a/config/manifests/bases/camel-k.clusterserviceversion.yaml b/config/manifests/bases/camel-k.clusterserviceversion.yaml
index 676c9c800..7dc7657d3 100644
--- a/config/manifests/bases/camel-k.clusterserviceversion.yaml
+++ b/config/manifests/bases/camel-k.clusterserviceversion.yaml
@@ -30,7 +30,7 @@ metadata:
     operators.operatorframework.io/project_layout: go.kubebuilder.io/v2
     repository: https://github.com/apache/camel-k
     support: Camel
-  name: camel-k.v1.5.0
+  name: camel-k.v1.5.1
   namespace: placeholder
 spec:
   apiservicedefinitions: {}
@@ -154,4 +154,4 @@ spec:
   selector:
     matchLabels:
       name: camel-k-operator
-  version: 1.5.0-snapshot
+  version: 1.5.1-snapshot
diff --git a/pkg/apis/camel/v1/integration_types_support.go b/pkg/apis/camel/v1/integration_types_support.go
index 31f09d5d9..c0df521e2 100644
--- a/pkg/apis/camel/v1/integration_types_support.go
+++ b/pkg/apis/camel/v1/integration_types_support.go
@@ -148,13 +148,20 @@ func (in *IntegrationSpec) GetConfigurationProperty(property string) string {
 		if confSpec.Type == "property" && strings.HasPrefix(confSpec.Value, property) {
 			splitConf := strings.Split(confSpec.Value, "=")
 			if len(splitConf) > 1 {
-				return splitConf[1]
+				return trimFirstLeadingSpace(splitConf[1])
 			}
 		}
 	}
 	return ""
 }
 
+func trimFirstLeadingSpace(val string) string {
+	if strings.HasPrefix(val, " ") {
+		return val[1:]
+	}
+	return val
+}
+
 // AddOrReplaceGeneratedResources --
 func (in *IntegrationStatus) AddOrReplaceGeneratedResources(resources ...ResourceSpec) {
 	newResources := make([]ResourceSpec, 0)
diff --git a/pkg/controller/build/monitor_pod.go b/pkg/controller/build/monitor_pod.go
index c14300d2b..3c3bf1307 100644
--- a/pkg/controller/build/monitor_pod.go
+++ b/pkg/controller/build/monitor_pod.go
@@ -224,7 +224,13 @@ func (action *monitorPodAction) addTimeoutAnnotation(ctx context.Context, pod *c
 		return nil
 	}
 	return action.patchPod(ctx, pod, func(p *corev1.Pod) {
-		p.GetAnnotations()[timeoutAnnotation] = time.String()
+		if p.GetAnnotations() != nil {
+			p.GetAnnotations()[timeoutAnnotation] = time.String()
+		} else {
+			p.SetAnnotations(map[string]string{
+				timeoutAnnotation: time.String(),
+			})
+		}
 	})
 }
 
diff --git a/pkg/controller/build/monitor_routine.go b/pkg/controller/build/monitor_routine.go
index 64a3bbd67..8ba1b6772 100644
--- a/pkg/controller/build/monitor_routine.go
+++ b/pkg/controller/build/monitor_routine.go
@@ -68,7 +68,11 @@ func (action *monitorRoutineAction) Handle(ctx context.Context, build *v1.Build)
 			build.Status.Error = "Build routine exists"
 			return build, nil
 		}
-		// Start the build asynchronously to avoid blocking the reconcile loop
+		status := v1.BuildStatus{Phase: v1.BuildPhaseRunning}
+		if err := action.updateBuildStatus(ctx, build, status); err != nil {
+			return nil, err
+		}
+		// Start the build asynchronously to avoid blocking the reconciliation loop
 		routines.Store(build.Name, true)
 		go action.runBuild(build)
 
@@ -92,11 +96,7 @@ func (action *monitorRoutineAction) runBuild(build *v1.Build) {
 	ctxWithTimeout, cancel := context.WithDeadline(ctx, build.Status.StartedAt.Add(build.Spec.Timeout.Duration))
 	defer cancel()
 
-	status := v1.BuildStatus{Phase: v1.BuildPhaseRunning}
-	if err := action.updateBuildStatus(ctx, build, status); err != nil {
-		return
-	}
-
+	status := v1.BuildStatus{}
 	buildDir := ""
 	Builder := builder.New(action.client)
 
diff --git a/pkg/controller/integration/build_kit.go b/pkg/controller/integration/build_kit.go
index 30a629f12..005ed45f9 100644
--- a/pkg/controller/integration/build_kit.go
+++ b/pkg/controller/integration/build_kit.go
@@ -122,13 +122,13 @@ func (action *buildKitAction) Handle(ctx context.Context, integration *v1.Integr
 	// Add some information for post-processing, this may need to be refactored
 	// to a proper data structure
 	platformKit.Labels = map[string]string{
-		"camel.apache.org/kit.type":             v1.IntegrationKitTypePlatform,
-		"camel.apache.org/created.by.kind":      v1.IntegrationKind,
-		"camel.apache.org/created.by.name":      integration.Name,
-		"camel.apache.org/created.by.namespace": integration.Namespace,
-		"camel.apache.org/created.by.version":   integration.ResourceVersion,
-		"camel.apache.org/runtime.version":      integration.Status.RuntimeVersion,
-		"camel.apache.org/runtime.provider":     string(integration.Status.RuntimeProvider),
+		"camel.apache.org/kit.type":           v1.IntegrationKitTypePlatform,
+		"camel.apache.org/runtime.version":    integration.Status.RuntimeVersion,
+		"camel.apache.org/runtime.provider":   string(integration.Status.RuntimeProvider),
+		kubernetes.CamelCreatorLabelKind:      v1.IntegrationKind,
+		kubernetes.CamelCreatorLabelName:      integration.Name,
+		kubernetes.CamelCreatorLabelNamespace: integration.Namespace,
+		kubernetes.CamelCreatorLabelVersion:   integration.ResourceVersion,
 	}
 
 	// Set the kit to have the same characteristics as the integrations
diff --git a/pkg/event/manager.go b/pkg/event/manager.go
index 6278ba8fe..c8362d2eb 100644
--- a/pkg/event/manager.go
+++ b/pkg/event/manager.go
@@ -21,15 +21,17 @@ import (
 	"context"
 	"fmt"
 
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/tools/record"
+
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/client"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/apache/camel-k/pkg/util/log"
-	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/client-go/tools/record"
-	runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 const (
@@ -260,17 +262,21 @@ func NotifyBuildError(ctx context.Context, c client.Client, recorder record.Even
 }
 
 // nolint:lll
-func notifyIfPhaseUpdated(ctx context.Context, c client.Client, recorder record.EventRecorder, new runtime.Object, oldPhase, newPhase string, resourceType, name, reason, info string) {
+func notifyIfPhaseUpdated(ctx context.Context, c client.Client, recorder record.EventRecorder, new ctrl.Object, oldPhase, newPhase string, resourceType, name, reason, info string) {
 	// Update information about phase changes
 	if oldPhase != newPhase {
 		phase := newPhase
 		if phase == "" {
 			phase = "[none]"
 		}
-		recorder.Eventf(new, corev1.EventTypeNormal, reason, "%s %s in phase %q%s", resourceType, name, phase, info)
+		recorder.Eventf(new, corev1.EventTypeNormal, reason, "%s %q in phase %q%s", resourceType, name, phase, info)
 
 		if creatorRef, creator := getCreatorObject(ctx, c, new); creatorRef != nil && creator != nil {
-			recorder.Eventf(creator, corev1.EventTypeNormal, ReasonRelatedObjectChanged, "%s %s subresource %s (%s) changed phase to %q%s", creatorRef.Kind, creatorRef.Name, name, resourceType, phase, info)
+			if namespace := new.GetNamespace(); namespace == creatorRef.Namespace {
+				recorder.Eventf(creator, corev1.EventTypeNormal, ReasonRelatedObjectChanged, "%s %q, created by %s %q, changed phase to %q%s", resourceType, name, creatorRef.Kind, creatorRef.Name, phase, info)
+			} else {
+				recorder.Eventf(creator, corev1.EventTypeNormal, ReasonRelatedObjectChanged, "%s \"%s/%s\", created by %s %q, changed phase to %q%s", resourceType, namespace, name, creatorRef.Kind, creatorRef.Name, phase, info)
+			}
 		}
 	}
 }
@@ -305,12 +311,8 @@ func getCreatorObject(ctx context.Context, c client.Client, obj runtime.Object)
 	if ref := kubernetes.GetCamelCreator(obj); ref != nil {
 		if ref.Kind == "Integration" {
 			it := v1.NewIntegration(ref.Namespace, ref.Name)
-			key := runtimeclient.ObjectKey{
-				Namespace: ref.Namespace,
-				Name:      ref.Name,
-			}
-			if err := c.Get(ctx, key, &it); err != nil {
-				log.Infof("Cannot get information about the Integration creating resource %v: %v", ref, err)
+			if err := c.Get(ctx, ctrl.ObjectKeyFromObject(&it), &it); err != nil {
+				log.Infof("Cannot get information about the creator Integration %v: %v", ref, err)
 				return nil, nil
 			}
 			return ref, &it
diff --git a/pkg/trait/container.go b/pkg/trait/container.go
index 429d10425..6da7a5338 100644
--- a/pkg/trait/container.go
+++ b/pkg/trait/container.go
@@ -22,6 +22,7 @@ import (
 	"path"
 	"sort"
 
+	"github.com/apache/camel-k/pkg/util/kubernetes"
 	appsv1 "k8s.io/api/apps/v1"
 	"k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
@@ -175,11 +176,11 @@ func (t *containerTrait) configureDependencies(e *Environment) error {
 			// Add some information for post-processing, this may need to be refactored
 			// to a proper data structure
 			kit.Labels = map[string]string{
-				"camel.apache.org/kit.type":             v1.IntegrationKitTypeExternal,
-				"camel.apache.org/created.by.kind":      v1.IntegrationKind,
-				"camel.apache.org/created.by.name":      e.Integration.Name,
-				"camel.apache.org/created.by.namespace": e.Integration.Namespace,
-				"camel.apache.org/created.by.version":   e.Integration.ResourceVersion,
+				"camel.apache.org/kit.type":           v1.IntegrationKitTypeExternal,
+				kubernetes.CamelCreatorLabelKind:      v1.IntegrationKind,
+				kubernetes.CamelCreatorLabelName:      e.Integration.Name,
+				kubernetes.CamelCreatorLabelNamespace: e.Integration.Namespace,
+				kubernetes.CamelCreatorLabelVersion:   e.Integration.ResourceVersion,
 			}
 
 			t.L.Infof("image %s", kit.Spec.Image)
diff --git a/pkg/trait/logging.go b/pkg/trait/logging.go
index d092aacb8..808ce1cbe 100644
--- a/pkg/trait/logging.go
+++ b/pkg/trait/logging.go
@@ -90,8 +90,12 @@ func (l loggingTrait) Apply(environment *Environment) error {
 		if util.IsTrue(l.JsonPrettyPrint) {
 			envvar.SetVal(&environment.EnvVars, envVarQuarkusLogConsoleJsonPrettyPrint, True)
 		}
-	} else if util.IsNilOrTrue(l.Color) {
-		envvar.SetVal(&environment.EnvVars, envVarQuarkusLogConsoleColor, True)
+	} else {
+		envvar.SetVal(&environment.EnvVars, envVarQuarkusLogConsoleJson, False)
+
+		if util.IsNilOrTrue(l.Color) {
+			envvar.SetVal(&environment.EnvVars, envVarQuarkusLogConsoleColor, True)
+		}
 	}
 
 	return nil
diff --git a/pkg/trait/owner.go b/pkg/trait/owner.go
index c821e0264..a95b67a99 100644
--- a/pkg/trait/owner.go
+++ b/pkg/trait/owner.go
@@ -78,17 +78,22 @@ func (t *ownerTrait) Apply(e *Environment) error {
 	}
 
 	e.Resources.VisitMetaObject(func(res metav1.Object) {
-		references := []metav1.OwnerReference{
-			{
-				APIVersion:         e.Integration.APIVersion,
-				Kind:               e.Integration.Kind,
-				Name:               e.Integration.Name,
-				UID:                e.Integration.UID,
-				Controller:         &controller,
-				BlockOwnerDeletion: &blockOwnerDeletion,
-			},
+		// Cross-namespace references are forbidden and also asynchronously refused
+		// by the api server (sometimes no error is thrown but the resource is not created).
+		// Ref: https://github.com/kubernetes/kubernetes/issues/65200
+		if res.GetNamespace() == "" || res.GetNamespace() == e.Integration.Namespace {
+			references := []metav1.OwnerReference{
+				{
+					APIVersion:         e.Integration.APIVersion,
+					Kind:               e.Integration.Kind,
+					Name:               e.Integration.Name,
+					UID:                e.Integration.UID,
+					Controller:         &controller,
+					BlockOwnerDeletion: &blockOwnerDeletion,
+				},
+			}
+			res.SetOwnerReferences(references)
 		}
-		res.SetOwnerReferences(references)
 
 		// Transfer annotations
 		t.propagateLabelAndAnnotations(res, targetLabels, targetAnnotations)
diff --git a/pkg/trait/pull_secret.go b/pkg/trait/pull_secret.go
index 115d60142..82b3c64f7 100644
--- a/pkg/trait/pull_secret.go
+++ b/pkg/trait/pull_secret.go
@@ -23,7 +23,9 @@ import (
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/platform"
 	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/apache/camel-k/pkg/util/openshift"
+	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
 	rbacv1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -109,22 +111,54 @@ func (t *pullSecretTrait) Apply(e *Environment) error {
 		})
 	}
 	if util.IsTrue(t.ImagePullerDelegation) {
-		rb := t.newImagePullerRoleBinding(e)
-		e.Resources.Add(rb)
+		if err := t.delegateImagePuller(e); err != nil {
+			return err
+		}
 	}
 
 	return nil
 }
 
+func (t *pullSecretTrait) delegateImagePuller(e *Environment) error {
+	// Applying the rolebinding directly because it's a resource in the operator namespace
+	// (different from the integration namespace when delegation is enabled).
+	rb := t.newImagePullerRoleBinding(e)
+	if err := kubernetes.ReplaceResource(e.C, e.Client, rb); err != nil {
+		return errors.Wrap(err, "error during the creation of the system:image-puller delegating role binding")
+	}
+	return nil
+}
+
 func (t *pullSecretTrait) newImagePullerRoleBinding(e *Environment) *rbacv1.RoleBinding {
+	targetNamespace := e.Integration.GetIntegrationKitNamespace(e.Platform)
+	var references []metav1.OwnerReference
+	if e.Platform != nil && e.Platform.Namespace == targetNamespace {
+		controller := true
+		blockOwnerDeletion := true
+		references = []metav1.OwnerReference{
+			{
+				APIVersion:         e.Platform.APIVersion,
+				Kind:               e.Platform.Kind,
+				Name:               e.Platform.Name,
+				UID:                e.Platform.UID,
+				Controller:         &controller,
+				BlockOwnerDeletion: &blockOwnerDeletion,
+			},
+		}
+	}
 	serviceAccount := e.Integration.Spec.ServiceAccountName
 	if serviceAccount == "" {
 		serviceAccount = "default"
 	}
 	return &rbacv1.RoleBinding{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "RoleBinding",
+			APIVersion: rbacv1.SchemeGroupVersion.String(),
+		},
 		ObjectMeta: metav1.ObjectMeta{
-			Namespace: e.Integration.GetIntegrationKitNamespace(e.Platform),
-			Name:      fmt.Sprintf("camel-k-puller-%s", e.Integration.Namespace),
+			Namespace:       targetNamespace,
+			Name:            fmt.Sprintf("camel-k-puller-%s-%s", e.Integration.Namespace, serviceAccount),
+			OwnerReferences: references,
 		},
 		RoleRef: rbacv1.RoleRef{
 			Kind: "ClusterRole",
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index f22e3d509..fc6d0f06e 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -46,6 +46,7 @@ import (
 
 // True --
 const True = "true"
+const False = "false"
 
 var (
 	basePath                  = "/etc/camel"
diff --git a/pkg/util/kubernetes/camel.go b/pkg/util/kubernetes/camel.go
index ca487b519..5cc7e1e2f 100644
--- a/pkg/util/kubernetes/camel.go
+++ b/pkg/util/kubernetes/camel.go
@@ -20,18 +20,20 @@ package kubernetes
 import (
 	"strings"
 
-	v1 "k8s.io/api/core/v1"
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 
-	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 )
 
 const (
 	CamelCreatorLabelPrefix = "camel.apache.org/created.by"
 
-	CamelCreatorLabelKind = CamelCreatorLabelPrefix + ".kind"
-	CamelCreatorLabelName = CamelCreatorLabelPrefix + ".name"
+	CamelCreatorLabelKind      = CamelCreatorLabelPrefix + ".kind"
+	CamelCreatorLabelName      = CamelCreatorLabelPrefix + ".name"
+	CamelCreatorLabelNamespace = CamelCreatorLabelPrefix + ".namespace"
+	CamelCreatorLabelVersion   = CamelCreatorLabelPrefix + ".version"
 )
 
 // FilterCamelCreatorLabels is used to inherit the creator information among resources
@@ -57,16 +59,20 @@ func MergeCamelCreatorLabels(source map[string]string, target map[string]string)
 }
 
 // GetCamelCreator returns the Camel creator object referenced by this runtime object, if present
-func GetCamelCreator(obj runtime.Object) *v1.ObjectReference {
+func GetCamelCreator(obj runtime.Object) *corev1.ObjectReference {
 	if m, ok := obj.(metav1.Object); ok {
 		kind := m.GetLabels()[CamelCreatorLabelKind]
 		name := m.GetLabels()[CamelCreatorLabelName]
+		namespace, ok := m.GetLabels()[CamelCreatorLabelNamespace]
+		if !ok {
+			namespace = m.GetNamespace()
+		}
 		if kind != "" && name != "" {
-			return &v1.ObjectReference{
+			return &corev1.ObjectReference{
 				Kind:       kind,
-				Namespace:  m.GetNamespace(),
+				Namespace:  namespace,
 				Name:       name,
-				APIVersion: camelv1.SchemeGroupVersion.String(),
+				APIVersion: v1.SchemeGroupVersion.String(),
 			}
 		}
 	}


Test patch:
diff --git a/pkg/apis/camel/v1/integration_types_support_test.go b/pkg/apis/camel/v1/integration_types_support_test.go
index 0f028172a..6f4d4827c 100644
--- a/pkg/apis/camel/v1/integration_types_support_test.go
+++ b/pkg/apis/camel/v1/integration_types_support_test.go
@@ -73,3 +73,28 @@ func TestAddDependency(t *testing.T) {
 	integration.AddDependency("file:dep")
 	assert.Equal(t, integration.Dependencies, []string{"file:dep"})
 }
+
+func TestGetConfigurationProperty(t *testing.T) {
+	integration := IntegrationSpec{}
+	integration.AddConfiguration("property", "key1=value1")
+	integration.AddConfiguration("property", "key2 = value2")
+	integration.AddConfiguration("property", "key3 = value with trailing space ")
+	integration.AddConfiguration("property", "key4 =  value with leading space")
+	integration.AddConfiguration("property", "key5 = ")
+	integration.AddConfiguration("property", "key6=")
+
+	missing := integration.GetConfigurationProperty("missing")
+	assert.Equal(t, "", missing)
+	v1 := integration.GetConfigurationProperty("key")
+	assert.Equal(t, "value1", v1)
+	v2 := integration.GetConfigurationProperty("key2")
+	assert.Equal(t, "value2", v2)
+	v3 := integration.GetConfigurationProperty("key3")
+	assert.Equal(t, "value with trailing space ", v3)
+	v4 := integration.GetConfigurationProperty("key4")
+	assert.Equal(t, " value with leading space", v4)
+	v5 := integration.GetConfigurationProperty("key5")
+	assert.Equal(t, "", v5)
+	v6 := integration.GetConfigurationProperty("key6")
+	assert.Equal(t, "", v6)
+}
diff --git a/pkg/cmd/util_test.go b/pkg/cmd/util_test.go
index b47c07192..5938b86bf 100644
--- a/pkg/cmd/util_test.go
+++ b/pkg/cmd/util_test.go
@@ -1,3 +1,5 @@
+// +build !darwin
+
 /*
 Licensed to the Apache Software Foundation (ASF) under one or more
 contributor license agreements.  See the NOTICE file distributed with
diff --git a/pkg/trait/pull_secret_test.go b/pkg/trait/pull_secret_test.go
index a1499e014..44ee3efa4 100644
--- a/pkg/trait/pull_secret_test.go
+++ b/pkg/trait/pull_secret_test.go
@@ -18,32 +18,23 @@ limitations under the License.
 package trait
 
 import (
+	"context"
 	"testing"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/test"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestPullSecret(t *testing.T) {
-	e := &Environment{}
-	e.Integration = &v1.Integration{
-		Status: v1.IntegrationStatus{
-			Phase: v1.IntegrationPhaseDeploying,
-		},
-	}
-
-	deployment := appsv1.Deployment{
-		Spec: appsv1.DeploymentSpec{
-			Template: corev1.PodTemplateSpec{
-				Spec: corev1.PodSpec{},
-			},
-		},
-	}
-	e.Resources = kubernetes.NewCollection(&deployment)
+	e, deployment := getEnvironmentAndDeployment(t)
 
 	trait := newPullSecretTrait().(*pullSecretTrait)
 	trait.SecretName = "xxxy"
@@ -57,15 +48,66 @@ func TestPullSecret(t *testing.T) {
 }
 
 func TestPullSecretDoesNothingWhenNotSetOnPlatform(t *testing.T) {
+	e, _ := getEnvironmentAndDeployment(t)
+	e.Platform = &v1.IntegrationPlatform{}
+
+	trait := newPullSecretTrait()
+	enabled, err := trait.Configure(e)
+	assert.Nil(t, err)
+	assert.False(t, enabled)
+}
+
+func TestPullSecretAuto(t *testing.T) {
+	e, _ := getEnvironmentAndDeployment(t)
+
+	trait := newPullSecretTrait().(*pullSecretTrait)
+	trait.Auto = newFalse()
+	enabled, err := trait.Configure(e)
+	assert.Nil(t, err)
+	assert.False(t, enabled)
+}
+
+func TestPullSecretImagePullerDelegation(t *testing.T) {
+	e, _ := getEnvironmentAndDeployment(t)
+
+	trait := newPullSecretTrait().(*pullSecretTrait)
+	trait.Auto = newFalse()
+	trait.ImagePullerDelegation = newTrue()
+	enabled, err := trait.Configure(e)
+	assert.Nil(t, err)
+	assert.True(t, enabled)
+	assert.True(t, *trait.ImagePullerDelegation)
+
+	err = trait.Apply(e)
+	assert.NoError(t, err)
+
+	var roleBinding rbacv1.RoleBinding
+	roleBindingKey := client.ObjectKey{
+		Namespace: "test",
+		Name:      "camel-k-puller-test-default",
+	}
+	err = e.Client.Get(e.C, roleBindingKey, &roleBinding)
+	assert.NoError(t, err)
+	assert.Len(t, roleBinding.Subjects, 1)
+}
+
+func getEnvironmentAndDeployment(t *testing.T) (*Environment, *appsv1.Deployment) {
 	e := &Environment{}
 	e.Integration = &v1.Integration{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "myit",
+		},
 		Status: v1.IntegrationStatus{
 			Phase: v1.IntegrationPhaseDeploying,
 		},
 	}
-	e.Platform = &v1.IntegrationPlatform{}
 
 	deployment := appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "test",
+			Name:      "myit",
+		},
 		Spec: appsv1.DeploymentSpec{
 			Template: corev1.PodTemplateSpec{
 				Spec: corev1.PodSpec{},
@@ -74,8 +116,20 @@ func TestPullSecretDoesNothingWhenNotSetOnPlatform(t *testing.T) {
 	}
 	e.Resources = kubernetes.NewCollection(&deployment)
 
-	trait := newPullSecretTrait()
-	enabled, err := trait.Configure(e)
-	assert.Nil(t, err)
-	assert.False(t, enabled)
+	var err error
+	e.C = context.TODO()
+	e.Client, err = test.NewFakeClient(e.Integration, &deployment)
+	assert.NoError(t, err)
+
+	return e, &deployment
+}
+
+func newFalse() *bool {
+	b := false
+	return &b
+}
+
+func newTrue() *bool {
+	b := true
+	return &b
 }