Repository: apache/camel-k
Pull request #2204 (2021-04-12 13:41:51)

chore: Tidy proposals directory
**Release Note**
```release-note
NONE
```



Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 900cffede..685422518 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -23,6 +23,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
@@ -32,6 +33,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml
index 557caeb6f..65e29ff81 100644
--- a/.github/workflows/builder.yml
+++ b/.github/workflows/builder.yml
@@ -23,6 +23,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
@@ -32,6 +33,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
diff --git a/.github/workflows/knative.yml b/.github/workflows/knative.yml
index caca4a3c2..27c6d44b8 100644
--- a/.github/workflows/knative.yml
+++ b/.github/workflows/knative.yml
@@ -23,6 +23,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
@@ -32,6 +33,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
diff --git a/.github/workflows/kubernetes.yml b/.github/workflows/kubernetes.yml
index 7a76e6614..81f816f55 100644
--- a/.github/workflows/kubernetes.yml
+++ b/.github/workflows/kubernetes.yml
@@ -23,6 +23,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
@@ -32,6 +33,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
diff --git a/.github/workflows/local.yml b/.github/workflows/local.yml
index b1067c0a7..77e95181d 100644
--- a/.github/workflows/local.yml
+++ b/.github/workflows/local.yml
@@ -23,6 +23,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
@@ -32,6 +33,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
diff --git a/.github/workflows/openshift.yml b/.github/workflows/openshift.yml
index 22aa6a1f7..27f058b52 100644
--- a/.github/workflows/openshift.yml
+++ b/.github/workflows/openshift.yml
@@ -23,6 +23,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
@@ -32,6 +33,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
diff --git a/.github/workflows/upgrade.yml b/.github/workflows/upgrade.yml
index b3b2e2a1f..ee9963019 100644
--- a/.github/workflows/upgrade.yml
+++ b/.github/workflows/upgrade.yml
@@ -23,6 +23,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
@@ -32,6 +33,7 @@ on:
       - master
       - "release-*"
     paths-ignore:
+      - 'proposals/**'
       - '**.adoc'
       - 'KEYS'
       - 'LICENSE'
@@ -64,9 +66,19 @@ jobs:
       with:
         version: "11"
     - name: Set Go
-      uses: actions/setup-go@v1
+      uses: actions/setup-go@v2
       with:
         go-version: 1.13.x
+    - name: Set up operator-sdk
+      run: |
+        curl -L https://github.com/operator-framework/operator-sdk/releases/download/v1.3.0/operator-sdk_linux_amd64 -o operator-sdk
+        chmod +x operator-sdk
+        sudo mv operator-sdk /usr/local/bin/
+    - name: Set up opm tool
+      run: |
+        curl -L https://github.com/operator-framework/operator-registry/releases/download/v1.16.1/linux-amd64-opm -o opm
+        chmod +x opm
+        sudo mv opm /usr/local/bin/
     - name: Kubernetes KinD Cluster
       uses: container-tools/kind-action@v1
       with:
@@ -75,6 +87,12 @@ jobs:
       run: |
         kubectl cluster-info
         kubectl describe nodes
+    - name: Install OLM
+      run: |
+        kubectl apply -f https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.17.0/crds.yaml
+        # wait for a while to be sure CRDs are installed
+        sleep 1
+        kubectl apply -f https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.17.0/olm.yaml
     - name: Get Kamel CLI
       run: |
         export KAMEL_VERSION=$(make get-last-released-version)
@@ -92,12 +110,52 @@ jobs:
         cp ./kamel ./build/_output/bin/
         echo "Building the images"
         export LOCAL_IMAGE=$KIND_REGISTRY/apache/camel-k:$(make get-version)
+        echo "LOCAL_IMAGE=${LOCAL_IMAGE}" >> $GITHUB_ENV
         docker build -t "${LOCAL_IMAGE}" -f build/Dockerfile .
         docker push ${LOCAL_IMAGE}
+    - name: Build Operator bundle
+      run: |
+        echo "Build Operator bundle"
+
+        # reinstall kustomize to be always on the same version
+        sudo rm $(which kustomize)
+
+        make kustomize
+
+        # replace image
+        $(cd config/manifests && kustomize edit set image "docker.io/apache/camel-k=${{ env.LOCAL_IMAGE }}")
+
+
+        # CSV the patch to force OLM upgrade
+        export RELEASE_VERSION=$(make get-version | grep -Po "\d.\d.\d")
+        cat <<EOF > config/manifests/patch.yml
+        apiVersion: operators.coreos.com/v1alpha1
+        kind: ClusterServiceVersion
+        metadata:
+          namespace: placeholder
+          name: camel-k.v$(make get-last-released-version)
+          annotations:
+            olm.skipRange: ">=1.0.0 <${RELEASE_VERSION}"
+        EOF
+
+        echo "Patching CSV with"
+        cat config/manifests/patch.yml
+        $(cd config/manifests && kustomize edit add patch patch.yml)
+
+        export LOCAL_IMAGE_BUNDLE=$KIND_REGISTRY/apache/camel-k-bundle:$(make get-version)
+        echo "LOCAL_IMAGE_BUNDLE=${LOCAL_IMAGE_BUNDLE}" >> $GITHUB_ENV
+        make bundle-build METADATA_IMAGE_NAME=${LOCAL_IMAGE_BUNDLE} BUNDLE_METADATA_OPTS="--channels stable"
+        docker push ${LOCAL_IMAGE_BUNDLE}
+    - name: Create new index image
+      run: |
+        export LOCAL_IIB=$KIND_REGISTRY/apache/camel-k-iib:$(make get-version)
+        echo "LOCAL_IIB=${LOCAL_IIB}" >> $GITHUB_ENV
+        opm index add --bundles ${{ env.LOCAL_IMAGE_BUNDLE }} --from-index quay.io/operatorhubio/catalog:latest --tag ${LOCAL_IIB} --skip-tls
+        podman push --tls-verify=false ${LOCAL_IIB}
     - name: Run IT
       run: |
         # Use the last released Kamel CLI
-        export KAMEL_BIN=/usr/local/bin/kamel
+        export RELEASED_KAMEL_BIN=/usr/local/bin/kamel
 
         # Configure install options
         export KAMEL_INSTALL_BUILD_PUBLISH_STRATEGY=Spectrum
@@ -105,10 +163,9 @@ jobs:
         export KAMEL_INSTALL_REGISTRY=$KIND_REGISTRY
         export KAMEL_INSTALL_REGISTRY_INSECURE=true
 
-        # TODO: Install OLM and cover upgrade managed by OLM
-        export KAMEL_INSTALL_OLM=false
-
         # Configure test options
+        export CAMEL_K_PREV_IIB=quay.io/operatorhubio/catalog:latest
+        export CAMEL_K_NEW_IIB=${{ env.LOCAL_IIB }}
         export KAMEL_K_TEST_RELEASE_VERSION=$(make get-last-released-version)
         export KAMEL_K_TEST_OPERATOR_CURRENT_IMAGE=$KIND_REGISTRY/apache/camel-k:$(make get-version)
 
diff --git a/config/manifests/kustomization.yaml b/config/manifests/kustomization.yaml
index 8697eb828..3dba3bd97 100644
--- a/config/manifests/kustomization.yaml
+++ b/config/manifests/kustomization.yaml
@@ -19,3 +19,4 @@ resources:
 - ../default
 - ../samples
 - ../scorecard
+- ./bases/camel-k.clusterserviceversion.yaml
diff --git a/pkg/cmd/bind.go b/pkg/cmd/bind.go
new file mode 100644
index 000000000..277189984
--- /dev/null
+++ b/pkg/cmd/bind.go
@@ -0,0 +1,357 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package cmd
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+
+	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/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/reference"
+	"github.com/apache/camel-k/pkg/util/uri"
+	"github.com/spf13/cobra"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// newCmdBind --
+func newCmdBind(rootCmdOptions *RootCmdOptions) (*cobra.Command, *bindCmdOptions) {
+	options := bindCmdOptions{
+		RootCmdOptions: rootCmdOptions,
+	}
+	cmd := cobra.Command{
+		Use:     "bind [source] [sink] ...",
+		Short:   "Bind Kubernetes resources, such as Kamelets, in an integration flow. Endpoints are expected in the format \"[[apigroup/]version:]kind:[namespace/]name\" or plain Camel URIs.",
+		PreRunE: decode(&options),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if err := options.validate(cmd, args); err != nil {
+				return err
+			}
+			if err := options.run(args); err != nil {
+				fmt.Println(err.Error())
+			}
+
+			return nil
+		},
+	}
+
+	cmd.Flags().String("name", "", "Name for the binding")
+	cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml")
+	cmd.Flags().StringArrayP("property", "p", nil, `Add a binding property in the form of "source.<key>=<value>", "sink.<key>=<value>" or "step-<n>.<key>=<value>"`)
+	cmd.Flags().Bool("skip-checks", false, "Do not verify the binding for compliance with Kamelets and other Kubernetes resources")
+	cmd.Flags().StringArray("step", nil, `Add binding steps as Kubernetes resources, such as Kamelets. Endpoints are expected in the format "[[apigroup/]version:]kind:[namespace/]name" or plain Camel URIs.`)
+
+	return &cmd, &options
+}
+
+const (
+	sourceKey     = "source"
+	sinkKey       = "sink"
+	stepKeyPrefix = "step-"
+)
+
+type bindCmdOptions struct {
+	*RootCmdOptions
+	Name         string   `mapstructure:"name" yaml:",omitempty"`
+	OutputFormat string   `mapstructure:"output" yaml:",omitempty"`
+	Properties   []string `mapstructure:"properties" yaml:",omitempty"`
+	SkipChecks   bool     `mapstructure:"skip-checks" yaml:",omitempty"`
+	Steps        []string `mapstructure:"steps" yaml:",omitempty"`
+}
+
+func (o *bindCmdOptions) validate(cmd *cobra.Command, args []string) error {
+	if len(args) > 2 {
+		return errors.New("too many arguments: expected source and sink")
+	} else if len(args) < 2 {
+		return errors.New("source or sink arguments are missing")
+	}
+
+	for _, p := range o.Properties {
+		if _, _, _, err := o.parseProperty(p); err != nil {
+			return err
+		}
+	}
+
+	if !o.SkipChecks {
+		source, err := o.decode(args[0], sourceKey)
+		if err != nil {
+			return err
+		}
+		if err := o.checkCompliance(cmd, source); err != nil {
+			return err
+		}
+
+		sink, err := o.decode(args[1], sinkKey)
+		if err != nil {
+			return err
+		}
+		if err := o.checkCompliance(cmd, sink); err != nil {
+			return err
+		}
+
+		for idx, stepDesc := range o.Steps {
+			stepKey := fmt.Sprintf("%s%d", stepKeyPrefix, idx)
+			step, err := o.decode(stepDesc, stepKey)
+			if err != nil {
+				return err
+			}
+			if err := o.checkCompliance(cmd, step); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func (o *bindCmdOptions) run(args []string) error {
+	source, err := o.decode(args[0], sourceKey)
+	if err != nil {
+		return err
+	}
+
+	sink, err := o.decode(args[1], sinkKey)
+	if err != nil {
+		return err
+	}
+
+	name := o.nameFor(source, sink)
+
+	binding := v1alpha1.KameletBinding{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: o.Namespace,
+			Name:      name,
+		},
+		Spec: v1alpha1.KameletBindingSpec{
+			Source: source,
+			Sink:   sink,
+		},
+	}
+
+	if len(o.Steps) > 0 {
+		binding.Spec.Steps = make([]v1alpha1.Endpoint, 0)
+		for idx, stepDesc := range o.Steps {
+			stepKey := fmt.Sprintf("%s%d", stepKeyPrefix, idx)
+			step, err := o.decode(stepDesc, stepKey)
+			if err != nil {
+				return err
+			}
+			binding.Spec.Steps = append(binding.Spec.Steps, step)
+		}
+	}
+
+	switch o.OutputFormat {
+	case "":
+		// continue..
+	case "yaml":
+		data, err := kubernetes.ToYAML(&binding)
+		if err != nil {
+			return err
+		}
+		fmt.Print(string(data))
+		return nil
+
+	case "json":
+		data, err := kubernetes.ToJSON(&binding)
+		if err != nil {
+			return err
+		}
+		fmt.Print(string(data))
+		return nil
+
+	default:
+		return fmt.Errorf("invalid output format option '%s', should be one of: yaml|json", o.OutputFormat)
+	}
+
+	client, err := o.GetCmdClient()
+	if err != nil {
+		return err
+	}
+
+	existed := false
+	err = client.Create(o.Context, &binding)
+	if err != nil && k8serrors.IsAlreadyExists(err) {
+		existed = true
+		err = kubernetes.ReplaceResource(o.Context, client, &binding)
+	}
+	if err != nil {
+		return err
+	}
+
+	if !existed {
+		fmt.Printf("kamelet binding \"%s\" created\n", name)
+	} else {
+		fmt.Printf("kamelet binding \"%s\" updated\n", name)
+	}
+	return nil
+}
+
+func (o *bindCmdOptions) decode(res string, key string) (v1alpha1.Endpoint, error) {
+	refConverter := reference.NewConverter(reference.KameletPrefix)
+	endpoint := v1alpha1.Endpoint{}
+	explicitProps := o.getProperties(key)
+	props, err := o.asEndpointProperties(explicitProps)
+	if err != nil {
+		return endpoint, err
+	}
+	endpoint.Properties = props
+
+	ref, err := refConverter.FromString(res)
+	if err != nil {
+		if uri.HasCamelURIFormat(res) {
+			endpoint.URI = &res
+			return endpoint, nil
+		}
+		return endpoint, err
+	}
+	endpoint.Ref = &ref
+	if endpoint.Ref.Namespace == "" {
+		endpoint.Ref.Namespace = o.Namespace
+	}
+	embeddedProps, err := refConverter.PropertiesFromString(res)
+	if err != nil {
+		return endpoint, err
+	}
+	if len(embeddedProps) > 0 {
+		allProps := make(map[string]string)
+		for k, v := range explicitProps {
+			allProps[k] = v
+		}
+		for k, v := range embeddedProps {
+			allProps[k] = v
+		}
+
+		props, err := o.asEndpointProperties(allProps)
+		if err != nil {
+			return endpoint, err
+		}
+		endpoint.Properties = props
+	}
+
+	return endpoint, nil
+}
+
+func (o *bindCmdOptions) nameFor(source, sink v1alpha1.Endpoint) string {
+	if o.Name != "" {
+		return o.Name
+	}
+	sourcePart := o.nameForEndpoint(source)
+	sinkPart := o.nameForEndpoint(sink)
+	name := fmt.Sprintf("%s-to-%s", sourcePart, sinkPart)
+	return kubernetes.SanitizeName(name)
+}
+
+func (o *bindCmdOptions) nameForEndpoint(endpoint v1alpha1.Endpoint) string {
+	if endpoint.URI != nil {
+		return uri.GetComponent(*endpoint.URI)
+	}
+	if endpoint.Ref != nil {
+		return endpoint.Ref.Name
+	}
+	return ""
+}
+
+func (o *bindCmdOptions) asEndpointProperties(props map[string]string) (*v1alpha1.EndpointProperties, error) {
+	if len(props) == 0 {
+		return nil, nil
+	}
+	data, err := json.Marshal(props)
+	if err != nil {
+		return nil, err
+	}
+	return &v1alpha1.EndpointProperties{
+		RawMessage: v1.RawMessage(data),
+	}, nil
+}
+
+func (o *bindCmdOptions) getProperties(refType string) map[string]string {
+	props := make(map[string]string)
+	for _, p := range o.Properties {
+		tp, k, v, err := o.parseProperty(p)
+		if err != nil {
+			continue
+		}
+		if tp == refType {
+			props[k] = v
+		}
+	}
+	return props
+}
+
+func (o *bindCmdOptions) parseProperty(prop string) (string, string, string, error) {
+	parts := strings.SplitN(prop, "=", 2)
+	if len(parts) != 2 {
+		return "", "", "", fmt.Errorf(`property %q does not follow format "[source|sink|step-<n>].<key>=<value>"`, prop)
+	}
+	keyParts := strings.SplitN(parts[0], ".", 2)
+	if len(keyParts) != 2 {
+		return "", "", "", fmt.Errorf(`property key %q does not follow format "[source|sink|step-<n>].<key>"`, parts[0])
+	}
+	isSource := keyParts[0] == sourceKey
+	isSink := keyParts[0] == sinkKey
+	isStep := strings.HasPrefix(keyParts[0], stepKeyPrefix)
+	if !isSource && !isSink && !isStep {
+		return "", "", "", fmt.Errorf(`property key %q does not start with "source.", "sink." or "step-<n>."`, parts[0])
+	}
+	return keyParts[0], keyParts[1], parts[1], nil
+}
+
+func (o *bindCmdOptions) checkCompliance(cmd *cobra.Command, endpoint v1alpha1.Endpoint) error {
+	if endpoint.Ref != nil && endpoint.Ref.Kind == "Kamelet" {
+		c, err := o.GetCmdClient()
+		if err != nil {
+			return err
+		}
+		key := client.ObjectKey{
+			Namespace: endpoint.Ref.Namespace,
+			Name:      endpoint.Ref.Name,
+		}
+		kamelet := v1alpha1.Kamelet{}
+		if err := c.Get(o.Context, key, &kamelet); err != nil {
+			if k8serrors.IsNotFound(err) {
+				// Kamelet may be in the operator namespace, but we currently don't have a way to determine it: we just warn
+				fmt.Fprintf(cmd.ErrOrStderr(), "Warning: Kamelet %q not found in namespace %q\n", key.Name, key.Namespace)
+				return nil
+			}
+			return err
+		}
+		if kamelet.Spec.Definition != nil && len(kamelet.Spec.Definition.Required) > 0 {
+			pMap, err := endpoint.Properties.GetPropertyMap()
+			if err != nil {
+				return err
+			}
+			for _, reqProp := range kamelet.Spec.Definition.Required {
+				found := false
+				if endpoint.Properties != nil {
+					if _, contains := pMap[reqProp]; contains {
+						found = true
+					}
+				}
+				if !found {
+					return fmt.Errorf("binding is missing required property %q for Kamelet %q", reqProp, key.Name)
+				}
+			}
+		}
+	}
+	return nil
+}
diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go
index c61a2fab1..06a775b00 100644
--- a/pkg/cmd/root.go
+++ b/pkg/cmd/root.go
@@ -147,6 +147,7 @@ func addKamelSubcommands(cmd *cobra.Command, options *RootCmdOptions) {
 	cmd.AddCommand(cmdOnly(newCmdDebug(options)))
 	cmd.AddCommand(cmdOnly(newCmdDump(options)))
 	cmd.AddCommand(newCmdLocal(options))
+	cmd.AddCommand(cmdOnly(newCmdBind(options)))
 }
 
 func addHelpSubCommands(cmd *cobra.Command, options *RootCmdOptions) error {
@@ -193,24 +194,24 @@ func (command *RootCmdOptions) preRun(cmd *cobra.Command, _ []string) error {
 		// Furthermore, there can be any incompatibilities, as the install command deploys
 		// the operator version it's compatible with.
 		if cmd.Use != installCommand && cmd.Use != operatorCommand {
-			checkAndShowCompatibilityWarning(command.Context, c, command.Namespace)
+			checkAndShowCompatibilityWarning(cmd, command.Context, c, command.Namespace)
 		}
 	}
 
 	return nil
 }
 
-func checkAndShowCompatibilityWarning(ctx context.Context, c client.Client, namespace string) {
+func checkAndShowCompatibilityWarning(cmd *cobra.Command, ctx context.Context, c client.Client, namespace string) {
 	operatorVersion, err := operatorVersion(ctx, c, namespace)
 	if err != nil {
 		if k8serrors.IsNotFound(err) {
-			fmt.Printf("No IntegrationPlatform resource in %s namespace\n", namespace)
+			fmt.Fprintf(cmd.ErrOrStderr(), "No IntegrationPlatform resource in %s namespace\n", namespace)
 		} else {
-			fmt.Printf("Unable to retrieve the operator version: %s", err.Error())
+			fmt.Fprintf(cmd.ErrOrStderr(), "Unable to retrieve the operator version: %s\n", err.Error())
 		}
 	} else {
 		if operatorVersion != "" && !compatibleVersions(operatorVersion, defaults.Version) {
-			fmt.Printf("You're using Camel K %s client with a %s cluster operator, it's recommended to use the same version to improve compatibility.\n\n", defaults.Version, operatorVersion)
+			fmt.Fprintf(cmd.ErrOrStderr(), "You're using Camel K %s client with a %s cluster operator, it's recommended to use the same version to improve compatibility.\n\n", defaults.Version, operatorVersion)
 		}
 	}
 }
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 7caea0083..255feafe7 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -414,7 +414,7 @@ func (o *runCmdOptions) syncIntegration(cmd *cobra.Command, c client.Client, sou
 				}
 			}()
 		} else {
-			fmt.Printf("WARNING: the following URL will not be watched for changes: %s\n", s)
+			fmt.Fprintf(cmd.ErrOrStderr(), "Warning: the following URL will not be watched for changes: %s\n", s)
 		}
 	}
 
diff --git a/pkg/trait/knative_service.go b/pkg/trait/knative_service.go
index a1e8ac19a..9cc77aaf6 100644
--- a/pkg/trait/knative_service.go
+++ b/pkg/trait/knative_service.go
@@ -102,10 +102,7 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	if e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
-		condition := e.Integration.Status.GetCondition(v1.IntegrationConditionKnativeServiceAvailable)
-		return condition != nil && condition.Status == corev1.ConditionTrue, nil
-	} else if !e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseDeploying) {
+	if !e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseDeploying) && !e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
 		return false, nil
 	}
 
@@ -164,6 +161,11 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) {
 		}
 	}
 
+	if e.IntegrationInPhase(v1.IntegrationPhaseRunning) {
+		condition := e.Integration.Status.GetCondition(v1.IntegrationConditionKnativeServiceAvailable)
+		return condition != nil && condition.Status == corev1.ConditionTrue, nil
+	}
+
 	return true, nil
 }
 
diff --git a/pkg/util/reference/reference.go b/pkg/util/reference/reference.go
index dc79cd59c..5af887e06 100644
--- a/pkg/util/reference/reference.go
+++ b/pkg/util/reference/reference.go
@@ -18,12 +18,14 @@ limitations under the License.
 package reference
 
 import (
-	"errors"
 	"fmt"
+	"net/url"
 	"regexp"
+	"strings"
 	"unicode"
 
 	camelv1alpha1 "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
 	eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
 	messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1"
@@ -35,8 +37,9 @@ const (
 )
 
 var (
-	simpleNameRegexp = regexp.MustCompile(`^(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)$`)
-	fullNameRegexp   = regexp.MustCompile(`^(?:(?P<apiVersion>(?:[a-z0-9-.]+/)?(?:[a-z0-9-.]+)):)?(?P<kind>[A-Za-z0-9-.]+):(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)$`)
+	simpleNameRegexp = regexp.MustCompile(`^(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)(?:$|[?].*$)`)
+	fullNameRegexp   = regexp.MustCompile(`^(?:(?P<apiVersion>(?:[a-z0-9-.]+/)?(?:[a-z0-9-.]+)):)?(?P<kind>[A-Za-z0-9-.]+):(?:(?P<namespace>[a-z0-9-.]+)/)?(?P<name>[a-z0-9-.]+)(?:$|[?].*$)`)
+	queryRegexp      = regexp.MustCompile(`^[^?]*[?](?P<query>.*)$`)
 
 	templates = map[string]corev1.ObjectReference{
 		"kamelet": corev1.ObjectReference{
@@ -81,6 +84,41 @@ func (c *Converter) FromString(str string) (corev1.ObjectReference, error) {
 	return ref, nil
 }
 
+func (c *Converter) PropertiesFromString(str string) (map[string]string, error) {
+	if queryRegexp.MatchString(str) {
+		groupNames := queryRegexp.SubexpNames()
+		res := make(map[string]string)
+		var query string
+		for _, match := range queryRegexp.FindAllStringSubmatch(str, -1) {
+			for idx, text := range match {
+				groupName := groupNames[idx]
+				switch groupName {
+				case "query":
+					query = text
+				}
+			}
+		}
+		parts := strings.Split(query, "&")
+		for _, part := range parts {
+			kv := strings.SplitN(part, "=", 2)
+			if len(kv) != 2 {
+				return nil, fmt.Errorf("invalid key=value format for string %q", part)
+			}
+			k, errkey := url.QueryUnescape(kv[0])
+			if errkey != nil {
+				return nil, errors.Wrapf(errkey, "cannot unescape key %q", kv[0])
+			}
+			v, errval := url.QueryUnescape(kv[1])
+			if errval != nil {
+				return nil, errors.Wrapf(errval, "cannot unescape value %q", kv[1])
+			}
+			res[k] = v
+		}
+		return res, nil
+	}
+	return nil, nil
+}
+
 func (c *Converter) expandReference(ref *corev1.ObjectReference) {
 	if template, ok := templates[ref.Kind]; ok {
 		if template.Kind != "" {
diff --git a/pkg/util/source/inspector.go b/pkg/util/source/inspector.go
index 746a03af1..3933a6182 100644
--- a/pkg/util/source/inspector.go
+++ b/pkg/util/source/inspector.go
@@ -324,6 +324,9 @@ func (i *baseInspector) hasOnlyPassiveEndpoints(fromURIs []string) bool {
 
 func (i *baseInspector) containsOnlyURIsIn(fromURI []string, allowed map[string]bool) bool {
 	for _, uri := range fromURI {
+		if uri == "kamelet:source" {
+			continue
+		}
 		prefix := i.getURIPrefix(uri)
 		if enabled, ok := allowed[prefix]; !ok || !enabled {
 			return false
diff --git a/pkg/util/uri/uri.go b/pkg/util/uri/uri.go
index 1e0cf1e83..542eee4c3 100644
--- a/pkg/util/uri/uri.go
+++ b/pkg/util/uri/uri.go
@@ -27,8 +27,15 @@ import (
 	"github.com/apache/camel-k/pkg/util/log"
 )
 
+var uriRegexp = regexp.MustCompile(`^[a-z0-9+][a-zA-Z0-9-+]*:.*$`)
+
 var queryExtractorRegexp = `^[^?]+\?(?:|.*[&])%s=([^&]+)(?:[&].*|$)`
 
+// HasCamelURIFormat tells if a given string may belong to a Camel URI, without checking any catalog
+func HasCamelURIFormat(uri string) bool {
+	return uriRegexp.MatchString(uri)
+}
+
 // GetComponent returns the Camel component used in the URI
 func GetComponent(uri string) string {
 	parts := strings.Split(uri, ":")
diff --git a/proposals/service-binding/assets/service-binding.svg b/proposals/assets/service-binding.svg
similarity index 100%
rename from proposals/service-binding/assets/service-binding.svg
rename to proposals/assets/service-binding.svg
diff --git a/proposals/service-binding.adoc b/proposals/service-binding.adoc
new file mode 100644
index 000000000..d2a166b4e
--- /dev/null
+++ b/proposals/service-binding.adoc
@@ -0,0 +1,123 @@
+---
+title: Service Binding
+authors:
+  - "@johnpoth"
+reviewers:
+  - "@lburgazzoli"
+approvers:
+  - TBD
+creation-date: 2020-15-12
+last-updated: 2020-15-12
+status: implementable
+see-also: []
+replaces: []
+superseded-by: []
+---
+
+[[service-binding]]
+= Service Binding
+
+== Summary
+
+This proposal aims at leveraging the https://github.com/k8s-service-bindings/spec#service-binding[Service Binding Specification] in Camel K.
+
+=== Terminology
+
+* _Provisioned Service_ 
+** A service e.g. a Database, or a Broker that can be _bound_ to an application by means defined in the https://github.com/k8s-service-bindings/spec#provisioned-service[specification]
+* _Application_
+** In the context of Camel K is an `Integration`
+
+== Motivation
+
+Users should be able to connect to a _Provisioned Service_ by specifying one or more __Service Binding__s from the command line, e.g.:
+
+[source,console]
+----
+$ kamel run intergration --connect database,broker
+----
+
+The _Service Binding_ could be referenced by name like shown above, in which case the _Service Binding_ already exists, or it could be created and managed by Camel K in which case the user would have to explicitly specify the _Provisioned Service_ they want to connect to:
+
+[source,console]
+----
+$ kamel run intergration --connect Database.v1alpha1.postgresql.baiju.dev/db-demo
+----
+
+The user may then reference the binding properties inside the integration just like any other properties.
+
+== Goals
+
+The goal of this proposal is to provide the current state of the https://github.com/k8s-service-bindings/spec#service-binding[Service Binding Specification] and how it might be leveraged in Camel K to ease route configuration and facilitate their re-usability.
+
+== Context
+
+The specification can be split into two categories: the _Service Binding Operator_, and the _Provisioned Service_ implementation.
+Several implementations of the _Service Binding Operator_ can be found readily available.
+
+Two have been tried in the context of this proposal:
+
+* https://github.com/vmware-labs/service-bindings
+* https://github.com/redhat-developer/service-binding-operator.
+
+_Provisioned Services_ implementations are still work in progress and aren't readily available.
+A list of https://github.com/k8s-service-bindings/spec/issues/16[first] and https://github.com/k8s-service-bindings/spec/issues/19[second] wave of specification adopters  is maintained. Knowing this, usability of the `Service Binding Specification` remains limited.
+However, an initial effort may be done in order to better understand how Camel K can benefit from it.
+
+=== Workflow
+
+Two main workflows have been identified in the specification that allows user applications to bind to a _Provisioned Service_.
+Let's review them and see which one would suit Camel K the best.
+
+The first way outlined in the specification is for the user to create a `ServiceBinding` indicating which _application_ to bind to which set of _Provisioned Services_:
+
+[source,yaml]
+----
+kind: ServiceBinding
+metadata:
+  name: account-service
+spec:
+  application:
+    apiVersion: apps/v1
+    kind:       Deployment
+    name:       camel-k-integration
+
+  service:
+    apiVersion: com.example/v1alpha1
+    kind:       Database
+    name:       my-database
+----
+
+Once created, it is the responsibility of the _Service Binding Operator_ to inject the binding information into the _application_.
+The _application_  is any `PodSpec`-able resource e.g a `Deployment`.
+The way it does this is that it first scales the `Deployment` down to 0.
+Then it injects the binding information into the `Deployment` in the form of a `Secret` or a `ConfigMap`.
+Finally, it scales the `Deployment` back up.
+
+The second way is through https://github.com/k8s-service-bindings/spec#custom-projection-service-binding-example-resource[Custom Projection].
+The difference between this approach, and the first one is that the binding information injection into the _application_ is not the responsibility of the _Service Binding Operator_ anymore.
+It wil be the responsibility of another Operator. In our case it will be Camel K's responsibility to retrieve the binding information and inject it into the `Integration`.
+
+The drawback of the first approach, in the context of Camel K, is that the ``Integration``'s `Deployment` would be created in an incomplete state and would result in an error the first time the underlying container is started because the binding information is not present.
+The second drawback is that the ``Integration``'s `Deployment` resource should ideally _not_ be altered by someone other than the Camel K operator itself.
+The advantage of using this approach is that less work is needed in Camel K as injection is taken care of by the _Service Binding Operator_.
+
+Conversely, the drawback of implementing the second approach is that more work is needed as Camel K would have to retrieve and inject the binding information.
+The advantage would be that Camel K retains full control of the ``Integration``'s `Deployment/Cron/Knative` resource, and we can delay the startup until the binding information is available.
+
+For these reasons, it seems that the second approach is preferred in the context of Camel K.
+
+== Proposal
+
+=== Architecture
+
+The proposed architecture is illustrated in the diagram below:
+
+image::assets/service-binding.svg[Camel K Integration Service Binding]
+
+where the `Secret` created by the _Service Binding Operator_ holds the binding information. Quarkus' Service Binding https://github.com/quarkusio/quarkus/issues/13617[support] could then be leveraged to configure the `Integration`.
+
+The steps `2-4` would not happen if the `ServiceBinding` is created beforehand.
+
+In the Optional steps `2-4`, Camel K will have to wait until the `ServiceBinding` is injected with the `Secret`.
+To avoid useless reconciliation loops, a new `Phase` would be added (e.g. `IntegrationPhaseWaitingForServiceBinding`) which would pause the integration until Camel K is notified of a change in the `ServiceBinding`.
diff --git a/proposals/service-binding/service-binding.adoc b/proposals/service-binding/service-binding.adoc
deleted file mode 100644
index ac3e730e2..000000000
--- a/proposals/service-binding/service-binding.adoc
+++ /dev/null
@@ -1,101 +0,0 @@
----
-title: Service Binding
-authors:
-  - "@johnpoth"
-reviewers:
-  - "@lburgazzoli"
-approvers:
-  - TBD
-creation-date: 2020-15-12
-last-updated: 2020-15-12
-status: implementable
-see-also: []
-replaces: []
-superseded-by: []
----
-
-[[service-binding]]
-= Service Binding
-
-== Summary
-
-This proposal aims at leveraging the https://github.com/k8s-service-bindings/spec#service-binding[Service Binding Specification] in Camel-k. 
-
-=== Terminology
-
-* _Provisioned Service_ 
-** A service e.g a Database or a Broker that can be _bound_ to an application by means definied in the https://github.com/k8s-service-bindings/spec#provisioned-service[specification]
-* _Application_
-** In the context of Camel-k is an `Integration`
-
-== Motivation
-
-Users should be able to connect to a _Provisioned Service_ by specifying one or more __Service Binding__s in the command line:
-
-```
-kamel run intergration --connect database,broker
-```
-
-The _Service Binding_ could be referenced by name like shown above, in which case the _Service Binding_ already exists, or it could be created and managed by Camel-k in which case the user would have to explicitly specify the _Provisioned Service_ they want to connect to:
-
-```
-kamel run intergration --connect Database.v1alpha1.postgresql.baiju.dev/db-demo
-```
-
-The user may then reference the binding properties inside the integration just like any other properties.
-
-== Goals
-
-The goal of this proposal is to provide the current state of the https://github.com/k8s-service-bindings/spec#service-binding[Service Binding Specification] and how it might be leveraged in Camel-k to ease route configuration and facilitate their reusability.
-
-== Context
-
-The specification can be split into two categories: the _Service Binding Operator_ and the _Provisioned Service_ implementation. Several implementations of the _Service Binding Operator_ can be found readily available. Two have have been tried in the context of this proposal: https://github.com/vmware-labs/service-bindings and https://github.com/redhat-developer/service-binding-operator. _Provisioned Services_ implementations are still work in progress and aren't readily available. A list of https://github.com/k8s-service-bindings/spec/issues/16[first] and https://github.com/k8s-service-bindings/spec/issues/19[second] wave of specification adopters  is maintained. Knowing this, usability of the `Service Binding Specification` remains limited. However an initial effort may be done in order to better understand how Camel-k can benefit from it.
-
-=== Workflow
-
-Two main workflows have been identified in the specification that allows user applications to bind to a _Provisioned Service_. Let's review them and see which one would suit Camel-k the best.
-
-The first way outlined in the specification is for the user to create a `ServiceBinding` indicating which _application_ to bind to which set of _Provisioned Services_:
-
-[source,yaml]
-----
-kind: ServiceBinding
-metadata:
-  name: account-service
-spec:
-  application:
-    apiVersion: apps/v1
-    kind:       Deployment
-    name:       camel-k-integration
-
-  service:
-    apiVersion: com.example/v1alpha1
-    kind:       Database
-    name:       my-database
-----
-
-Once created, it is the responsibility of the _Service Binding Operator_ to inject the binding information into the _application_. The _application_  is any `PodSpec`-able resource e.g a `Deployment`. The way it does this is that it first scales the `Deployment` down to 0. Then it injects the binding information into the `Deployment` in the form of a `Secret` or a `ConfigMap`. Finally it scales the `Deployment` back up.
-
-The second way is through https://github.com/k8s-service-bindings/spec#custom-projection-service-binding-example-resource[Custom Projection]. The difference between this approach and the first one is that the binding information injection into the _application_ is not the responsibility of the _Service Binding Operator_ anymore. It wil be the responsibility of another Operator. In our case it will be Camel-k's responsibility to retrieve the binding information and inject it into the `Integration`.
-
-The drawback of the first approach, in the context of Camel-k, is that the ``Integration``'s `Deployment` would be created in an incomplete state and would result in an error the first time the underlying container is started because the binding information is not present. The second drawback is that the ``Integration``'s `Deployment` resource should ideally _not_ be altered by someone other than the Camel-k operator itself. The advantage of using this approach is that less work is needed in Camel-k as injection is taken care of by the _Service Binding Operator_.
-
-Conversely, the drawback of implementing the second approach is that more work is needed as Camel-k would have to retrieve and inject the binding information.
-The advantage would be that Camel-k retains full control of the ``Integration``'s `Deployment/Cron/Knative` resource and we can delay the startup until the binding information is available.
-
-For these reasons, it seems that the second approach is preferred in the context of Camel-k.
-
-== Proposal
-
-=== Architecture
-
-The proposed architecture is illustrated in the diagram below:
-
-image::assets/service-binding.svg[Camel K Integration Service Binding]
-
-where the `Secret` created by the _Service Binding Operator_ holds the binding information. Quarkus' Service Binding https://github.com/quarkusio/quarkus/issues/13617[support] could then be leveraged to configure the `Integration`.
-
-The steps `2-4` would not happen if the `ServiceBinding` is created beforehand.
-
-In the Optional steps `2-4`, Camel-k will have to wait until the `ServiceBinding` is injected with the `Secret`. To avoid useless reconciliation loops, a new `Phase` would be added (e.g `IntegrationPhaseWaitingForServiceBinding`) which would pause the integration until Camel-k is notified of a change in the `ServiceBinding`.


Test patch:
diff --git a/e2e/common/cli/dev_mode_test.go b/e2e/common/cli/dev_mode_test.go
index fef9153a5..93cb05207 100644
--- a/e2e/common/cli/dev_mode_test.go
+++ b/e2e/common/cli/dev_mode_test.go
@@ -38,6 +38,7 @@ func TestRunDevMode(t *testing.T) {
 		Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
 
 		t.Run("run yaml dev mode", func(t *testing.T) {
+			RegisterTestingT(t)
 			ctx, cancel := context.WithCancel(TestContext)
 			defer cancel()
 			piper, pipew := io.Pipe()
@@ -65,6 +66,7 @@ func TestRunDevMode(t *testing.T) {
 		})
 
 		t.Run("run yaml remote dev mode", func(t *testing.T) {
+			RegisterTestingT(t)
 			ctx, cancel := context.WithCancel(TestContext)
 			defer cancel()
 			piper, pipew := io.Pipe()
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 1b4526656..6e2632ffb 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -27,6 +27,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	v1alpha12 "github.com/operator-framework/api/pkg/operators/v1alpha1"
 	"io"
 	"io/ioutil"
 	"os"
@@ -34,6 +35,7 @@ import (
 	"strings"
 	"testing"
 	"time"
+	"unsafe"
 
 	"github.com/google/uuid"
 	"github.com/onsi/gomega"
@@ -116,6 +118,7 @@ func init() {
 	client.FastMapperAllowedAPIGroups["eventing.knative.dev"] = true
 	client.FastMapperAllowedAPIGroups["messaging.knative.dev"] = true
 	client.FastMapperAllowedAPIGroups["serving.knative.dev"] = true
+	client.FastMapperAllowedAPIGroups["operators.coreos.com"] = true
 	client.FastMapperAllowedAPIGroups["policy"] = true
 
 	var err error
@@ -670,6 +673,65 @@ func KnativeService(ns string, name string) func() *servingv1.Service {
 	}
 }
 
+func CKClusterServiceVersion(conditions func(v1alpha12.ClusterServiceVersion) bool ,ns string) func() *v1alpha12.ClusterServiceVersion {
+	return func() *v1alpha12.ClusterServiceVersion {
+		lst := v1alpha12.ClusterServiceVersionList{}
+		if err := TestClient().List(TestContext, &lst, ctrl.InNamespace(ns)); err != nil {
+			panic(err)
+		}
+		for _, s := range lst.Items {
+			if strings.Contains(s.Name, "camel-k") && conditions(s){
+				return &s
+			}
+		}
+		return nil
+	}
+}
+
+func CKClusterServiceVersionPhase(conditions func(v1alpha12.ClusterServiceVersion) bool, ns string) func() v1alpha12.ClusterServiceVersionPhase {
+	return func() v1alpha12.ClusterServiceVersionPhase {
+		if csv := CKClusterServiceVersion(conditions, ns)(); csv != nil && unsafe.Sizeof(csv.Status) > 0 {
+			return csv.Status.Phase
+		}
+		return ""
+	}
+}
+
+func CatalogSource(name string, ns string) func() *v1alpha12.CatalogSource {
+	return func() *v1alpha12.CatalogSource {
+		answer := v1alpha12.CatalogSource{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       "CatalogSource",
+				APIVersion: v1alpha1.SchemeGroupVersion.String(),
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: ns,
+				Name:      name,
+			},
+		}
+		key := ctrl.ObjectKey{
+			Namespace: ns,
+			Name:      name,
+		}
+		if err := TestClient().Get(TestContext, key, &answer); err != nil && k8serrors.IsNotFound(err) {
+			return nil
+		} else if err != nil {
+			log.Errorf(err, "Error while retrieving CatalogSource %s", name)
+			return nil
+		}
+		return &answer
+	}
+}
+
+func CatalogSourcePhase(name string, ns string) func() string {
+	return func() string {
+		if source := CatalogSource(name, ns)(); source != nil && source.Status.GRPCConnectionState != nil {
+			return CatalogSource(name, ns)().Status.GRPCConnectionState.LastObservedState
+		}
+		return ""
+	}
+}
+
 func Deployment(ns string, name string) func() *appsv1.Deployment {
 	return func() *appsv1.Deployment {
 		answer := appsv1.Deployment{
diff --git a/e2e/upgrade/olm_upgrade_test.go b/e2e/upgrade/olm_upgrade_test.go
new file mode 100644
index 000000000..291349382
--- /dev/null
+++ b/e2e/upgrade/olm_upgrade_test.go
@@ -0,0 +1,163 @@
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"fmt"
+	. "github.com/apache/camel-k/e2e/support"
+	. "github.com/onsi/gomega"
+	"github.com/operator-framework/api/pkg/lib/version"
+	"github.com/operator-framework/api/pkg/operators/v1alpha1"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"os"
+	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+	"testing"
+)
+
+const CATALOG_SOURCE_NAME = "test-camel-k-source"
+
+func TestOLMAutomaticUpgrade(t *testing.T) {
+	prevIIB := os.Getenv("CAMEL_K_PREV_IIB")
+	newIIB := os.Getenv("CAMEL_K_NEW_IIB")
+	kamel := os.Getenv("RELEASED_KAMEL_BIN")
+
+	if prevIIB == "" || newIIB == "" {
+		t.Skip("OLM Upgrade test needs CAMEL_K_PREV_IIB and CAMEL_K_PREV_IIB ENV variables")
+	}
+
+	WithNewTestNamespace(t, func(ns string) {
+		Expect(createCatalogSource(CATALOG_SOURCE_NAME, prevIIB, ns)).To(Succeed())
+		Eventually(CatalogSourcePhase(CATALOG_SOURCE_NAME, ns), TestTimeoutMedium).Should(Equal("READY"))
+
+		//set KAMEL_BIN only for this test - don't override the ENV variable for all tests
+		Expect(os.Setenv("KAMEL_BIN", kamel)).To(Succeed())
+
+		Expect(Kamel("install", "-n", ns, "--olm=true", "--olm-source", CATALOG_SOURCE_NAME, "--olm-source-namespace", ns).Execute()).To(Succeed())
+
+		//find the only one Camel-K CSV
+		noAdditionalConditions := func(csv v1alpha1.ClusterServiceVersion) bool {
+			return true
+		}
+		Eventually(CKClusterServiceVersionPhase(noAdditionalConditions, ns), TestTimeoutMedium).Should(Equal(v1alpha1.CSVPhaseSucceeded))
+
+		// Refresh the test client to account for the newly installed CRDs
+		SyncClient()
+
+		var prevCSVVersion version.OperatorVersion
+		var newCSVVersion version.OperatorVersion
+
+		// IntegrationPlatform should match at least on the version prefix
+		// CSV patch version can be increased with the OperatorHub respin of the same Camel-K release
+		var prevIPVersionPrefix string
+		var newIPVersionPrefix string
+
+		prevCSVVersion = CKClusterServiceVersion(noAdditionalConditions, ns)().Spec.Version
+		prevIPVersionPrefix = fmt.Sprintf("%d.%d", prevCSVVersion.Version.Major, prevCSVVersion.Version.Minor)
+
+		Expect(OperatorPod(ns)).ToNot(BeNil())
+
+		Expect(Kamel("run", "-n", ns, "files/yaml.yaml").Execute()).To(Succeed())
+		Eventually(IntegrationPodPhase(ns, "yaml"), TestTimeoutMedium).Should(Equal(v1.PodRunning))
+
+		Eventually(PlatformVersion(ns)).Should(ContainSubstring(prevIPVersionPrefix))
+		Expect(IntegrationVersion(ns, "yaml")()).To(ContainSubstring(prevIPVersionPrefix))
+
+		t.Run("OLM upgrade", func(t *testing.T) {
+
+			//invoke OLM upgrade
+			Expect(createCatalogSource(CATALOG_SOURCE_NAME, newIIB, ns)).To(Succeed())
+
+			// previous CSV is REPLACING
+			Eventually(CKClusterServiceVersionPhase(func(csv v1alpha1.ClusterServiceVersion) bool {
+				return csv.Spec.Version.Version.String() == prevCSVVersion.Version.String()
+			}, ns), TestTimeoutMedium).Should(Equal(v1alpha1.CSVPhaseReplacing))
+
+			// the new version is installed
+			Eventually(CKClusterServiceVersionPhase(func(csv v1alpha1.ClusterServiceVersion) bool {
+				return csv.Spec.Version.Version.String() != prevCSVVersion.Version.String()
+			}, ns), TestTimeoutMedium).Should(Equal(v1alpha1.CSVPhaseSucceeded))
+
+			// the old version is gone
+			Eventually(CKClusterServiceVersion(func(csv v1alpha1.ClusterServiceVersion) bool {
+				return csv.Spec.Version.Version.String() == prevCSVVersion.Version.String()
+			}, ns), TestTimeoutMedium).Should(BeNil())
+
+			newCSVVersion = CKClusterServiceVersion(noAdditionalConditions, ns)().Spec.Version
+			newIPVersionPrefix = fmt.Sprintf("%d.%d", newCSVVersion.Version.Major, newCSVVersion.Version.Minor)
+
+			Expect(prevCSVVersion.Version.String()).NotTo(Equal(newCSVVersion.Version.String()))
+
+			Eventually(OperatorPodPhase(ns)).Should(Equal(v1.PodRunning))
+
+			Eventually(PlatformVersion(ns)).Should(ContainSubstring(newIPVersionPrefix))
+		})
+
+		t.Run("Integration upgrade", func(t *testing.T) {
+
+			// Clear the KAMEL_BIN environment variable so that the current version is used from now on
+			Expect(os.Setenv("KAMEL_BIN", "")).To(Succeed())
+
+			Expect(IntegrationVersion(ns, "yaml")()).To(ContainSubstring(prevIPVersionPrefix))
+			Expect(Kamel("rebuild", "yaml", "-n", ns).Execute()).To(Succeed())
+
+			Eventually(IntegrationVersion(ns, "yaml"), TestTimeoutMedium).Should(ContainSubstring(newIPVersionPrefix))
+			Eventually(IntegrationPodPhase(ns, "yaml")).Should(Equal(v1.PodRunning))
+
+			// Clean up
+			Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+			Expect(Kamel("uninstall", "-n", ns).Execute()).To(Succeed())
+		})
+
+	})
+}
+
+func createCatalogSource(name string, image string, ns string) error {
+	catalogSource := v1alpha1.CatalogSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "CatalogSource",
+			APIVersion: v1alpha1.SchemeGroupVersion.String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: ns,
+			Name:      name,
+		},
+	}
+	key := ctrl.ObjectKey{
+		Namespace: ns,
+		Name:      name,
+	}
+	if err := TestClient().Get(TestContext, key, &catalogSource); errors.IsNotFound(err) {
+		catalogSource.Spec = v1alpha1.CatalogSourceSpec{
+			Image:       image,
+			SourceType:  "grpc",
+			DisplayName: "OLM upgrade test Catalog",
+			Publisher:   "grpc",
+		}
+		return TestClient().Create(TestContext, &catalogSource)
+	} else {
+		catalogSource.Spec.Image = image
+		return TestClient().Update(TestContext, &catalogSource)
+	}
+
+}
diff --git a/e2e/upgrade/upgrade_operator_test.go b/e2e/upgrade/upgrade_operator_test.go
index 9510846c3..2ce57759a 100644
--- a/e2e/upgrade/upgrade_operator_test.go
+++ b/e2e/upgrade/upgrade_operator_test.go
@@ -36,6 +36,9 @@ import (
 )
 
 func TestOperatorUpgrade(t *testing.T) {
+	// Clean all cluster-wide resources that could corrupt the test run
+	Expect(Kamel("uninstall", "--all", "--olm=false").Execute()).To(Succeed())
+
 	WithNewTestNamespace(t, func(ns string) {
 		version, ok := os.LookupEnv("KAMEL_K_TEST_RELEASE_VERSION")
 		Expect(ok).To(BeTrue())
@@ -43,8 +46,14 @@ func TestOperatorUpgrade(t *testing.T) {
 		image, ok := os.LookupEnv("KAMEL_K_TEST_OPERATOR_CURRENT_IMAGE")
 		Expect(ok).To(BeTrue())
 
-		Expect(Kamel("install", "--cluster-setup").Execute()).To(Succeed())
-		Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
+		kamel, ok := os.LookupEnv("RELEASED_KAMEL_BIN")
+		Expect(ok).To(BeTrue())
+
+		//set KAMEL_BIN only for this test - don't override the ENV variable for all tests
+		Expect(os.Setenv("KAMEL_BIN", kamel)).To(Succeed())
+
+		Expect(Kamel("install", "--olm=false", "--cluster-setup", "--force").Execute()).To(Succeed())
+		Expect(Kamel("install", "--olm=false", "-n", ns).Execute()).To(Succeed())
 
 		// Check the operator pod is running
 		Eventually(OperatorPodPhase(ns), TestTimeoutMedium).Should(Equal(v1.PodRunning))
@@ -69,8 +78,8 @@ func TestOperatorUpgrade(t *testing.T) {
 
 		// Upgrade the operator by installing the current version
 		// FIXME: it seems forcing the installation does not re-install the CRDs
-		Expect(Kamel("install", "--cluster-setup", "--force").Execute()).To(Succeed())
-		Expect(Kamel("install", "-n", ns, "--force", "--operator-image", image).Execute()).To(Succeed())
+		Expect(Kamel("install", "--olm=false", "--cluster-setup", "--force").Execute()).To(Succeed())
+		Expect(Kamel("install", "-n", ns, "--olm=false", "--force", "--operator-image", image).Execute()).To(Succeed())
 
 		// Check the operator image is the current built one
 		Eventually(OperatorImage(ns)).Should(Equal(image))
@@ -99,5 +108,6 @@ func TestOperatorUpgrade(t *testing.T) {
 
 		// Clean up
 		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+		Expect(Kamel("uninstall", "--all", "--olm=false").Execute()).To(Succeed())
 	})
 }
diff --git a/e2e/yaks/common/apache-kamelet-catalog/kamelet.feature b/e2e/yaks/common/apache-kamelet-catalog/kamelet.feature
index 648295735..46618d7c8 100644
--- a/e2e/yaks/common/apache-kamelet-catalog/kamelet.feature
+++ b/e2e/yaks/common/apache-kamelet-catalog/kamelet.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can run Kamelets from default catalog
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Integrations can use default catalog
     Given Camel-K integration logger is running
     Then Camel-K integration logger should print Camel K
diff --git a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature
index f84e78e0a..608c7d01f 100644
--- a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature
+++ b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload-specific.feature
@@ -4,8 +4,8 @@ Feature: Camel K can load specific secrets for Kamelets
     Given Disable auto removal of Kamelet resources
     Given Disable auto removal of Kubernetes resources
     Given Camel-K resource polling configuration
-      | maxAttempts          | 20   |
-      | delayBetweenAttempts | 1000 |
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
 
   Scenario: KameletBinding can load specific settings for Kamelet
     Given create Kubernetes service stub-service-2 with target port 8081
diff --git a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature
index 297e93aa1..3662acd70 100644
--- a/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature
+++ b/e2e/yaks/common/kamelet-binding-autoload/kamelet-autoload.feature
@@ -4,8 +4,8 @@ Feature: Camel K can load default secrets for Kamelets
     Given Disable auto removal of Kamelet resources
     Given Disable auto removal of Kubernetes resources
     Given Camel-K resource polling configuration
-      | maxAttempts          | 20   |
-      | delayBetweenAttempts | 1000 |
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
 
   Scenario: KameletBinding can load default settings for Kamelet
     Given create Kubernetes service stub-service with target port 8080
diff --git a/e2e/yaks/common/kamelet-binding-broker/kamelet.feature b/e2e/yaks/common/kamelet-binding-broker/kamelet.feature
index ea45ada51..afd7a0c56 100644
--- a/e2e/yaks/common/kamelet-binding-broker/kamelet.feature
+++ b/e2e/yaks/common/kamelet-binding-broker/kamelet.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can bind Kamelets to the broker
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Sending event to the broker with KameletBinding
     Given Camel-K integration logger-sink-binding is running
     Then Camel-K integration logger-sink-binding should print message: Hello Custom Event
diff --git a/e2e/yaks/common/kamelet-binding/display.groovy b/e2e/yaks/common/kamelet-binding-http/display.groovy
similarity index 100%
rename from e2e/yaks/common/kamelet-binding/display.groovy
rename to e2e/yaks/common/kamelet-binding-http/display.groovy
diff --git a/e2e/yaks/common/kamelet-binding-http/kamelet.feature b/e2e/yaks/common/kamelet-binding-http/kamelet.feature
new file mode 100644
index 000000000..443082387
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-http/kamelet.feature
@@ -0,0 +1,11 @@
+Feature: Camel K can bind Kamelets via HTTP
+
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
+  Scenario: Binding to a HTTP URI should use CloudEvents
+    Given Camel-K integration display is running
+    Then Camel-K integration display should print type: org.apache.camel.event
+    Then Camel-K integration display should print Hello
diff --git a/e2e/yaks/common/kamelet-binding/timer-source-binding-display.yaml b/e2e/yaks/common/kamelet-binding-http/timer-source-binding-display.yaml
similarity index 100%
rename from e2e/yaks/common/kamelet-binding/timer-source-binding-display.yaml
rename to e2e/yaks/common/kamelet-binding-http/timer-source-binding-display.yaml
diff --git a/e2e/yaks/common/kamelet-binding-http/timer-source.kamelet.yaml b/e2e/yaks/common/kamelet-binding-http/timer-source.kamelet.yaml
new file mode 100644
index 000000000..a08cecfbb
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-http/timer-source.kamelet.yaml
@@ -0,0 +1,54 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+apiVersion: camel.apache.org/v1alpha1
+kind: Kamelet
+metadata:
+  name: timer-source
+  labels:
+    camel.apache.org/kamelet.type: "source"
+spec:
+  definition:
+    title: "Timer"
+    description: "Produces periodic events with a custom payload"
+    required:
+      - message
+    properties:
+      period:
+        title: Period
+        description: The time interval between two events
+        type: integer
+        default: 1000
+      message:
+        title: Message
+        description: The message to generate
+        type: string
+  types:
+    out:
+      mediaType: application/json
+      schema:
+        id: text.camel.apache.org
+        type: string
+  flow:
+    from:
+      uri: timer:tick
+      parameters:
+        period: "{{period}}"
+      steps:
+        - set-body:
+            constant: "{{message}}"
+        - to: "kamelet:sink"
diff --git a/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml b/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml
new file mode 100644
index 000000000..de3709b89
--- /dev/null
+++ b/e2e/yaks/common/kamelet-binding-http/yaks-config.yaml
@@ -0,0 +1,30 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+config:
+  namespace:
+    temporary: true
+pre:
+- name: installation
+  run: |
+    kamel install -n $YAKS_NAMESPACE
+
+    kubectl apply -f timer-source.kamelet.yaml -n $YAKS_NAMESPACE
+
+    kamel run display.groovy -w -n $YAKS_NAMESPACE
+    cat timer-source-binding-display.yaml | sed  's/{namespace}/'"${YAKS_NAMESPACE}"'/' | kubectl apply -n $YAKS_NAMESPACE -f -
+    kubectl wait kameletbinding timer-source-binding-display --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
diff --git a/e2e/yaks/common/kamelet-binding/kamelet.feature b/e2e/yaks/common/kamelet-binding/kamelet.feature
index 4234a3b9c..3cf777569 100644
--- a/e2e/yaks/common/kamelet-binding/kamelet.feature
+++ b/e2e/yaks/common/kamelet-binding/kamelet.feature
@@ -1,10 +1,10 @@
 Feature: Camel K can bind Kamelets
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Running integration using a simple Kamelet with KameletBinding
     Given Camel-K integration logger-sink-binding is running
     Then Camel-K integration logger-sink-binding should print message: Hello Kamelets
-
-  Scenario: Binding to a HTTP URI should use CloudEvents
-    Given Camel-K integration display is running
-    Then Camel-K integration display should print type: org.apache.camel.event
-    Then Camel-K integration display should print Hello
diff --git a/e2e/yaks/common/kamelet-binding/yaks-config.yaml b/e2e/yaks/common/kamelet-binding/yaks-config.yaml
index 1e0370b0c..a75bafe41 100644
--- a/e2e/yaks/common/kamelet-binding/yaks-config.yaml
+++ b/e2e/yaks/common/kamelet-binding/yaks-config.yaml
@@ -33,7 +33,3 @@ pre:
 
     kubectl wait kameletbinding timer-source-binding --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
     kubectl wait kameletbinding logger-sink-binding --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
-
-    kamel run display.groovy -w -n $YAKS_NAMESPACE
-    cat timer-source-binding-display.yaml | sed  's/{namespace}/'"${YAKS_NAMESPACE}"'/' | kubectl apply -n $YAKS_NAMESPACE -f -
-    kubectl wait kameletbinding timer-source-binding-display --for=condition=Ready --timeout=10m -n $YAKS_NAMESPACE
diff --git a/e2e/yaks/common/kamelet-no-properties/kamelet-no-props.feature b/e2e/yaks/common/kamelet-no-properties/kamelet-no-props.feature
index 04806ef1f..29ab6d6bb 100644
--- a/e2e/yaks/common/kamelet-no-properties/kamelet-no-props.feature
+++ b/e2e/yaks/common/kamelet-no-properties/kamelet-no-props.feature
@@ -3,6 +3,9 @@ Feature: Kamelet may have no properties
   Background:
     Given Disable auto removal of Camel-K resources
     Given Disable auto removal of Kamelet resources
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
 
   Scenario: Create Kamelet
     Given create Kamelet no-props-source with flow
diff --git a/e2e/yaks/common/kamelet-steps/steps-binding.feature b/e2e/yaks/common/kamelet-steps/steps-binding.feature
index f09994597..4166cf412 100644
--- a/e2e/yaks/common/kamelet-steps/steps-binding.feature
+++ b/e2e/yaks/common/kamelet-steps/steps-binding.feature
@@ -1,5 +1,10 @@
 Feature: KameletBindings can have multiple processing steps
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Data is transformed by the steps
     Given Camel-K integration steps-binding is running
     Then Camel-K integration steps-binding should print Hello Apache Camel
diff --git a/e2e/yaks/common/kamelet/kamelet.feature b/e2e/yaks/common/kamelet/kamelet.feature
index a3d92177b..5e2c8963c 100644
--- a/e2e/yaks/common/kamelet/kamelet.feature
+++ b/e2e/yaks/common/kamelet/kamelet.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can run Kamelets
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Integrations can use multiple kamelets
     Given Camel-K integration source-sink is running
     Then Camel-K integration source-sink should print nice echo: Camel K
diff --git a/e2e/yaks/common/knative-sinkbinding/sinkbinding.feature b/e2e/yaks/common/knative-sinkbinding/sinkbinding.feature
index 842ee44a7..6af9673e6 100644
--- a/e2e/yaks/common/knative-sinkbinding/sinkbinding.feature
+++ b/e2e/yaks/common/knative-sinkbinding/sinkbinding.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can run source in sinkbinding mode
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Integration gets the message from the sinkbinding source
     Given Camel-K integration receiver is running
     Then Camel-K integration receiver should print HELLO SINKBINDING
diff --git a/e2e/yaks/common/knative/sources.feature b/e2e/yaks/common/knative/sources.feature
index db0edbeb4..0257e8a97 100644
--- a/e2e/yaks/common/knative/sources.feature
+++ b/e2e/yaks/common/knative/sources.feature
@@ -1,5 +1,10 @@
 Feature: Camel K can run latest released Knative CamelSource
 
+  Background:
+    Given Camel-K resource polling configuration
+      | maxAttempts          | 40   |
+      | delayBetweenAttempts | 3000 |
+
   Scenario: Integration gets the message from the source
     Given Camel-K integration receiver is running
     Then Camel-K integration receiver should print MagicString!
diff --git a/pkg/metadata/metadata_http_test.go b/pkg/metadata/metadata_http_test.go
index a0ec2bbc7..3f653acdf 100644
--- a/pkg/metadata/metadata_http_test.go
+++ b/pkg/metadata/metadata_http_test.go
@@ -57,6 +57,7 @@ func TestHttpOnlyJavaSource(t *testing.T) {
 			from("direct:bots/cippa").to("log:stash");
 			from("netty-http:uri").to("log:stash");
 			from("seda:path").to("log:stash");
+			from("kamelet:source").to("log:sink");
 		`,
 		},
 		Language: v1.LanguageJavaSource,
diff --git a/pkg/util/reference/reference_test.go b/pkg/util/reference/reference_test.go
index f13c5d846..ca5be0bc7 100644
--- a/pkg/util/reference/reference_test.go
+++ b/pkg/util/reference/reference_test.go
@@ -33,6 +33,7 @@ func TestExpressions(t *testing.T) {
 		error         bool
 		ref           corev1.ObjectReference
 		stringRef     string
+		properties    map[string]string
 	}{
 		{
 			name:  "lowercase:source",
@@ -123,6 +124,37 @@ func TestExpressions(t *testing.T) {
 			},
 			stringRef: "postgres.org/v1alpha1:PostgreSQL:ns1/db",
 		},
+		{
+			name: "postgres.org/v1alpha1:PostgreSQL:ns1/db?user=user1&password=pwd2&host=192.168.2.2&special=%201&special2=a=1",
+			ref: corev1.ObjectReference{
+				APIVersion: "postgres.org/v1alpha1",
+				Kind:       "PostgreSQL",
+				Namespace:  "ns1",
+				Name:       "db",
+			},
+			stringRef: "postgres.org/v1alpha1:PostgreSQL:ns1/db",
+			properties: map[string]string{
+				"user":     "user1",
+				"password": "pwd2",
+				"host":     "192.168.2.2",
+				"special":  " 1",
+				"special2": "a=1",
+			},
+		},
+		{
+			name: "source?a=b&b=c&d=e",
+			ref: corev1.ObjectReference{
+				Kind:       "Kamelet",
+				APIVersion: "camel.apache.org/v1alpha1",
+				Name:       "source",
+			},
+			stringRef: "camel.apache.org/v1alpha1:Kamelet:source",
+			properties: map[string]string{
+				"a": "b",
+				"b": "c",
+				"d": "e",
+			},
+		},
 	}
 
 	for i, tc := range tests {
@@ -143,6 +175,10 @@ func TestExpressions(t *testing.T) {
 				asString, err2 := converter.ToString(ref)
 				assert.NoError(t, err2)
 
+				props, err3 := converter.PropertiesFromString(tc.name)
+				assert.NoError(t, err3)
+				assert.Equal(t, tc.properties, props)
+
 				assert.NoError(t, err)
 				assert.Equal(t, tc.ref, ref)
 				assert.Equal(t, tc.stringRef, asString)
diff --git a/pkg/util/uri/uri_test.go b/pkg/util/uri/uri_test.go
index ae22ff8a9..19dcdcaa1 100644
--- a/pkg/util/uri/uri_test.go
+++ b/pkg/util/uri/uri_test.go
@@ -142,3 +142,40 @@ func TestAppendParameters(t *testing.T) {
 		})
 	}
 }
+
+func TestCamelURIFormat(t *testing.T) {
+	tests := []struct {
+		uri     string
+		invalid bool
+	}{
+		{
+			uri: "knative:channnel",
+		},
+		{
+			uri: "atomix-value:",
+		},
+		{
+			uri: "aws-ec2:",
+		},
+		{
+			uri: "coap+tcp:",
+		},
+		{
+			uri: "solrCloud:",
+		},
+		{
+			uri:     "PostgreSQL:db",
+			invalid: true,
+		},
+		{
+			uri:     "postgres.org/v1alpha1:PostgreSQL:db",
+			invalid: true,
+		},
+	}
+
+	for i, tc := range tests {
+		t.Run(fmt.Sprintf("%d-%s", i, tc.uri), func(t *testing.T) {
+			assert.Equal(t, !tc.invalid, HasCamelURIFormat(tc.uri))
+		})
+	}
+}