Fix #1234: add a master trait (as addon)
diff --git a/addons/master/master.go b/addons/master/master.go
index ed40e1a..33b4ff8 100644
--- a/addons/master/master.go
+++ b/addons/master/master.go
@@ -18,30 +18,180 @@
 package master
 
 import (
+	"fmt"
+	"strings"
+
+	"github.com/apache/camel-k/deploy"
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/metadata"
 	"github.com/apache/camel-k/pkg/trait"
+	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/uri"
+	"k8s.io/apimachinery/pkg/runtime"
 )
 
 // The Master trait allows to configure the integration to automatically leverage Kubernetes resources for doing
 // leader election and starting *master* routes only on certain instances.
 //
-// It's activated automatically when using the master endpoint in a route, e.g. `from("master:telegram:bots")...`.
+// It's activated automatically when using the master endpoint in a route, e.g. `from("master:lockname:telegram:bots")...`.
+//
+// NOTE: this trait adds special permissions to the integration service account in order to read/write configmaps and read pods.
+// It's recommended to use a different service account than "default" when running the integration.
 //
 // +camel-k:trait=master
 type masterTrait struct {
 	trait.BaseTrait `property:",squash"`
+	// Enables automatic configuration of the trait.
+	Auto *bool `property:"auto"`
+	// When this flag is active, the operator analyzes the source code to add dependencies required by delegate endpoints.
+	// E.g. when using `master:lockname:timer`, then `camel:timer` is automatically added to the set of dependencies.
+	// It's enabled by default.
+	IncludeDelegateDependencies *bool `property:"include-delegate-dependencies"`
+	// Name of the configmap that will be used to store the lock. Defaults to "<integration-name>-lock".
+	Configmap string `property:"configmap"`
+	// Label that will be used to identify all pods contending the lock. Defaults to "camel.apache.org/integration".
+	LabelKey string `property:"label-key"`
+	// Label value that will be used to identify all pods contending the lock. Defaults to the integration name.
+	LabelValue           string `property:"label-value"`
+	delegateDependencies []string
 }
 
+// NewMasterTrait --
 func NewMasterTrait() trait.Trait {
 	return &masterTrait{
-		BaseTrait: trait.NewBaseTrait("master", 2500),
+		BaseTrait: trait.NewBaseTrait("master", 850),
 	}
 }
 
+const (
+	masterComponent = "master"
+)
+
 func (t *masterTrait) Configure(e *trait.Environment) (bool, error) {
-	return false, nil
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
+	}
+
+	if !e.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+		return false, nil
+	}
+
+	if t.Auto == nil || *t.Auto {
+		// Check if the master component has been used
+		sources, err := kubernetes.ResolveIntegrationSources(t.Ctx, t.Client, e.Integration, e.Resources)
+		if err != nil {
+			return false, err
+		}
+
+		meta := metadata.ExtractAll(e.CamelCatalog, sources)
+
+		if t.Enabled == nil {
+			for _, endpoint := range meta.FromURIs {
+				if uri.GetComponent(endpoint) == masterComponent {
+					enabled := true
+					t.Enabled = &enabled
+				}
+			}
+		}
+
+		if t.Enabled == nil || !*t.Enabled {
+			return false, nil
+		}
+
+		if t.IncludeDelegateDependencies == nil || *t.IncludeDelegateDependencies {
+			t.delegateDependencies = findAdditionalDependencies(e, meta)
+		}
+
+		if t.Configmap == "" {
+			t.Configmap = fmt.Sprintf("%s-lock", e.Integration.Name)
+		}
+
+		if t.LabelKey == "" {
+			t.LabelKey = "camel.apache.org/integration"
+		}
+
+		if t.LabelValue == "" {
+			t.LabelValue = e.Integration.Name
+		}
+	}
+
+	return t.Enabled != nil && *t.Enabled, nil
 }
 
 func (t *masterTrait) Apply(e *trait.Environment) error {
 
+	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
+		util.StringSliceUniqueAdd(&e.Integration.Status.Dependencies, "mvn:org.apache.camel.k/camel-k-runtime-master")
+
+		// Master sub endpoints need to be added to the list of dependencies
+		for _, dep := range t.delegateDependencies {
+			util.StringSliceUniqueAdd(&e.Integration.Status.Dependencies, dep)
+		}
+
+	} else if e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+		serviceAccount := e.Integration.Spec.ServiceAccountName
+		if serviceAccount == "" {
+			serviceAccount = "default"
+		}
+
+		var templateData = struct {
+			Namespace      string
+			Name           string
+			ServiceAccount string
+		}{
+			Namespace:      e.Integration.Namespace,
+			Name:           fmt.Sprintf("%s-master", e.Integration.Name),
+			ServiceAccount: serviceAccount,
+		}
+
+		role, err := loadResource(e, "master-role.tmpl", templateData)
+		if err != nil {
+			return err
+		}
+		roleBinding, err := loadResource(e, "master-role-binding.tmpl", templateData)
+		if err != nil {
+			return err
+		}
+
+		e.Resources.Add(role)
+		e.Resources.Add(roleBinding)
+
+		e.Integration.Status.Configuration = append(e.Integration.Status.Configuration,
+			v1.ConfigurationSpec{Type: "property", Value: "customizer.master.enabled=true"},
+			v1.ConfigurationSpec{Type: "property", Value: fmt.Sprintf("customizer.master.configMapName=%s", t.Configmap)},
+			v1.ConfigurationSpec{Type: "property", Value: fmt.Sprintf("customizer.master.labelKey=%s", t.LabelKey)},
+			v1.ConfigurationSpec{Type: "property", Value: fmt.Sprintf("customizer.master.labelValue=%s", t.LabelValue)},
+		)
+	}
+
 	return nil
 }
+
+func findAdditionalDependencies(e *trait.Environment, meta metadata.IntegrationMetadata) (dependencies []string) {
+	for _, endpoint := range meta.FromURIs {
+		if uri.GetComponent(endpoint) == masterComponent {
+			parts := strings.Split(endpoint, ":")
+			if len(parts) > 2 {
+				// syntax "master:lockname:endpoint:..."
+				childComponent := parts[2]
+				if artifact := e.CamelCatalog.GetArtifactByScheme(childComponent); artifact != nil {
+					dependencies = append(dependencies, artifact.GetDependencyID())
+				}
+			}
+		}
+	}
+	return dependencies
+}
+
+func loadResource(e *trait.Environment, name string, params interface{}) (runtime.Object, error) {
+	data, err := deploy.TemplateResource(fmt.Sprintf("/addons/master/%s", name), params)
+	if err != nil {
+		return nil, err
+	}
+	obj, err := kubernetes.LoadResourceFromYaml(e.Client.GetScheme(), data)
+	if err != nil {
+		return nil, err
+	}
+	return obj, nil
+}
diff --git a/build/maven/pom-runtime.xml b/build/maven/pom-runtime.xml
index e93d27b..6f9ef89 100644
--- a/build/maven/pom-runtime.xml
+++ b/build/maven/pom-runtime.xml
@@ -89,6 +89,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.camel.k</groupId>
+            <artifactId>camel-k-runtime-master</artifactId>
+            <version>${runtime.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.k</groupId>
             <artifactId>camel-k-runtime-cron</artifactId>
             <version>${runtime.version}</version>
         </dependency>
diff --git a/deploy/addons/master/master-role-binding.tmpl b/deploy/addons/master/master-role-binding.tmpl
new file mode 100644
index 0000000..b6d0bea
--- /dev/null
+++ b/deploy/addons/master/master-role-binding.tmpl
@@ -0,0 +1,16 @@
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: {{ .Name }}
+  namespace: {{ .Namespace }}
+  labels:
+    app: "camel-k"
+subjects:
+  - kind: ServiceAccount
+    namespace: {{ .Namespace }}
+    name: {{ .ServiceAccount }}
+roleRef:
+  kind: Role
+  namespace: {{ .Namespace }}
+  name: {{ .Name }}
+  apiGroup: rbac.authorization.k8s.io
diff --git a/deploy/addons/master/master-role.tmpl b/deploy/addons/master/master-role.tmpl
new file mode 100644
index 0000000..efe939e
--- /dev/null
+++ b/deploy/addons/master/master-role.tmpl
@@ -0,0 +1,27 @@
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: {{ .Name }}
+  namespace: {{ .Namespace }}
+  labels:
+    app: "camel-k"
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - configmaps
+  verbs:
+  - create
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  verbs:
+  - get
+  - list
+  - watch
diff --git a/deploy/resources.go b/deploy/resources.go
index 5e10a0c..8719462 100644
--- a/deploy/resources.go
+++ b/deploy/resources.go
@@ -38,6 +38,28 @@
 			name:    "/",
 			modTime: time.Time{},
 		},
+		"/addons": &vfsgen۰DirInfo{
+			name:    "addons",
+			modTime: time.Time{},
+		},
+		"/addons/master": &vfsgen۰DirInfo{
+			name:    "master",
+			modTime: time.Time{},
+		},
+		"/addons/master/master-role-binding.tmpl": &vfsgen۰CompressedFileInfo{
+			name:             "master-role-binding.tmpl",
+			modTime:          time.Time{},
+			uncompressedSize: 362,
+
+			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x90\xbd\x4e\xc5\x30\x0c\x85\xf7\x3c\x85\x75\xf7\x1b\xd4\x0d\x65\x83\x85\x8d\xa1\x48\xec\x4e\x6a\xc0\x34\x89\xa3\xfc\x74\xa0\xea\xbb\xa3\xb6\x48\x14\x09\xf5\x8e\xc9\xa7\x63\x7f\x3e\x23\xc7\xc1\x40\x2f\x9e\x1e\x39\x0e\x1c\xdf\x15\x26\x7e\xa5\x5c\x58\xa2\x81\x6c\xd1\x69\x6c\xf5\x43\x32\x7f\x61\x65\x89\x7a\xbc\x2f\x9a\xe5\x6e\xea\x2c\x55\xec\x54\xa0\x8a\x03\x56\x34\x0a\x20\x62\x20\x03\xf3\x0c\xfa\x19\x03\xc1\xb2\xfc\xfc\x95\x84\xee\x00\xb6\xe7\x4e\x3d\x5a\xf2\x65\xcd\x02\x60\x4a\x06\x2e\x0e\x03\xf9\xeb\x78\x51\xa5\xd9\x4f\x72\x75\x83\x57\xd8\x35\x5f\x28\x4f\xec\xe8\xc1\x39\x69\xb1\x6e\xa9\xf3\xf9\x47\xa7\xbf\xe1\x95\x67\xf1\xd4\xd3\xdb\xba\xe1\xb7\x86\x9b\xce\xff\x5d\x89\x89\x9f\xb2\xb4\x74\xd2\x98\xfa\x0e\x00\x00\xff\xff\xa6\x3e\x0f\x20\x6a\x01\x00\x00"),
+		},
+		"/addons/master/master-role.tmpl": &vfsgen۰CompressedFileInfo{
+			name:             "master-role.tmpl",
+			modTime:          time.Time{},
+			uncompressedSize: 347,
+
+			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x8d\xb1\x6e\xc3\x30\x0c\x44\x77\x7d\x05\xe1\xdd\x2e\xb2\x15\xfa\x81\x6e\x1d\x3a\x74\x3f\xcb\x6c\x22\x58\x16\x09\x4a\x4a\x81\x06\xf9\xf7\xa2\x72\x02\x14\xed\x92\x89\xc7\x77\x47\xde\x1a\xf3\xe2\xe9\x4d\x12\x3b\x68\x7c\x67\x2b\x51\xb2\x27\x9b\x11\x26\xb4\x7a\x12\x8b\x5f\xa8\x51\xf2\xb4\x3e\x97\x29\xca\xd3\xf9\x30\x73\xc5\xc1\x6d\x5c\xb1\xa0\xc2\x3b\xa2\x8c\x8d\x3d\x5d\x2e\x34\xbd\x62\x63\xba\x5e\x6f\xac\x28\xc2\x2f\xa3\xaf\xbb\x9b\x30\x73\x2a\x3f\xb7\x44\x50\xf5\x34\x04\x6c\x9c\xc6\x75\x70\xd6\x12\x17\xef\x46\x82\xc6\x17\x93\xa6\x3d\x36\xd2\x30\x38\x22\xe3\x22\xcd\x02\xdf\x58\x90\xfc\x11\x8f\x1b\xb4\x38\xa2\x33\xdb\x7c\xe7\xc6\xa8\xdc\xe5\x91\x6b\x9f\x29\x96\x5d\x28\x6a\x38\x75\xd5\x74\xb9\xa7\x3e\x3b\x7c\xa8\x53\x65\xf9\xd3\xf6\xaf\x62\xff\xf6\x1d\x00\x00\xff\xff\x40\x05\x71\xea\x5b\x01\x00\x00"),
+		},
 		"/builder-role-binding.yaml": &vfsgen۰CompressedFileInfo{
 			name:             "builder-role-binding.yaml",
 			modTime:          time.Time{},
@@ -310,6 +332,7 @@
 		},
 	}
 	fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
+		fs["/addons"].(os.FileInfo),
 		fs["/builder-role-binding.yaml"].(os.FileInfo),
 		fs["/builder-role-kubernetes.yaml"].(os.FileInfo),
 		fs["/builder-role-openshift.yaml"].(os.FileInfo),
@@ -344,6 +367,13 @@
 		fs["/templates"].(os.FileInfo),
 		fs["/user-cluster-role.yaml"].(os.FileInfo),
 	}
+	fs["/addons"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
+		fs["/addons/master"].(os.FileInfo),
+	}
+	fs["/addons/master"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
+		fs["/addons/master/master-role-binding.tmpl"].(os.FileInfo),
+		fs["/addons/master/master-role.tmpl"].(os.FileInfo),
+	}
 	fs["/templates"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
 		fs["/templates/groovy.tmpl"].(os.FileInfo),
 		fs["/templates/java.tmpl"].(os.FileInfo),
diff --git a/deploy/resources_support.go b/deploy/resources_support.go
index 23a514b..9164f88 100644
--- a/deploy/resources_support.go
+++ b/deploy/resources_support.go
@@ -18,8 +18,10 @@
 package deploy
 
 import (
+	"bytes"
 	"io/ioutil"
 	"strings"
+	"text/template"
 
 	"github.com/apache/camel-k/pkg/util/log"
 )
@@ -51,6 +53,24 @@
 	return data
 }
 
+// TemplateResource loads a file resource as go template and processes it using the given parameters
+func TemplateResource(name string, params interface{}) (string, error) {
+	rawData := ResourceAsString(name)
+	if rawData == "" {
+		return "", nil
+	}
+	tmpl, err := template.New(name).Parse(rawData)
+	if err != nil {
+		return "", err
+	}
+
+	var buf bytes.Buffer
+	if err := tmpl.Execute(&buf, params); err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
 // Resources lists all file names in the given path (starts with '/')
 func Resources(dirName string) []string {
 	dir, err := assets.Open(dirName)
diff --git a/examples/Master.java b/examples/Master.java
new file mode 100755
index 0000000..c9bccb8
--- /dev/null
+++ b/examples/Master.java
@@ -0,0 +1,20 @@
+// camel-k: language=java
+
+import org.apache.camel.builder.RouteBuilder;
+
+/**
+ * This example shows how to start a route on a single instance of the integration.
+ * Increase the number of replicas to see it in action (the route will be started on a single pod only).
+ */
+public class Master extends RouteBuilder {
+  @Override
+  public void configure() throws Exception {
+
+      // Write your routes here, for example:
+      from("master:lock:timer:master?period=1s")
+        .setBody()
+          .simple("This message is printed by a single pod, even if you increase the number of replicas!")
+        .to("log:info");
+
+  }
+}
diff --git a/pkg/apis/camel/v1/camelcatalog_types_support.go b/pkg/apis/camel/v1/camelcatalog_types_support.go
index 7ee6ee8..edda60f 100644
--- a/pkg/apis/camel/v1/camelcatalog_types_support.go
+++ b/pkg/apis/camel/v1/camelcatalog_types_support.go
@@ -17,7 +17,11 @@
 
 package v1
 
-import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+import (
+	"strings"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
 
 // NewCamelCatalog --
 func NewCamelCatalog(namespace string, name string) CamelCatalog {
@@ -57,3 +61,15 @@
 		},
 	}
 }
+
+// GetDependencyID returns a Camel K recognizable maven dependency for the artifact
+func (a CamelArtifact) GetDependencyID() string {
+	artifactID := a.ArtifactID
+	if a.GroupID == "org.apache.camel" && strings.HasPrefix(artifactID, "camel-") {
+		return "camel:" + artifactID[6:]
+	}
+	if a.GroupID == "org.apache.camel.quarkus" && strings.HasPrefix(artifactID, "camel-quarkus-") {
+		return "camel-quarkus:" + artifactID[14:]
+	}
+	return "mvn:" + a.GroupID + ":" + artifactID + ":" + a.Version
+}
diff --git a/pkg/trait/cron.go b/pkg/trait/cron.go
index 1024e88..b2b44d6 100644
--- a/pkg/trait/cron.go
+++ b/pkg/trait/cron.go
@@ -172,7 +172,7 @@
 	}
 
 	// CronJob strategy requires common schedule
-	strategy, err := e.DetermineControllerStrategy(t.ctx, t.client)
+	strategy, err := e.DetermineControllerStrategy(t.Ctx, t.Client)
 	if err != nil {
 		return false, err
 	}
@@ -326,7 +326,7 @@
 func (t *cronTrait) getSourcesFromURIs(e *Environment) ([]string, error) {
 	var sources []v1.SourceSpec
 	var err error
-	if sources, err = kubernetes.ResolveIntegrationSources(t.ctx, t.client, e.Integration, e.Resources); err != nil {
+	if sources, err = kubernetes.ResolveIntegrationSources(t.Ctx, t.Client, e.Integration, e.Resources); err != nil {
 		return nil, err
 	}
 	meta := metadata.ExtractAll(e.CamelCatalog, sources)
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index c8e04a2..4abf44c 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -62,7 +62,7 @@
 	//
 	// Don't deploy when a different strategy is needed (e.g. Knative, Cron)
 	//
-	strategy, err := e.DetermineControllerStrategy(t.ctx, t.client)
+	strategy, err := e.DetermineControllerStrategy(t.Ctx, t.Client)
 	if err != nil {
 		e.Integration.Status.SetErrorCondition(
 			v1.IntegrationConditionDeploymentAvailable,
diff --git a/pkg/trait/deployment_test.go b/pkg/trait/deployment_test.go
index 6205987..7d9c666 100644
--- a/pkg/trait/deployment_test.go
+++ b/pkg/trait/deployment_test.go
@@ -140,7 +140,7 @@
 	trait := newDeploymentTrait().(*deploymentTrait)
 	enabled := true
 	trait.Enabled = &enabled
-	trait.client, _ = test.NewFakeClient(&appsv1.Deployment{
+	trait.Client, _ = test.NewFakeClient(&appsv1.Deployment{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "integration-name",
 			Namespace: "namespace",
diff --git a/pkg/trait/gc.go b/pkg/trait/gc.go
index c9013c1..2c42cc9 100644
--- a/pkg/trait/gc.go
+++ b/pkg/trait/gc.go
@@ -186,7 +186,7 @@
 			client.InNamespace(e.Integration.Namespace),
 			util.MatchingSelector{Selector: selector},
 		}
-		if err := t.client.List(context.TODO(), &resources, options...); err != nil {
+		if err := t.Client.List(context.TODO(), &resources, options...); err != nil {
 			if !k8serrors.IsNotFound(err) && !k8serrors.IsForbidden(err) {
 				t.L.ForIntegration(e.Integration).Errorf(err, "cannot list child resources: %v", gvk)
 			}
@@ -195,7 +195,7 @@
 
 		for _, resource := range resources.Items {
 			r := resource
-			err := t.client.Delete(context.TODO(), &r, client.PropagationPolicy(metav1.DeletePropagationBackground))
+			err := t.Client.Delete(context.TODO(), &r, client.PropagationPolicy(metav1.DeletePropagationBackground))
 			if err != nil {
 				// The resource may have already been deleted
 				if !k8serrors.IsNotFound(err) {
@@ -257,7 +257,7 @@
 		if diskCachedDiscoveryClient != nil {
 			return diskCachedDiscoveryClient, nil
 		}
-		config := t.client.GetConfig()
+		config := t.Client.GetConfig()
 		httpCacheDir := filepath.Join(mustHomeDir(), ".kube", "http-cache")
 		diskCacheDir := filepath.Join(mustHomeDir(), ".kube", "cache", "discovery", toHostDir(config.Host))
 		var err error
@@ -268,14 +268,14 @@
 		if memoryCachedDiscoveryClient != nil {
 			return memoryCachedDiscoveryClient, nil
 		}
-		memoryCachedDiscoveryClient = memory.NewMemCacheClient(t.client.Discovery())
+		memoryCachedDiscoveryClient = memory.NewMemCacheClient(t.Client.Discovery())
 		return memoryCachedDiscoveryClient, nil
 
 	case disabledDiscoveryCache, "":
-		return t.client.Discovery(), nil
+		return t.Client.Discovery(), nil
 
 	default:
 		t.L.ForIntegration(e.Integration).Infof("unsupported discovery cache type: %s", *t.DiscoveryCache)
-		return t.client.Discovery(), nil
+		return t.Client.Discovery(), nil
 	}
 }
diff --git a/pkg/trait/jvm.go b/pkg/trait/jvm.go
index c75f854..4596623 100644
--- a/pkg/trait/jvm.go
+++ b/pkg/trait/jvm.go
@@ -78,7 +78,7 @@
 			Name:      name,
 		}
 
-		if err := t.client.Get(t.ctx, key, &k); err != nil {
+		if err := t.Client.Get(t.Ctx, key, &k); err != nil {
 			return errors.Wrapf(err, "unable to find integration kit %s, %s", name, err)
 		}
 
diff --git a/pkg/trait/jvm_test.go b/pkg/trait/jvm_test.go
index 1db7f39..15f012b 100644
--- a/pkg/trait/jvm_test.go
+++ b/pkg/trait/jvm_test.go
@@ -204,8 +204,8 @@
 	trait := newJvmTrait().(*jvmTrait)
 	enabled := true
 	trait.Enabled = &enabled
-	trait.ctx = context.TODO()
-	trait.client = client
+	trait.Ctx = context.TODO()
+	trait.Client = client
 
 	environment := &Environment{
 		Catalog:      NewCatalog(context.TODO(), nil),
diff --git a/pkg/trait/knative.go b/pkg/trait/knative.go
index 7032c49..cd55fac 100644
--- a/pkg/trait/knative.go
+++ b/pkg/trait/knative.go
@@ -388,13 +388,13 @@
 			continue
 		}
 		possibleRefs := knativeutil.FillMissingReferenceData(serviceType, ref)
-		actualRef, err := knativeutil.GetAddressableReference(t.ctx, t.client, possibleRefs, e.Integration.Namespace, ref.Name)
+		actualRef, err := knativeutil.GetAddressableReference(t.Ctx, t.Client, possibleRefs, e.Integration.Namespace, ref.Name)
 		if err != nil && k8serrors.IsNotFound(err) {
 			return errors.Errorf("cannot find %s %s", serviceType, ref.Name)
 		} else if err != nil {
 			return errors.Wrapf(err, "error looking up %s %s", serviceType, ref.Name)
 		}
-		targetURL, err := knativeutil.GetSinkURL(t.ctx, t.client, actualRef, e.Integration.Namespace)
+		targetURL, err := knativeutil.GetSinkURL(t.Ctx, t.Client, actualRef, e.Integration.Namespace)
 		if err != nil {
 			return errors.Wrapf(err, "cannot determine address of %s %s", string(serviceType), ref.Name)
 		}
diff --git a/pkg/trait/knative_service.go b/pkg/trait/knative_service.go
index 63dc548..5f10270 100644
--- a/pkg/trait/knative_service.go
+++ b/pkg/trait/knative_service.go
@@ -121,7 +121,7 @@
 		return false, nil
 	}
 
-	strategy, err := e.DetermineControllerStrategy(t.ctx, t.client)
+	strategy, err := e.DetermineControllerStrategy(t.Ctx, t.Client)
 	if err != nil {
 		e.Integration.Status.SetErrorCondition(
 			v1.IntegrationConditionKnativeServiceAvailable,
@@ -145,7 +145,7 @@
 	if t.Auto == nil || *t.Auto {
 		// Check the right value for minScale, as not all services are allowed to scale down to 0
 		if t.MinScale == nil {
-			sources, err := kubernetes.ResolveIntegrationSources(t.ctx, t.client, e.Integration, e.Resources)
+			sources, err := kubernetes.ResolveIntegrationSources(t.Ctx, t.Client, e.Integration, e.Resources)
 			if err != nil {
 				e.Integration.Status.SetErrorCondition(
 					v1.IntegrationConditionKnativeServiceAvailable,
@@ -250,7 +250,7 @@
 
 	var sources []v1.SourceSpec
 	var err error
-	if sources, err = kubernetes.ResolveIntegrationSources(t.ctx, t.client, e.Integration, e.Resources); err != nil {
+	if sources, err = kubernetes.ResolveIntegrationSources(t.Ctx, t.Client, e.Integration, e.Resources); err != nil {
 		return nil, err
 	}
 
diff --git a/pkg/trait/platform.go b/pkg/trait/platform.go
index fe11fdc..9db375f 100644
--- a/pkg/trait/platform.go
+++ b/pkg/trait/platform.go
@@ -57,7 +57,7 @@
 	if t.Auto == nil || !*t.Auto {
 		if e.Platform == nil && t.CreateDefault == nil {
 			// Calculate if the platform should be automatically created when missing.
-			if ocp, err := openshift.IsOpenShift(t.client); err != nil {
+			if ocp, err := openshift.IsOpenShift(t.Client); err != nil {
 				return false, err
 			} else if ocp {
 				t.CreateDefault = &ocp
@@ -92,7 +92,7 @@
 }
 
 func (t *platformTrait) getOrCreatePlatform(e *Environment) (*v1.IntegrationPlatform, error) {
-	pl, err := platform.GetOrLookupAny(t.ctx, t.client, e.Integration.Namespace, e.Integration.Status.Platform)
+	pl, err := platform.GetOrLookupAny(t.Ctx, t.Client, e.Integration.Namespace, e.Integration.Status.Platform)
 	if err != nil && k8serrors.IsNotFound(err) {
 		if t.CreateDefault != nil && *t.CreateDefault {
 			platformName := e.Integration.Status.Platform
diff --git a/pkg/trait/platform_test.go b/pkg/trait/platform_test.go
index 43b306d..8932ad2 100644
--- a/pkg/trait/platform_test.go
+++ b/pkg/trait/platform_test.go
@@ -61,7 +61,7 @@
 			trait.CreateDefault = &createPlatform
 
 			var err error
-			trait.client, err = test.NewFakeClient()
+			trait.Client, err = test.NewFakeClient()
 			assert.Nil(t, err)
 
 			enabled, err := trait.Configure(&e)
@@ -96,7 +96,7 @@
 	trait.CreateDefault = &createPlatform
 
 	var err error
-	trait.client, err = test.NewFakeClient()
+	trait.Client, err = test.NewFakeClient()
 	assert.Nil(t, err)
 
 	enabled, err := trait.Configure(&e)
@@ -155,7 +155,7 @@
 			var err error
 			existingPlatform := v1.NewIntegrationPlatform("ns1", "existing")
 			existingPlatform.Status.Phase = input.platformPhase
-			trait.client, err = test.NewFakeClient(&existingPlatform)
+			trait.Client, err = test.NewFakeClient(&existingPlatform)
 			assert.Nil(t, err)
 
 			enabled, err := trait.Configure(&e)
diff --git a/pkg/trait/pull_secret.go b/pkg/trait/pull_secret.go
index c7e0326..57fe0da 100644
--- a/pkg/trait/pull_secret.go
+++ b/pkg/trait/pull_secret.go
@@ -64,7 +64,7 @@
 			if secret != "" {
 				key := client.ObjectKey{Namespace: e.Platform.Namespace, Name: secret}
 				obj := corev1.Secret{}
-				if err := t.client.Get(t.ctx, key, &obj); err != nil {
+				if err := t.Client.Get(t.Ctx, key, &obj); err != nil {
 					return false, err
 				}
 				if obj.Type == corev1.SecretTypeDockerConfigJson {
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index 73daac2..6257e9e 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -75,7 +75,7 @@
 	}
 
 	if t.Auto == nil || *t.Auto {
-		sources, err := kubernetes.ResolveIntegrationSources(t.ctx, t.client, e.Integration, e.Resources)
+		sources, err := kubernetes.ResolveIntegrationSources(t.Ctx, t.Client, e.Integration, e.Resources)
 		if err != nil {
 			e.Integration.Status.SetCondition(
 				v1.IntegrationConditionServiceAvailable,
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index 9d863f2..3d87d1b 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -108,36 +108,36 @@
 // NewBaseTrait --
 func NewBaseTrait(id string, order int) BaseTrait {
 	return BaseTrait{
-		id:    ID(id),
-		order: order,
-		L:     log.Log.WithName("traits").WithValues("trait", id),
+		TraitID:        ID(id),
+		ExecutionOrder: order,
+		L:              log.Log.WithName("traits").WithValues("trait", id),
 	}
 }
 
 // BaseTrait is the root trait with noop implementations for hooks
 type BaseTrait struct {
-	id ID
+	TraitID ID
 	// Can be used to enable or disable a trait. All traits share this common property.
-	Enabled *bool `property:"enabled"`
-	client  client.Client
-	ctx     context.Context
-	order   int
-	L       log.Logger
+	Enabled        *bool `property:"enabled"`
+	Client         client.Client
+	Ctx            context.Context
+	ExecutionOrder int
+	L              log.Logger
 }
 
 // ID returns the identifier of the trait
 func (trait *BaseTrait) ID() ID {
-	return trait.id
+	return trait.TraitID
 }
 
 // InjectClient implements client.ClientInject and allows to inject a client into the trait
 func (trait *BaseTrait) InjectClient(c client.Client) {
-	trait.client = c
+	trait.Client = c
 }
 
 // InjectContext allows to inject a context into the trait
 func (trait *BaseTrait) InjectContext(ctx context.Context) {
-	trait.ctx = ctx
+	trait.Ctx = ctx
 }
 
 // InfluencesKit determines if the trait has any influence on Integration Kits
@@ -163,7 +163,7 @@
 
 // Order contains the order value provided during initialization
 func (trait *BaseTrait) Order() int {
-	return trait.order
+	return trait.ExecutionOrder
 }
 
 /* ControllerStrategySelector */
diff --git a/pkg/util/source/inspector.go b/pkg/util/source/inspector.go
index 0f1a219..788e512 100644
--- a/pkg/util/source/inspector.go
+++ b/pkg/util/source/inspector.go
@@ -190,14 +190,7 @@
 	}
 	uriStart := uriSplit[0]
 	if component := i.catalog.GetArtifactByScheme(uriStart); component != nil {
-		artifactID := component.ArtifactID
-		if component.GroupID == "org.apache.camel" && strings.HasPrefix(artifactID, "camel-") {
-			return "camel:" + artifactID[6:]
-		}
-		if component.GroupID == "org.apache.camel.quarkus" && strings.HasPrefix(artifactID, "camel-quarkus-") {
-			return "camel-quarkus:" + artifactID[14:]
-		}
-		return "mvn:" + component.GroupID + ":" + artifactID + ":" + component.Version
+		return component.GetDependencyID()
 	}
 	return ""
 }