Repository: apache/camel-k
Pull request #2048 (2021-02-17 17:25:26)

Add local build command replacing local create
<!-- Description -->


This patch enables the changes outlined in issue https://github.com/apache/camel-k/issues/2038

Concretely the patch brings in the following CLI changes:

1. `inspect` is now `local inspect` with no other functional changes
2. `local create` has been renamed to `local build` and has had the functionality expanded:

In addition to the features of `local create`, `local build` can also now create a directory structure with the integration files: property files, routes and dependencies.

```
kamel local build --integration-directory <dir-name> file1.yaml file2.java file3.groovy
```

The directory will be created in the parent directory of where the command was invoked and will contain the following directory structure:
```
<dir-name>/properties
<dir-name>/routes
<dir-name>/dependencies
```

The `properties`, `routes` and `dependencies` subdirectories will contain the respective integration files.

For convenience one can output just the dependencies as follows:
```
kamel local build --integration-directory <dir-name> --dependencies-only file1.yaml file2.java file3.groovy
```

3. `local run` can now take as input an integration directory constructed by `local build`. Only full integration directories can be processed i.e. directories created with `--dependencies-only` are not supported.

`local run` can use an integration directory as input and run the integration locally:
```
kamel local run --integration-directory <dir-name>
```

`local run` can use an integration directory and run the integration within a container:
```
kamel local run --integration-directory <dir-name> --containerize --network=host --image <image-name>
```

For now, if route files contain modeline options which depend on lazily evaluated environment variables (for example `[[env:ENV_VAR]]`) then the command above will need to specify the environment variable explicitly like so:
```
kamel local run --integration-directory <dir-name> --env ENV_VAR
kamel local run --integration-directory <dir-name> --containerize --network=host --image <image-name> --env ENV_VAR
```

<!--
Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". 

You can (optionally) mark this PR with labels "kind/bug" or "kind/feature" to make sure
the text is added to the right section of the release notes. 
-->

**Release Note**
```release-note
- transform current inspect command to: kamel local inspect
- rename kamel local create to kamel local build
- enable kamel local build to construct a local directory hierarchy, at a custom location specified by the user, containing all the elements required by the integration.
- enable kamel local run to pick up integration directory and just run the integration locally.
- enable kamel local build to save just the dependencies in a custom location specified by the user.
```



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fe922e2c8..0edfab299 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,11 @@
 
 **Merged pull requests:**
 
+- chore\(api\): Add Scale methods to the Integration generated client API [\#2052](https://github.com/apache/camel-k/pull/2052) ([astefanutti](https://github.com/astefanutti))
+- For the moment lets remove absolute path [\#2049](https://github.com/apache/camel-k/pull/2049) ([oscerd](https://github.com/oscerd))
+- fix: Consider ObjectMeta field for deep derivative comparison before patching [\#2042](https://github.com/apache/camel-k/pull/2042) ([astefanutti](https://github.com/astefanutti))
+- fix\(pdb\): Add TypeMeta field to PodDisruptionBudget resource [\#2041](https://github.com/apache/camel-k/pull/2041) ([astefanutti](https://github.com/astefanutti))
+- feat: Use server-side apply to create and patch owned resources [\#2039](https://github.com/apache/camel-k/pull/2039) ([astefanutti](https://github.com/astefanutti))
 - chore\(ci\): Pin the Kubernetes version deployed by Kind [\#2037](https://github.com/apache/camel-k/pull/2037) ([astefanutti](https://github.com/astefanutti))
 - fix\(ci\): Use kubectl wait command to check Knative readiness [\#2036](https://github.com/apache/camel-k/pull/2036) ([astefanutti](https://github.com/astefanutti))
 - chore\(olm\): Update IntegrationPlatform creation step in CSV description [\#2034](https://github.com/apache/camel-k/pull/2034) ([astefanutti](https://github.com/astefanutti))
@@ -757,6 +762,7 @@
 **Merged pull requests:**
 
 - Fix \#1449: add support for modeline options [\#1455](https://github.com/apache/camel-k/pull/1455) ([nicolaferraro](https://github.com/nicolaferraro))
+- information to update the homebrew formula [\#1454](https://github.com/apache/camel-k/pull/1454) ([ipolyzos](https://github.com/ipolyzos))
 - Fix \#1450: fix servicemonitor roles on direct install [\#1453](https://github.com/apache/camel-k/pull/1453) ([nicolaferraro](https://github.com/nicolaferraro))
 - Helm fix [\#1448](https://github.com/apache/camel-k/pull/1448) ([nicolaferraro](https://github.com/nicolaferraro))
 
@@ -775,7 +781,6 @@
 
 **Merged pull requests:**
 
-- information to update the homebrew formula [\#1454](https://github.com/apache/camel-k/pull/1454) ([ipolyzos](https://github.com/ipolyzos))
 - Fix \#1446 and \#1395: completely update integration resource on redepl… [\#1447](https://github.com/apache/camel-k/pull/1447) ([nicolaferraro](https://github.com/nicolaferraro))
 - Fix \#1367: add more build events [\#1444](https://github.com/apache/camel-k/pull/1444) ([nicolaferraro](https://github.com/nicolaferraro))
 - Partially Fixes Camel 14995: generates master and 3/threescale traits docs. [\#1441](https://github.com/apache/camel-k/pull/1441) ([djencks](https://github.com/djencks))
@@ -1209,7 +1214,6 @@
 - Orphan generated XML DSL from OpenAPI JSON after deleting integration [\#839](https://github.com/apache/camel-k/issues/839)
 - All links of the documentation are missing spaces before and after displayed text [\#794](https://github.com/apache/camel-k/issues/794)
 - Update Camel K tagline in Kamel CLI [\#732](https://github.com/apache/camel-k/issues/732)
-- Review reset and delete with --all option for merging similar functions to one place  [\#598](https://github.com/apache/camel-k/issues/598)
 - Able to run maven build with debug and/or extended info [\#85](https://github.com/apache/camel-k/issues/85)
 - Remove references to host that did compilation in stacktraces [\#972](https://github.com/apache/camel-k/issues/972)
 - Integration fails with --dev mode [\#968](https://github.com/apache/camel-k/issues/968)
@@ -1513,6 +1517,7 @@
 - allow to configure default configurations on platform [\#608](https://github.com/apache/camel-k/issues/608)
 - Script set-version.sh does not honor the registry/repository settings [\#602](https://github.com/apache/camel-k/issues/602)
 - add options to configure affinity [\#601](https://github.com/apache/camel-k/issues/601)
+- Review reset and delete with --all option for merging similar functions to one place  [\#598](https://github.com/apache/camel-k/issues/598)
 - camel-k groovy, jvm, spring-boot gives error pushing image [\#588](https://github.com/apache/camel-k/issues/588)
 - on-premises: mount rwx volume [\#587](https://github.com/apache/camel-k/issues/587)
 - context with status error should not be included when looking up suitable context for an integration [\#584](https://github.com/apache/camel-k/issues/584)
@@ -1615,6 +1620,8 @@
 - feature\(kaniko\): support for incremental build [\#530](https://github.com/apache/camel-k/pull/530) ([lburgazzoli](https://github.com/lburgazzoli))
 - ordered delete [\#529](https://github.com/apache/camel-k/pull/529) ([lburgazzoli](https://github.com/lburgazzoli))
 - chore\(kamel\): fix reset command [\#526](https://github.com/apache/camel-k/pull/526) ([lburgazzoli](https://github.com/lburgazzoli))
+- chore\(release\): add automatic version update to release [\#524](https://github.com/apache/camel-k/pull/524) ([nicolaferraro](https://github.com/nicolaferraro))
+- Adding Probes to Knative services [\#514](https://github.com/apache/camel-k/pull/514) ([lburgazzoli](https://github.com/lburgazzoli))
 
 ## [0.3.0](https://github.com/apache/camel-k/tree/0.3.0) (2019-03-04)
 
@@ -1642,14 +1649,12 @@
 
 **Merged pull requests:**
 
-- chore\(release\): add automatic version update to release [\#524](https://github.com/apache/camel-k/pull/524) ([nicolaferraro](https://github.com/nicolaferraro))
 - fix: Allow file names as delete cmd argument [\#522](https://github.com/apache/camel-k/pull/522) ([christophd](https://github.com/christophd))
 - chore\(image\): include camel runtime adapters [\#521](https://github.com/apache/camel-k/pull/521) ([lburgazzoli](https://github.com/lburgazzoli))
 - test\(builder\): more assertions on deps [\#520](https://github.com/apache/camel-k/pull/520) ([oscerd](https://github.com/oscerd))
 - chore\(build\): remove jitpack configuration [\#519](https://github.com/apache/camel-k/pull/519) ([lburgazzoli](https://github.com/lburgazzoli))
 - chore\(runtime\): Bump runtime to 0.3.0 [\#518](https://github.com/apache/camel-k/pull/518) ([oscerd](https://github.com/oscerd))
 - knative\(serving\): add support for additional annotations [\#517](https://github.com/apache/camel-k/pull/517) ([lburgazzoli](https://github.com/lburgazzoli))
-- Adding Probes to Knative services [\#514](https://github.com/apache/camel-k/pull/514) ([lburgazzoli](https://github.com/lburgazzoli))
 - chore\(make\): fix images-push target [\#510](https://github.com/apache/camel-k/pull/510) ([lburgazzoli](https://github.com/lburgazzoli))
 - minikube doc update [\#509](https://github.com/apache/camel-k/pull/509) ([kameshsampath](https://github.com/kameshsampath))
 - chore\(crd\): add missing label to CamelCatalog CRD [\#502](https://github.com/apache/camel-k/pull/502) ([lburgazzoli](https://github.com/lburgazzoli))
diff --git a/deploy/traits.yaml b/deploy/traits.yaml
index f2936cc2a..ebe104d6d 100755
--- a/deploy/traits.yaml
+++ b/deploy/traits.yaml
@@ -633,6 +633,20 @@ traits:
   - name: description-path
     type: string
     description: The path where the Open-API specification is published (default `/openapi.json`)
+- name: toleration
+  platform: false
+  profiles:
+  - Kubernetes
+  - Knative
+  - OpenShift
+  description: 'This trait sets Tolerations over Integration pods. Tolerations allow (but do not require) the pods to schedule onto nodes with matching taints. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ for more details. The toleration should be expressed in a similar manner of taints *_Key[=Value]:Effect[:Seconds]_* where values in square brackets are optional. Examples: node-role.kubernetes.io/master:NoSchedule node.kubernetes.io/network-unavailable:NoExecute:3000 disktype=ssd:PreferNoSchedule It''s disabled by default.'
+  properties:
+  - name: enabled
+    type: bool
+    description: Can be used to enable or disable a trait. All traits share this common property.
+  - name: taints
+    type: '[]string'
+    description: The taint to tolerate in the form Key[=Value]:Effect[:Seconds]
 - name: tracing
   platform: false
   profiles:
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index fffc8c970..49610117a 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -59,5 +59,6 @@
 ** xref:traits:route.adoc[Route]
 ** xref:traits:service-binding.adoc[Service Binding]
 ** xref:traits:service.adoc[Service]
+** xref:traits:toleration.adoc[Toleration]
 ** xref:traits:tracing.adoc[Tracing]
 // End of autogenerated code - DO NOT EDIT! (trait-nav)
diff --git a/docs/modules/ROOT/pages/troubleshooting/debugging.adoc b/docs/modules/ROOT/pages/troubleshooting/debugging.adoc
index 3e229da17..6d08b42a2 100644
--- a/docs/modules/ROOT/pages/troubleshooting/debugging.adoc
+++ b/docs/modules/ROOT/pages/troubleshooting/debugging.adoc
@@ -5,7 +5,7 @@ Attaching a Java debugger to an integration is a common way to start the investi
 
 Even if Camel K integrations run on a Kubernetes cluster, it's very easy to attach a Java debugger to the remote integration using the CLI.
 
-If you're a VS Code user, you can watch this link:https://www.youtube.com/watch?v=cFPuxd1Je3c[video] or follow this link:https://camel.apache.org/blog/2021/01/DebugInVSCodeWithCamelK1.3.0/[blogpost].
+If you're a VS Code user, you can watch this link:https://www.youtube.com/watch?v=cFPuxd1Je3c[video].
 
 Find below a more generic explanation using IntelliJ.
 
diff --git a/docs/modules/traits/pages/toleration.adoc b/docs/modules/traits/pages/toleration.adoc
new file mode 100755
index 000000000..906745be0
--- /dev/null
+++ b/docs/modules/traits/pages/toleration.adoc
@@ -0,0 +1,43 @@
+= Toleration Trait
+
+// Start of autogenerated code - DO NOT EDIT! (description)
+This trait sets Tolerations over Integration pods. Tolerations allow (but do not require) the pods to schedule onto nodes with matching taints.
+See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ for more details.
+
+The toleration should be expressed in a similar manner of taints *_Key[=Value]:Effect[:Seconds]_* where values in square brackets are optional. Examples:
+
+node-role.kubernetes.io/master:NoSchedule
+node.kubernetes.io/network-unavailable:NoExecute:3000
+disktype=ssd:PreferNoSchedule
+
+It's disabled by default.
+
+
+This trait is available in the following profiles: **Kubernetes, Knative, OpenShift**.
+
+// End of autogenerated code - DO NOT EDIT! (description)
+// Start of autogenerated code - DO NOT EDIT! (configuration)
+== Configuration
+
+Trait properties can be specified when running any integration with the CLI:
+[source,console]
+----
+$ kamel run --trait toleration.[key]=[value] --trait toleration.[key2]=[value2] integration.groovy
+----
+The following configuration options are available:
+
+[cols="2m,1m,5a"]
+|===
+|Property | Type | Description
+
+| toleration.enabled
+| bool
+| Can be used to enable or disable a trait. All traits share this common property.
+
+| toleration.taints
+| []string
+| The taint to tolerate in the form Key[=Value]:Effect[:Seconds]
+
+|===
+
+// End of autogenerated code - DO NOT EDIT! (configuration)
diff --git a/pkg/apis/camel/v1/integration_types.go b/pkg/apis/camel/v1/integration_types.go
index 52a585368..678d0e61b 100644
--- a/pkg/apis/camel/v1/integration_types.go
+++ b/pkg/apis/camel/v1/integration_types.go
@@ -65,6 +65,8 @@ type IntegrationStatus struct {
 }
 
 // +genclient
+// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/api/autoscaling/v1.Scale
+// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale
 // +kubebuilder:object:root=true
 // +kubebuilder:resource:path=integrations,scope=Namespaced,shortName=it,categories=kamel;camel
 // +kubebuilder:subresource:status
diff --git a/pkg/client/camel/clientset/versioned/typed/camel/v1/fake/fake_integration.go b/pkg/client/camel/clientset/versioned/typed/camel/v1/fake/fake_integration.go
index d05954441..7a976f609 100644
--- a/pkg/client/camel/clientset/versioned/typed/camel/v1/fake/fake_integration.go
+++ b/pkg/client/camel/clientset/versioned/typed/camel/v1/fake/fake_integration.go
@@ -23,6 +23,7 @@ import (
 	"context"
 
 	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	autoscalingv1 "k8s.io/api/autoscaling/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	labels "k8s.io/apimachinery/pkg/labels"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -141,3 +142,25 @@ func (c *FakeIntegrations) Patch(ctx context.Context, name string, pt types.Patc
 	}
 	return obj.(*camelv1.Integration), err
 }
+
+// GetScale takes name of the integration, and returns the corresponding scale object, and an error if there is any.
+func (c *FakeIntegrations) GetScale(ctx context.Context, integrationName string, options v1.GetOptions) (result *autoscalingv1.Scale, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewGetSubresourceAction(integrationsResource, c.ns, "scale", integrationName), &autoscalingv1.Scale{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*autoscalingv1.Scale), err
+}
+
+// UpdateScale takes the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any.
+func (c *FakeIntegrations) UpdateScale(ctx context.Context, integrationName string, scale *autoscalingv1.Scale, opts v1.UpdateOptions) (result *autoscalingv1.Scale, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewUpdateSubresourceAction(integrationsResource, "scale", c.ns, scale), &autoscalingv1.Scale{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*autoscalingv1.Scale), err
+}
diff --git a/pkg/client/camel/clientset/versioned/typed/camel/v1/integration.go b/pkg/client/camel/clientset/versioned/typed/camel/v1/integration.go
index 01e6a9d1c..47b7d98a1 100644
--- a/pkg/client/camel/clientset/versioned/typed/camel/v1/integration.go
+++ b/pkg/client/camel/clientset/versioned/typed/camel/v1/integration.go
@@ -25,6 +25,7 @@ import (
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	scheme "github.com/apache/camel-k/pkg/client/camel/clientset/versioned/scheme"
+	autoscalingv1 "k8s.io/api/autoscaling/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	types "k8s.io/apimachinery/pkg/types"
 	watch "k8s.io/apimachinery/pkg/watch"
@@ -48,6 +49,9 @@ type IntegrationInterface interface {
 	List(ctx context.Context, opts metav1.ListOptions) (*v1.IntegrationList, error)
 	Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
 	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Integration, err error)
+	GetScale(ctx context.Context, integrationName string, options metav1.GetOptions) (*autoscalingv1.Scale, error)
+	UpdateScale(ctx context.Context, integrationName string, scale *autoscalingv1.Scale, opts metav1.UpdateOptions) (*autoscalingv1.Scale, error)
+
 	IntegrationExpansion
 }
 
@@ -194,3 +198,32 @@ func (c *integrations) Patch(ctx context.Context, name string, pt types.PatchTyp
 		Into(result)
 	return
 }
+
+// GetScale takes name of the integration, and returns the corresponding autoscalingv1.Scale object, and an error if there is any.
+func (c *integrations) GetScale(ctx context.Context, integrationName string, options metav1.GetOptions) (result *autoscalingv1.Scale, err error) {
+	result = &autoscalingv1.Scale{}
+	err = c.client.Get().
+		Namespace(c.ns).
+		Resource("integrations").
+		Name(integrationName).
+		SubResource("scale").
+		VersionedParams(&options, scheme.ParameterCodec).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// UpdateScale takes the top resource name and the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any.
+func (c *integrations) UpdateScale(ctx context.Context, integrationName string, scale *autoscalingv1.Scale, opts metav1.UpdateOptions) (result *autoscalingv1.Scale, err error) {
+	result = &autoscalingv1.Scale{}
+	err = c.client.Put().
+		Namespace(c.ns).
+		Resource("integrations").
+		Name(integrationName).
+		SubResource("scale").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(scale).
+		Do(ctx).
+		Into(result)
+	return
+}
diff --git a/pkg/cmd/completion_bash.go b/pkg/cmd/completion_bash.go
index 929ab0891..6d554c1ed 100644
--- a/pkg/cmd/completion_bash.go
+++ b/pkg/cmd/completion_bash.go
@@ -89,6 +89,32 @@ __kamel_deletion_policy() {
     COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") )
 }
 
+__kamel_kubectl_get_servicebinding() {
+    local template
+    local template_gvkn
+    local kubectl_out
+    local service_names
+    local services_list
+
+    template="{{ range .items  }}{{ .metadata.name }} {{ end }}"
+    template_gvkn="{{ range .items  }}{{ .kind  }}/{{ .apiVersion  }}/{{ .metadata.name }} {{ end }}" 
+    if kubectl_out=$(kubectl get -o template --template="${template}" crd -l service.binding/provisioned-service=true 2>/dev/null); then
+        kubectl_out="${kubectl_out// /,}"
+        service_names="${kubectl_out}servicebinding"
+        if kubectl_out=$(kubectl get -o template --template="${template_gvkn}" ${service_names} 2>/dev/null); then
+            for resource in  $kubectl_out
+            do
+               name=$(echo ${resource} | cut -d'/' -f 4)
+               version=$(echo ${resource} | cut -d'/' -f 3)
+               group=$(echo ${resource} | cut -d'/' -f 2)
+               kind=$(echo ${resource} | cut -d'/' -f 1)
+               services_list="${services_list} ${kind}.${version}.${group}/${name}"
+            done
+            COMPREPLY=( $( compgen -W "${services_list[*]}" -- "$cur" ) )
+        fi
+    fi
+}
+
 __kamel_kubectl_get_configmap() {
     local template
     local kubectl_out
@@ -252,6 +278,13 @@ func configureKnownBashCompletions(command *cobra.Command) {
 			cobra.BashCompCustom: {"__kamel_deletion_policy"},
 		},
 	)
+	configureBashAnnotationForFlag(
+		command,
+		"connect",
+		map[string][]string{
+			cobra.BashCompCustom: {"__kamel_kubectl_get_servicebinding"},
+		},
+	)
 }
 
 func configureBashAnnotationForFlag(command *cobra.Command, flagName string, annotations map[string][]string) {
diff --git a/pkg/cmd/local.go b/pkg/cmd/local.go
index af04dad5e..fd035a00c 100644
--- a/pkg/cmd/local.go
+++ b/pkg/cmd/local.go
@@ -49,8 +49,9 @@ func addLocalSubCommands(cmd *cobra.Command, options *RootCmdOptions) error {
 		return errors.New("could not find any configured local command")
 	}
 
+	localCmd.AddCommand(cmdOnly(newCmdLocalBuild(options)))
+	localCmd.AddCommand(cmdOnly(newCmdLocalInspect(options)))
 	localCmd.AddCommand(cmdOnly(newCmdLocalRun(options)))
-	localCmd.AddCommand(cmdOnly(newCmdLocalCreate(options)))
 
 	return nil
 }
diff --git a/pkg/cmd/local_build.go b/pkg/cmd/local_build.go
new file mode 100644
index 000000000..2e4219417
--- /dev/null
+++ b/pkg/cmd/local_build.go
@@ -0,0 +1,254 @@
+/*
+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 (
+	"fmt"
+
+	"github.com/apache/camel-k/pkg/util"
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+)
+
+func newCmdLocalBuild(rootCmdOptions *RootCmdOptions) (*cobra.Command, *localBuildCmdOptions) {
+	options := localBuildCmdOptions{
+		RootCmdOptions: rootCmdOptions,
+	}
+
+	cmd := cobra.Command{
+		Use:     "build [options]",
+		Short:   "Build integration images locally.",
+		Long:    `Build integration images locally for containerized integrations.`,
+		PreRunE: decode(&options),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if err := options.validate(args); err != nil {
+				return err
+			}
+			if err := options.init(args); err != nil {
+				return err
+			}
+			if err := options.run(cmd, args); err != nil {
+				fmt.Println(err.Error())
+			}
+			if err := options.deinit(args); err != nil {
+				return err
+			}
+
+			return nil
+		},
+		Annotations: map[string]string{
+			offlineCommandLabel: "true",
+		},
+	}
+
+	cmd.Flags().Bool("base-image", false, "Build base image used as a starting point for any integration.")
+	cmd.Flags().Bool("dependencies-only", false, "Only output the integration dependencies. The integration-directory flag must be set.")
+	cmd.Flags().String("container-registry", "", "Registry that holds intermediate images. This flag should only be used in conjunction with the base-image flag.")
+	cmd.Flags().String("image", "", "Full path to integration image including registry.")
+	cmd.Flags().String("integration-directory", "", "Directory to hold local integration files.")
+	cmd.Flags().StringArray("property-file", nil, "Add a property file to the integration.")
+	cmd.Flags().StringArrayP("property", "p", nil, "Add a Camel property to the integration.")
+	cmd.Flags().StringArrayP("dependency", "d", nil, "Add an additional dependency")
+	cmd.Flags().StringArray("maven-repository", nil, "Use a maven repository")
+
+	return &cmd, &options
+}
+
+type localBuildCmdOptions struct {
+	*RootCmdOptions
+	BaseImage              bool     `mapstructure:"base-image"`
+	DependenciesOnly       bool     `mapstructure:"dependencies-only"`
+	ContainerRegistry      string   `mapstructure:"container-registry"`
+	Image                  string   `mapstructure:"image"`
+	IntegrationDirectory   string   `mapstructure:"integration-directory"`
+	AdditionalDependencies []string `mapstructure:"dependencies"`
+	Properties             []string `mapstructure:"properties"`
+	PropertyFiles          []string `mapstructure:"property-files"`
+	MavenRepositories      []string `mapstructure:"maven-repositories"`
+}
+
+func (command *localBuildCmdOptions) validate(args []string) error {
+	// Validate integration files.
+	if len(args) > 0 {
+		err := validateIntegrationFiles(args)
+		if err != nil {
+			return err
+		}
+
+		// Cannot have both integration files and the base image construction enabled.
+		if command.BaseImage {
+			return errors.New("integration files have been provided and the base image construction is enabled")
+		}
+	}
+
+	// Validate additional dependencies specified by the user.
+	err := validateAdditionalDependencies(command.AdditionalDependencies)
+	if err != nil {
+		return err
+	}
+
+	// Validate properties file.
+	err = validateFiles(command.PropertyFiles)
+	if err != nil {
+		return nil
+	}
+
+	// ContainerRegistry should only be specified when building the base image.
+	if !command.BaseImage && command.ContainerRegistry != "" {
+		return errors.New("cannot specify container registry unless a base integration image is being built")
+	}
+
+	// Docker registry must be set.
+	if command.BaseImage && command.ContainerRegistry == "" {
+		return errors.New("base image cannot be built because container registry has not been provided")
+	}
+
+	// If an integration directory is provided then no base image containerization can be enabled.
+	if command.BaseImage && command.IntegrationDirectory != "" {
+		return errors.New("base image construction does not use integration files")
+	}
+
+	// The integration directory must be set when only outputting dependencies.
+	if command.DependenciesOnly && command.IntegrationDirectory == "" {
+		return errors.New("to output dependencies the integration directory flag must be set")
+	}
+
+	return nil
+}
+
+func (command *localBuildCmdOptions) init(args []string) error {
+	// Create integration directory if one is provided.
+	err := util.CreateDirectory(command.IntegrationDirectory)
+	if err != nil {
+		return err
+	}
+
+	if command.BaseImage || command.Image != "" {
+		// If base image construction is enabled create a directory for it.
+		err := createDockerBaseWorkingDirectory()
+		if err != nil {
+			return err
+		}
+
+		// If integration image construction is enabled, an integration image will be built.
+		if command.Image != "" {
+			err := createDockerWorkingDirectory()
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	err = createMavenWorkingDirectory()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (command *localBuildCmdOptions) run(cmd *cobra.Command, args []string) error {
+	dependenciesList := []string{}
+	propertyFilesList := []string{}
+	routeFiles := args
+	if !command.BaseImage {
+		// Fetch dependencies.
+		dependencies, err := getDependencies(args, command.AdditionalDependencies, command.MavenRepositories, true)
+		if err != nil {
+			return err
+		}
+
+		propertyFiles := []string{}
+		if !command.DependenciesOnly {
+			// Manage integration properties which may come from files or CLI.
+			propertyFiles, err = updateIntegrationProperties(command.Properties, command.PropertyFiles, false)
+			if err != nil {
+				return err
+			}
+		}
+
+		dependenciesList = dependencies
+		propertyFilesList = propertyFiles
+		hasIntegrationDir := command.IntegrationDirectory != ""
+		if hasIntegrationDir {
+			// Create dependencies subdirectory.
+			localDependenciesDirectory := getCustomDependenciesDir(command.IntegrationDirectory)
+
+			// Copy dependencies in persistent IntegrationDirectory/dependencies
+			dependenciesList, err = util.CopyIntegrationFilesToDirectory(dependencies, localDependenciesDirectory)
+			if err != nil {
+				return err
+			}
+
+			// Once dependencies have been copied to local folder, we can exit.
+			if command.DependenciesOnly {
+				return nil
+			}
+
+			// Create dependencies subdirectory.
+			localPropertiesDirectory := getCustomPropertiesDir(command.IntegrationDirectory)
+
+			// Copy dependencies in persistent IntegrationDirectory/dependencies
+			propertyFilesList, err = util.CopyIntegrationFilesToDirectory(propertyFiles, localPropertiesDirectory)
+			if err != nil {
+				return err
+			}
+
+			// Save routes.
+			localRoutesDirectory := getCustomRoutesDir(command.IntegrationDirectory)
+
+			// Copy routes in persistent IntegrationDirectory/dependencies
+			routeFiles, err = util.CopyIntegrationFilesToDirectory(args, localRoutesDirectory)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Integration directory can only be used when building an integration image or when we just
+	// build the integration without also building the image. A local build of the integration is
+	// represented by all the files that define	 the integration: dependencies, properties and routes.
+
+	// The only case where we should not execute the image integration creation is when we want to
+	// just output the files that comprise the integration locally.
+	if command.IntegrationDirectory != "" && command.Image == "" {
+		return nil
+	}
+
+	// Create and build integration image.
+	err := createAndBuildIntegrationImage(command.Context, command.ContainerRegistry, command.BaseImage,
+		command.Image, propertyFilesList, dependenciesList, routeFiles, cmd.OutOrStdout(), cmd.ErrOrStderr())
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (command *localBuildCmdOptions) deinit(args []string) error {
+	// If base image construction is enabled delete the directory for it.
+	deleteDockerBaseWorkingDirectory()
+
+	// If integration files are provided delete the maven project folder.
+	if !command.BaseImage {
+		deleteDockerWorkingDirectory()
+		deleteMavenWorkingDirectory()
+	}
+
+	return nil
+}
diff --git a/pkg/cmd/local_create.go b/pkg/cmd/local_create.go
deleted file mode 100644
index 965880161..000000000
--- a/pkg/cmd/local_create.go
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
-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 (
-	"fmt"
-
-	"github.com/pkg/errors"
-	"github.com/spf13/cobra"
-)
-
-func newCmdLocalCreate(rootCmdOptions *RootCmdOptions) (*cobra.Command, *localCreateCmdOptions) {
-	options := localCreateCmdOptions{
-		RootCmdOptions: rootCmdOptions,
-	}
-
-	cmd := cobra.Command{
-		Use:     "create [options]",
-		Short:   "Create integration images locally.",
-		Long:    `Create integration images locally for containerized integrations.`,
-		PreRunE: decode(&options),
-		RunE: func(cmd *cobra.Command, args []string) error {
-			if err := options.validate(args); err != nil {
-				return err
-			}
-			if err := options.init(args); err != nil {
-				return err
-			}
-			if err := options.run(cmd, args); err != nil {
-				fmt.Println(err.Error())
-			}
-			if err := options.deinit(args); err != nil {
-				return err
-			}
-
-			return nil
-		},
-		Annotations: map[string]string{
-			offlineCommandLabel: "true",
-		},
-	}
-
-	cmd.Flags().Bool("base-image", false, "Create base image used as a starting point for any integration.")
-	cmd.Flags().String("container-registry", "", "Registry that holds intermediate images.")
-	cmd.Flags().String("image", "", "Full path to integration image including registry.")
-	cmd.Flags().StringArray("property-file", nil, "Add a property file to the integration.")
-	cmd.Flags().StringArrayP("property", "p", nil, "Add a Camel property to the integration.")
-	cmd.Flags().StringArrayP("dependency", "d", nil, "Add an additional dependency")
-	cmd.Flags().StringArray("maven-repository", nil, "Use a maven repository")
-
-	return &cmd, &options
-}
-
-type localCreateCmdOptions struct {
-	*RootCmdOptions
-	BaseImage              bool     `mapstructure:"base-image"`
-	ContainerRegistry      string   `mapstructure:"container-registry"`
-	Image                  string   `mapstructure:"image"`
-	AdditionalDependencies []string `mapstructure:"dependencies"`
-	Properties             []string `mapstructure:"properties"`
-	PropertyFiles          []string `mapstructure:"property-files"`
-	MavenRepositories      []string `mapstructure:"maven-repositories"`
-}
-
-func (command *localCreateCmdOptions) validate(args []string) error {
-	// Validate integration files.
-	if len(args) > 0 {
-		err := validateIntegrationFiles(args)
-		if err != nil {
-			return err
-		}
-
-		if command.ContainerRegistry != "" {
-			return errors.New("cannot specify container registry when building integration image")
-		}
-
-		if command.Image == "" {
-			return errors.New("image path not provided for integration")
-		}
-	}
-
-	// Validate additional dependencies specified by the user.
-	err := validateAdditionalDependencies(command.AdditionalDependencies)
-	if err != nil {
-		return err
-	}
-
-	// Validate properties file.
-	err = validateFiles(command.PropertyFiles)
-	if err != nil {
-		return nil
-	}
-
-	// Docker registry must be set.
-	if command.BaseImage && command.ContainerRegistry == "" {
-		return errors.New("base image cannot be created as registry has not been provided")
-	}
-
-	return nil
-}
-
-func (command *localCreateCmdOptions) init(args []string) error {
-	// If base image construction is enabled create a directory for it.
-	err := createDockerBaseWorkingDirectory()
-	if err != nil {
-		return err
-	}
-
-	// If integration files are provided an integration image will be built.
-	if !command.BaseImage {
-		err := createDockerWorkingDirectory()
-		if err != nil {
-			return err
-		}
-
-		err = createMavenWorkingDirectory()
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func (command *localCreateCmdOptions) run(cmd *cobra.Command, args []string) error {
-	dependenciesList := []string{}
-	propertyFilesList := []string{}
-	if !command.BaseImage {
-		// Fetch dependencies.
-		dependencies, err := getDependencies(args, command.AdditionalDependencies, command.MavenRepositories, true)
-		if err != nil {
-			return err
-		}
-		dependenciesList = dependencies
-
-		// Manage integration properties which may come from files or CLI.
-		propertyFiles, err := updateIntegrationProperties(command.Properties, command.PropertyFiles)
-		if err != nil {
-			return err
-		}
-		propertyFilesList = propertyFiles
-	}
-
-	// Create and build integration image.
-	err := createAndBuildIntegrationImage(command.Context, command.ContainerRegistry, command.BaseImage,
-		command.Image, propertyFilesList, dependenciesList, args, cmd.OutOrStdout(), cmd.ErrOrStderr())
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (command *localCreateCmdOptions) deinit(args []string) error {
-	// If base image construction is enabled delete the directory for it.
-	deleteDockerBaseWorkingDirectory()
-
-	// If integration files are provided delete the maven project folder.
-	if !command.BaseImage {
-		deleteDockerWorkingDirectory()
-		deleteMavenWorkingDirectory()
-	}
-
-	return nil
-}
diff --git a/pkg/cmd/inspect.go b/pkg/cmd/local_inspect.go
similarity index 87%
rename from pkg/cmd/inspect.go
rename to pkg/cmd/local_inspect.go
index 3687879b7..c225a77f3 100644
--- a/pkg/cmd/inspect.go
+++ b/pkg/cmd/local_inspect.go
@@ -23,8 +23,8 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func newCmdInspect(rootCmdOptions *RootCmdOptions) (*cobra.Command, *inspectCmdOptions) {
-	options := inspectCmdOptions{
+func newCmdLocalInspect(rootCmdOptions *RootCmdOptions) (*cobra.Command, *localInspectCmdOptions) {
+	options := localInspectCmdOptions{
 		RootCmdOptions: rootCmdOptions,
 	}
 
@@ -64,7 +64,7 @@ will be generated by calling Maven and then printed in the selected output forma
 	return &cmd, &options
 }
 
-type inspectCmdOptions struct {
+type localInspectCmdOptions struct {
 	*RootCmdOptions
 	AllDependencies        bool     `mapstructure:"all-dependencies"`
 	OutputFormat           string   `mapstructure:"output"`
@@ -72,7 +72,7 @@ type inspectCmdOptions struct {
 	MavenRepositories      []string `mapstructure:"maven-repositories"`
 }
 
-func (command *inspectCmdOptions) validate(args []string) error {
+func (command *localInspectCmdOptions) validate(args []string) error {
 	// Validate integration files.
 	err := validateIntegrationFiles(args)
 	if err != nil {
@@ -88,11 +88,11 @@ func (command *inspectCmdOptions) validate(args []string) error {
 	return nil
 }
 
-func (command *inspectCmdOptions) init() error {
+func (command *localInspectCmdOptions) init() error {
 	return createMavenWorkingDirectory()
 }
 
-func (command *inspectCmdOptions) run(args []string) error {
+func (command *localInspectCmdOptions) run(args []string) error {
 	// Fetch dependencies.
 	dependencies, err := getDependencies(args, command.AdditionalDependencies, command.MavenRepositories, command.AllDependencies)
 	if err != nil {
@@ -108,6 +108,6 @@ func (command *inspectCmdOptions) run(args []string) error {
 	return nil
 }
 
-func (command *inspectCmdOptions) deinit() error {
+func (command *localInspectCmdOptions) deinit() error {
 	return deleteMavenWorkingDirectory()
 }
diff --git a/pkg/cmd/local_run.go b/pkg/cmd/local_run.go
index ca5ade519..64ada02d7 100644
--- a/pkg/cmd/local_run.go
+++ b/pkg/cmd/local_run.go
@@ -20,6 +20,7 @@ package cmd
 import (
 	"fmt"
 
+	"github.com/apache/camel-k/pkg/util"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 )
@@ -58,6 +59,7 @@ func newCmdLocalRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *localRunCm
 	cmd.Flags().Bool("containerize", false, "Run integration in a local container.")
 	cmd.Flags().String("image", "", "Full path to integration image including registry.")
 	cmd.Flags().String("network", "", "Custom network name to be used by the underlying Docker command.")
+	cmd.Flags().String("integration-directory", "", "Directory which holds the locally built integration and is the result of a local build action.")
 	cmd.Flags().StringArrayP("env", "e", nil, "Flag to specify an environment variable [--env VARIABLE=value].")
 	cmd.Flags().StringArray("property-file", nil, "Add a property file to the integration.")
 	cmd.Flags().StringArrayP("property", "p", nil, "Add a Camel property to the integration.")
@@ -72,6 +74,7 @@ type localRunCmdOptions struct {
 	Containerize           bool     `mapstructure:"containerize"`
 	Image                  string   `mapstructure:"image"`
 	Network                string   `mapstructure:"network"`
+	IntegrationDirectory   string   `mapstructure:"integration-directory"`
 	EnvironmentVariables   []string `mapstructure:"envs"`
 	PropertyFiles          []string `mapstructure:"property-files"`
 	Properties             []string `mapstructure:"properties"`
@@ -80,8 +83,9 @@ type localRunCmdOptions struct {
 }
 
 func (command *localRunCmdOptions) validate(args []string) error {
-	// Validate integration files.
-	if command.Image == "" || command.Containerize {
+	// Validate integration files when no image is provided and we are
+	// not running an already locally-built integration.
+	if command.Image == "" && command.IntegrationDirectory == "" {
 		err := validateIntegrationFiles(args)
 		if err != nil {
 			return err
@@ -140,22 +144,53 @@ func (command *localRunCmdOptions) run(cmd *cobra.Command, args []string) error
 		return nil
 	}
 
-	// Fetch dependencies.
-	dependencies, err := getDependencies(args, command.AdditionalDependencies, command.MavenRepositories, true)
-	if err != nil {
-		return err
+	hasIntegrationDir := command.IntegrationDirectory != ""
+
+	dependencies := []string{}
+	if hasIntegrationDir {
+		localBuildDependencies, err := getLocalBuildDependencies(command.IntegrationDirectory)
+		if err != nil {
+			return err
+		}
+		dependencies = localBuildDependencies
+	} else {
+		// Fetch dependencies.
+		computedDependencies, err := getDependencies(args, command.AdditionalDependencies, command.MavenRepositories, true)
+		if err != nil {
+			return err
+		}
+		dependencies = computedDependencies
 	}
 
 	// Manage integration properties which may come from files or CLI.
-	propertyFiles, err := updateIntegrationProperties(command.Properties, command.PropertyFiles)
+	propertyFiles := command.PropertyFiles
+	if hasIntegrationDir {
+		localBuildPropertyFiles, err := getLocalBuildProperties(command.IntegrationDirectory)
+		if err != nil {
+			return err
+		}
+		propertyFiles = localBuildPropertyFiles
+	}
+
+	updatedPropertyFiles, err := updateIntegrationProperties(command.Properties, propertyFiles, hasIntegrationDir)
 	if err != nil {
 		return err
 	}
+	propertyFiles = updatedPropertyFiles
+
+	routes := args
+	if hasIntegrationDir {
+		localBuildRoutes, err := getLocalBuildRoutes(command.IntegrationDirectory)
+		if err != nil {
+			return err
+		}
+		routes = localBuildRoutes
+	}
 
 	// If this is a containerized local run, create, build and run the container image.
 	if command.Containerize {
 		// Create and build integration image.
-		err = createAndBuildIntegrationImage(command.Context, "", false, command.Image, propertyFiles, dependencies, args, cmd.OutOrStdout(), cmd.ErrOrStderr())
+		err := createAndBuildIntegrationImage(command.Context, "", false, command.Image, propertyFiles, dependencies, routes, cmd.OutOrStdout(), cmd.ErrOrStderr())
 		if err != nil {
 			return err
 		}
@@ -166,8 +201,13 @@ func (command *localRunCmdOptions) run(cmd *cobra.Command, args []string) error
 			return err
 		}
 	} else {
+		propertieDir := util.GetLocalPropertiesDir()
+		if hasIntegrationDir {
+			propertieDir = getCustomPropertiesDir(command.IntegrationDirectory)
+		}
+
 		// Run integration locally.
-		err = RunLocalIntegrationRunCommand(command.Context, propertyFiles, dependencies, args, cmd.OutOrStdout(), cmd.ErrOrStderr())
+		err := RunLocalIntegrationRunCommand(command.Context, propertyFiles, dependencies, routes, propertieDir, cmd.OutOrStdout(), cmd.ErrOrStderr())
 		if err != nil {
 			return err
 		}
diff --git a/pkg/cmd/modeline.go b/pkg/cmd/modeline.go
index 99711e55d..d6fe533a8 100644
--- a/pkg/cmd/modeline.go
+++ b/pkg/cmd/modeline.go
@@ -33,7 +33,7 @@ import (
 
 const (
 	runCmdName        = "run"
-	createCmdName     = "create"
+	buildCmdName      = "build"
 	localCmdName      = "local"
 	runCmdSourcesArgs = "source"
 	inspectCmdName    = "inspect"
@@ -90,10 +90,10 @@ func createKamelWithModelineCommand(ctx context.Context, args []string) (*cobra.
 		return nil, nil, err
 	}
 
-	isLocalCreate := target.Name() == createCmdName && target.Parent().Name() == localCmdName
+	isLocalBuild := target.Name() == buildCmdName && target.Parent().Name() == localCmdName
 	isInspect := target.Name() == inspectCmdName
 
-	if target.Name() != runCmdName && !isLocalCreate && !isInspect {
+	if target.Name() != runCmdName && !isLocalBuild && !isInspect {
 		return rootCmd, args, nil
 	}
 
diff --git a/pkg/cmd/operator.go b/pkg/cmd/operator.go
index 276a1d425..f3d758e06 100644
--- a/pkg/cmd/operator.go
+++ b/pkg/cmd/operator.go
@@ -23,9 +23,10 @@ import (
 	"github.com/apache/camel-k/pkg/cmd/operator"
 )
 
+const operatorCommand = "operator"
+
 func newCmdOperator() (*cobra.Command, *operatorCmdOptions) {
-	options := operatorCmdOptions{
-	}
+	options := operatorCmdOptions{}
 
 	cmd := cobra.Command{
 		Use:     "operator",
diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go
index 0f10dc679..b08f580f5 100644
--- a/pkg/cmd/root.go
+++ b/pkg/cmd/root.go
@@ -145,7 +145,6 @@ func addKamelSubcommands(cmd *cobra.Command, options *RootCmdOptions) {
 	cmd.AddCommand(cmdOnly(newCmdBuilder(options)))
 	cmd.AddCommand(cmdOnly(newCmdInit(options)))
 	cmd.AddCommand(cmdOnly(newCmdDebug(options)))
-	cmd.AddCommand(cmdOnly(newCmdInspect(options)))
 	cmd.AddCommand(newCmdLocal(options))
 }
 
@@ -189,10 +188,10 @@ func (command *RootCmdOptions) preRun(cmd *cobra.Command, _ []string) error {
 		// Check that the Kamel CLI matches that of the operator.
 		// The check relies on the version reported in the IntegrationPlatform status,
 		// which requires the operator is running and the IntegrationPlatform resource
-		// reconciled. Hence the compatibility check is skipped for the install command.
+		// reconciled. Hence the compatibility check is skipped for the install and the operator command.
 		// Furthermore, there can be any incompatibilities, as the install command deploys
 		// the operator version it's compatible with.
-		if cmd.Use != installCommand {
+		if cmd.Use != installCommand && cmd.Use != operatorCommand {
 			checkAndShowCompatibilityWarning(command.Context, c, command.Namespace)
 		}
 	}
diff --git a/pkg/cmd/util_commands.go b/pkg/cmd/util_commands.go
index 7e78fcdf1..a49603edb 100644
--- a/pkg/cmd/util_commands.go
+++ b/pkg/cmd/util_commands.go
@@ -103,8 +103,8 @@ func assembleIntegrationRunCommand(ctx context.Context, properties []string, dep
 }
 
 // RunLocalIntegrationRunCommand --
-func RunLocalIntegrationRunCommand(ctx context.Context, properties []string, dependencies []string, routes []string, stdout, stderr io.Writer) error {
-	cmd, err := assembleIntegrationRunCommand(ctx, properties, dependencies, routes, util.GetLocalPropertiesDir(), stdout, stderr, true)
+func RunLocalIntegrationRunCommand(ctx context.Context, properties []string, dependencies []string, routes []string, propertiesDir string, stdout, stderr io.Writer) error {
+	cmd, err := assembleIntegrationRunCommand(ctx, properties, dependencies, routes, propertiesDir, stdout, stderr, true)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/cmd/util_dependencies.go b/pkg/cmd/util_dependencies.go
index f987aa75c..4db062e20 100644
--- a/pkg/cmd/util_dependencies.go
+++ b/pkg/cmd/util_dependencies.go
@@ -172,6 +172,49 @@ func getTransitiveDependencies(
 	return transitiveDependencies, nil
 }
 
+func getRegularFilesInDir(directory string) ([]string, error) {
+	dirFiles := []string{}
+	files, err := ioutil.ReadDir(directory)
+	for _, file := range files {
+		fileName := file.Name()
+
+		// Do not include hidden files or sub-directories.
+		if !file.IsDir() && !strings.HasPrefix(fileName, ".") {
+			dirFiles = append(dirFiles, path.Join(directory, fileName))
+		}
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	return dirFiles, nil
+}
+
+func getLocalBuildDependencies(integrationDirectory string) ([]string, error) {
+	locallyBuiltDependencies, err := getRegularFilesInDir(getCustomDependenciesDir(integrationDirectory))
+	if err != nil {
+		return nil, err
+	}
+	return locallyBuiltDependencies, nil
+}
+
+func getLocalBuildProperties(integrationDirectory string) ([]string, error) {
+	locallyBuiltProperties, err := getRegularFilesInDir(getCustomPropertiesDir(integrationDirectory))
+	if err != nil {
+		return nil, err
+	}
+	return locallyBuiltProperties, nil
+}
+
+func getLocalBuildRoutes(integrationDirectory string) ([]string, error) {
+	locallyBuiltRoutes, err := getRegularFilesInDir(getCustomRoutesDir(integrationDirectory))
+	if err != nil {
+		return nil, err
+	}
+	return locallyBuiltRoutes, nil
+}
+
 func generateCatalog() (*camel.RuntimeCatalog, error) {
 	// A Camel catalog is required for this operation
 	settings := ""
@@ -329,7 +372,7 @@ func validatePropertyFiles(propertyFiles []string) error {
 	return nil
 }
 
-func updateIntegrationProperties(properties []string, propertyFiles []string) ([]string, error) {
+func updateIntegrationProperties(properties []string, propertyFiles []string, hasIntegrationDir bool) ([]string, error) {
 	// Create properties directory under Maven working directory.
 	// This ensures that property files of different integrations do not clash.
 	err := util.CreateLocalPropertiesDirectory()
@@ -345,14 +388,16 @@ func updateIntegrationProperties(properties []string, propertyFiles []string) ([
 		relocatedPropertyFiles = append(relocatedPropertyFiles, relocatedPropertyFile)
 	}
 
-	// Output list of properties to property file if any CLI properties were given.
-	if len(properties) > 0 {
-		propertyFilePath := path.Join(util.GetLocalPropertiesDir(), "CLI.properties")
-		err = ioutil.WriteFile(propertyFilePath, []byte(strings.Join(properties, "\n")), 0777)
-		if err != nil {
-			return nil, err
+	if !hasIntegrationDir {
+		// Output list of properties to property file if any CLI properties were given.
+		if len(properties) > 0 {
+			propertyFilePath := path.Join(util.GetLocalPropertiesDir(), "CLI.properties")
+			err = ioutil.WriteFile(propertyFilePath, []byte(strings.Join(properties, "\n")), 0777)
+			if err != nil {
+				return nil, err
+			}
+			relocatedPropertyFiles = append(relocatedPropertyFiles, propertyFilePath)
 		}
-		relocatedPropertyFiles = append(relocatedPropertyFiles, propertyFilePath)
 	}
 
 	return relocatedPropertyFiles, nil
@@ -406,3 +451,15 @@ func deleteMavenWorkingDirectory() error {
 
 	return nil
 }
+
+func getCustomDependenciesDir(integrationDirectory string) string {
+	return path.Join(integrationDirectory, "dependencies")
+}
+
+func getCustomPropertiesDir(integrationDirectory string) string {
+	return path.Join(integrationDirectory, "properties")
+}
+
+func getCustomRoutesDir(integrationDirectory string) string {
+	return path.Join(integrationDirectory, "routes")
+}
diff --git a/pkg/trait/deployer.go b/pkg/trait/deployer.go
index 062b91d7e..a9a11d7b7 100644
--- a/pkg/trait/deployer.go
+++ b/pkg/trait/deployer.go
@@ -18,13 +18,17 @@ limitations under the License.
 package trait
 
 import (
+	"net/http"
+
 	"github.com/pkg/errors"
+
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
-	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/apache/camel-k/pkg/util/patch"
 )
 
@@ -40,6 +44,8 @@ type deployerTrait struct {
 
 var _ ControllerStrategySelector = &deployerTrait{}
 
+var hasServerSideApply = true
+
 func newDeployerTrait() Trait {
 	return &deployerTrait{
 		BaseTrait: NewBaseTrait("deployer", 900),
@@ -62,15 +68,6 @@ func (t *deployerTrait) Configure(e *Environment) (bool, error) {
 func (t *deployerTrait) Apply(e *Environment) error {
 	switch e.Integration.Status.Phase {
 
-	case v1.IntegrationPhaseNone, v1.IntegrationPhaseWaitingForPlatform, v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying:
-		// Register a post action that updates the resources generated by the traits
-		e.PostActions = append(e.PostActions, func(env *Environment) error {
-			if err := kubernetes.ReplaceResources(env.C, env.Client, env.Resources.Items()); err != nil {
-				return errors.Wrap(err, "error during replace resource")
-			}
-			return nil
-		})
-
 	case v1.IntegrationPhaseBuildingKit, v1.IntegrationPhaseResolvingKit:
 		if e.IntegrationKitInPhase(v1.IntegrationKitPhaseReady) {
 			e.PostProcessors = append(e.PostProcessors, func(environment *Environment) error {
@@ -80,39 +77,30 @@ func (t *deployerTrait) Apply(e *Environment) error {
 			})
 		}
 
-	case v1.IntegrationPhaseRunning, v1.IntegrationPhaseWaitingForBindings:
+	case v1.IntegrationPhaseNone, v1.IntegrationPhaseInitialization,
+		v1.IntegrationPhaseWaitingForPlatform, v1.IntegrationPhaseWaitingForBindings,
+		v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning:
 		// Register a post action that patches the resources generated by the traits
 		e.PostActions = append(e.PostActions, func(env *Environment) error {
 			for _, resource := range env.Resources.Items() {
-				key, err := client.ObjectKeyFromObject(resource)
-				if err != nil {
-					return err
-				}
-
-				object := resource.DeepCopyObject()
-				err = env.Client.Get(env.C, key, object)
-				if err != nil {
-					return err
-				}
-
-				// If both objects have "ObjectMeta" and "Spec" fields and they contain all the expected fields
-				// (plus optional others), then avoid patching.
-				if !patch.ObjectMetaEqualDeepDerivative(object, resource) ||
-					!patch.SpecEqualDeepDerivative(object, resource) {
-
-					p, err := patch.PositiveMergePatch(object, resource)
-					if err != nil {
-						return err
-					} else if len(p) == 0 {
-						// Avoid triggering a patch request for nothing
+				// We assume that server-side apply is enabled by default.
+				// It is currently convoluted to check pro-actively whether server-side apply
+				// is enabled. This is possible to fetch the OpenAPI endpoint, which returns
+				// the entire server API document, then lookup the resource PATCH endpoint, and
+				// check its list of accepted MIME types.
+				// As a simpler solution, we fallback to client-side apply at the first
+				// 415 error, and assume server-side apply is not available globally.
+				if hasServerSideApply {
+					if err := t.serverSideApply(env, resource); err == nil {
 						continue
-					}
-
-					err = env.Client.Patch(env.C, resource, client.RawPatch(types.MergePatchType, p))
-					if err != nil {
-						return errors.Wrap(err, "error during patch resource")
+					} else if isIncompatibleServerError(err) {
+						t.L.Info("Fallback to client-side apply to patch resources")
+						hasServerSideApply = false
 					}
 				}
+				if err := t.clientSideApply(env, resource); err != nil {
+					return err
+				}
 			}
 			return nil
 		})
@@ -121,6 +109,58 @@ func (t *deployerTrait) Apply(e *Environment) error {
 	return nil
 }
 
+func (t *deployerTrait) serverSideApply(env *Environment, resource runtime.Object) error {
+	target, err := patch.PositiveApplyPatch(resource)
+	if err != nil {
+		return err
+	}
+	err = env.Client.Patch(env.C, target, client.Apply, client.ForceOwnership, client.FieldOwner("camel-k-operator"))
+	if err != nil {
+		return errors.Wrapf(err, "error during apply resource: %v", resource)
+	}
+	return nil
+}
+
+func (t *deployerTrait) clientSideApply(env *Environment, resource runtime.Object) error {
+	err := env.Client.Create(env.C, resource)
+	if err == nil {
+		return nil
+	} else if !k8serrors.IsAlreadyExists(err) {
+		return errors.Wrapf(err, "error during create resource: %v", resource)
+	}
+	key, err := client.ObjectKeyFromObject(resource)
+	if err != nil {
+		return err
+	}
+	object := resource.DeepCopyObject()
+	err = env.Client.Get(env.C, key, object)
+	if err != nil {
+		return err
+	}
+	p, err := patch.PositiveMergePatch(object, resource)
+	if err != nil {
+		return err
+	} else if len(p) == 0 {
+		// Avoid triggering a patch request for nothing
+		return nil
+	}
+	err = env.Client.Patch(env.C, resource, client.RawPatch(types.MergePatchType, p))
+	if err != nil {
+		return errors.Wrapf(err, "error during patch resource: %v", resource)
+	}
+	return nil
+}
+
+func isIncompatibleServerError(err error) bool {
+	// 415: Unsupported media type means we're talking to a server which doesn't
+	// support server-side apply.
+	if _, ok := err.(*k8serrors.StatusError); !ok {
+		// Non-StatusError means the error isn't because the server is incompatible.
+		return false
+	}
+	return err.(*k8serrors.StatusError).Status().Code == http.StatusUnsupportedMediaType
+}
+
 func (t *deployerTrait) SelectControllerStrategy(e *Environment) (*ControllerStrategy, error) {
 	if t.Enabled != nil && !*t.Enabled {
 		return nil, nil
diff --git a/pkg/trait/openapi.go b/pkg/trait/openapi.go
index 99d94bc0e..ba23835d5 100644
--- a/pkg/trait/openapi.go
+++ b/pkg/trait/openapi.go
@@ -26,18 +26,18 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/apache/camel-k/pkg/util"
-	"github.com/apache/camel-k/pkg/util/digest"
 	"github.com/pkg/errors"
-	k8serrors "k8s.io/apimachinery/pkg/api/errors"
-	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	corev1 "k8s.io/api/core/v1"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/util"
 	"github.com/apache/camel-k/pkg/util/defaults"
+	"github.com/apache/camel-k/pkg/util/digest"
 	"github.com/apache/camel-k/pkg/util/gzip"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/apache/camel-k/pkg/util/maven"
@@ -171,6 +171,8 @@ func (t *openAPITrait) generateOpenAPIConfigMap(e *Environment, resource v1.Reso
 		// ConfigMap already exists and matches the source
 		// Re-adding it to update its revision
 		cm.ResourceVersion = ""
+		// Clear the managed fields to support server-side apply
+		cm.ManagedFields = nil
 		e.Resources.Add(&cm)
 		return nil
 	}
diff --git a/pkg/trait/toleration.go b/pkg/trait/toleration.go
new file mode 100644
index 000000000..700b1ea98
--- /dev/null
+++ b/pkg/trait/toleration.go
@@ -0,0 +1,153 @@
+/*
+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 trait
+
+import (
+	"fmt"
+	"regexp"
+	"strconv"
+
+	appsv1 "k8s.io/api/apps/v1"
+	"k8s.io/api/batch/v1beta1"
+	corev1 "k8s.io/api/core/v1"
+	serving "knative.dev/serving/pkg/apis/serving/v1"
+
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/util"
+)
+
+// This trait sets Tolerations over Integration pods. Tolerations allow (but do not require) the pods to schedule onto nodes with matching taints.
+// See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ for more details.
+//
+// The toleration should be expressed in a similar manner of taints *_Key[=Value]:Effect[:Seconds]_* where values in square brackets are optional. Examples:
+//
+// node-role.kubernetes.io/master:NoSchedule
+// node.kubernetes.io/network-unavailable:NoExecute:3000
+// disktype=ssd:PreferNoSchedule
+//
+// It's disabled by default.
+//
+// +camel-k:trait=toleration
+type tolerationTrait struct {
+	BaseTrait `property:",squash"`
+	// The taint to tolerate in the form Key[=Value]:Effect[:Seconds]
+	Taints []string `property:"taints" json:"taints,omitempty"`
+}
+
+var validTaintRegexp = regexp.MustCompile(`^([\w\/_\-\.]+)(=)?([\w_\-\.]+)?:(NoSchedule|NoExecute|PreferNoSchedule):?(\d*)?$`)
+
+func newTolerationTrait() Trait {
+	return &tolerationTrait{
+		BaseTrait: NewBaseTrait("toleration", 1200),
+	}
+}
+
+func (t *tolerationTrait) Configure(e *Environment) (bool, error) {
+	if util.IsNilOrFalse(t.Enabled) {
+		return false, nil
+	}
+
+	if len(t.Taints) == 0 {
+		return false, fmt.Errorf("no taint was provided")
+	}
+
+	return e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning), nil
+}
+
+func (t *tolerationTrait) Apply(e *Environment) (err error) {
+	tolerations, err := t.getTolerations()
+	if err != nil {
+		return err
+	}
+	var specTolerations *[]corev1.Toleration
+	found := false
+
+	// Deployment
+	deployment := e.Resources.GetDeployment(func(d *appsv1.Deployment) bool {
+		return d.Name == e.Integration.Name
+	})
+	if deployment != nil {
+		specTolerations = &deployment.Spec.Template.Spec.Tolerations
+		found = true
+	}
+
+	// Knative service
+	if !found {
+		knativeService := e.Resources.GetKnativeService(func(s *serving.Service) bool {
+			return s.Name == e.Integration.Name
+		})
+		if knativeService != nil {
+			specTolerations = &knativeService.Spec.Template.Spec.Tolerations
+			found = true
+		}
+	}
+
+	// Cronjob
+	if !found {
+		cronJob := e.Resources.GetCronJob(func(c *v1beta1.CronJob) bool {
+			return c.Name == e.Integration.Name
+		})
+		if cronJob != nil {
+			specTolerations = &cronJob.Spec.JobTemplate.Spec.Template.Spec.Tolerations
+			found = true
+		}
+	}
+
+	// Add the toleration
+	if found {
+		if *specTolerations == nil {
+			*specTolerations = make([]corev1.Toleration, 0)
+		}
+		*specTolerations = append(*specTolerations, tolerations...)
+	}
+
+	return nil
+}
+
+func (t *tolerationTrait) getTolerations() ([]corev1.Toleration, error) {
+	tolerations := make([]corev1.Toleration, 0)
+	for _, t := range t.Taints {
+		if !validTaintRegexp.MatchString(t) {
+			return nil, fmt.Errorf("could not match taint %v", t)
+		}
+		toleration := corev1.Toleration{}
+		// Parse the regexp groups
+		groups := validTaintRegexp.FindStringSubmatch(t)
+		toleration.Key = groups[1]
+		if groups[2] != "" {
+			toleration.Operator = corev1.TolerationOpEqual
+		} else {
+			toleration.Operator = corev1.TolerationOpExists
+		}
+		if groups[3] != "" {
+			toleration.Value = groups[3]
+		}
+		toleration.Effect = corev1.TaintEffect(groups[4])
+
+		if groups[5] != "" {
+			tolerationSeconds, err := strconv.ParseInt(groups[5], 10, 64)
+			if err != nil {
+				return nil, err
+			}
+			toleration.TolerationSeconds = &tolerationSeconds
+		}
+		tolerations = append(tolerations, toleration)
+	}
+
+	return tolerations, nil
+}
diff --git a/pkg/trait/trait_register.go b/pkg/trait/trait_register.go
index 1ec4e3127..4c06b3790 100644
--- a/pkg/trait/trait_register.go
+++ b/pkg/trait/trait_register.go
@@ -35,6 +35,7 @@ func init() {
 	AddToTraits(newDeploymentTrait)
 	AddToTraits(newGarbageCollectorTrait)
 	AddToTraits(newAffinityTrait)
+	AddToTraits(newTolerationTrait)
 	AddToTraits(newKnativeServiceTrait)
 	AddToTraits(newServiceTrait)
 	AddToTraits(newContainerTrait)
diff --git a/pkg/util/kubernetes/replace.go b/pkg/util/kubernetes/replace.go
index 55c8dcf78..a9f045d2f 100644
--- a/pkg/util/kubernetes/replace.go
+++ b/pkg/util/kubernetes/replace.go
@@ -20,27 +20,21 @@ package kubernetes
 import (
 	"context"
 
-	"github.com/apache/camel-k/pkg/client"
-	routev1 "github.com/openshift/api/route/v1"
 	"github.com/pkg/errors"
+
 	corev1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
-	serving "knative.dev/serving/pkg/apis/serving/v1"
+
 	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
-)
 
-// ReplaceResources allows to completely replace a list of resources on Kubernetes, taking care of immutable fields and resource versions
-func ReplaceResources(ctx context.Context, c client.Client, objects []runtime.Object) error {
-	for _, object := range objects {
-		err := ReplaceResource(ctx, c, object)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
+	serving "knative.dev/serving/pkg/apis/serving/v1"
+
+	routev1 "github.com/openshift/api/route/v1"
+
+	"github.com/apache/camel-k/pkg/client"
+)
 
 // ReplaceResource allows to completely replace a resource on Kubernetes, taking care of immutable fields and resource versions
 func ReplaceResource(ctx context.Context, c client.Client, res runtime.Object) error {
diff --git a/pkg/util/patch/patch.go b/pkg/util/patch/patch.go
index e2c54b1e4..09c426542 100644
--- a/pkg/util/patch/patch.go
+++ b/pkg/util/patch/patch.go
@@ -21,7 +21,8 @@ import (
 	"reflect"
 
 	jsonpatch "github.com/evanphx/json-patch"
-	"k8s.io/apimachinery/pkg/api/equality"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/json"
@@ -63,6 +64,26 @@ func PositiveMergePatch(source runtime.Object, target runtime.Object) ([]byte, e
 	return json.Marshal(positivePatch)
 }
 
+func PositiveApplyPatch(source runtime.Object) (runtime.Object, error) {
+	sourceJSON, err := json.Marshal(source)
+	if err != nil {
+		return nil, err
+	}
+
+	var positivePatch map[string]interface{}
+	err = json.Unmarshal(sourceJSON, &positivePatch)
+	if err != nil {
+		return nil, err
+	}
+
+	// The following is a work-around to remove null fields from the apply patch,
+	// so that ownership is not taken for non-managed fields.
+	// See https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2155-clientgo-apply
+	removeNilValues(reflect.ValueOf(positivePatch), reflect.Value{})
+
+	return &unstructured.Unstructured{Object: positivePatch}, nil
+}
+
 func removeNilValues(v reflect.Value, parent reflect.Value) {
 	for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
 		v = v.Elem()
@@ -90,41 +111,3 @@ func removeNilValues(v reflect.Value, parent reflect.Value) {
 		}
 	}
 }
-
-func ObjectMetaEqualDeepDerivative(object runtime.Object, expected runtime.Object) (res bool) {
-	defer func() {
-		if r := recover(); r != nil {
-			res = false
-		}
-	}()
-
-	if expected == nil {
-		return true
-	} else if object == nil {
-		return false
-	}
-
-	objectMeta := reflect.ValueOf(object).Elem().FieldByName("ObjectMeta").Interface()
-	expectedMeta := reflect.ValueOf(expected).Elem().FieldByName("ObjectMeta").Interface()
-
-	return equality.Semantic.DeepDerivative(expectedMeta, objectMeta)
-}
-
-func SpecEqualDeepDerivative(object runtime.Object, expected runtime.Object) (res bool) {
-	defer func() {
-		if r := recover(); r != nil {
-			res = false
-		}
-	}()
-
-	if expected == nil {
-		return true
-	} else if object == nil {
-		return false
-	}
-
-	objectSpec := reflect.ValueOf(object).Elem().FieldByName("Spec").Interface()
-	expectedSpec := reflect.ValueOf(expected).Elem().FieldByName("Spec").Interface()
-
-	return equality.Semantic.DeepDerivative(expectedSpec, objectSpec)
-}
diff --git a/pkg/util/util.go b/pkg/util/util.go
index 8d091cf76..b6f1cff06 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -290,17 +290,19 @@ func DirectoryExists(directory string) (bool, error) {
 
 // CreateDirectory --
 func CreateDirectory(directory string) error {
-	// If directory does not exist, create it.
-	directoryExists, err := DirectoryExists(directory)
-	if err != nil {
-		return err
-	}
-
-	if !directoryExists {
-		err := os.MkdirAll(directory, 0777)
+	if directory != "" {
+		// If directory does not exist, create it.
+		directoryExists, err := DirectoryExists(directory)
 		if err != nil {
 			return err
 		}
+
+		if !directoryExists {
+			err := os.MkdirAll(directory, 0777)
+			if err != nil {
+				return err
+			}
+		}
 	}
 
 	return nil
@@ -538,3 +540,22 @@ func EvaluateCLIAndLazyEnvVars() ([]string, error) {
 
 	return evaluatedEnvVars, nil
 }
+
+// CopyIntegrationFilesToDirectory --
+func CopyIntegrationFilesToDirectory(files []string, directory string) ([]string, error) {
+	// Create directory if one does not already exist.
+	err := CreateDirectory(directory)
+	if err != nil {
+		return nil, err
+	}
+
+	// Coopy files to new location. Also create the list with relocated files.
+	relocatedFilesList := []string{}
+	for _, filePath := range files {
+		newFilePath := path.Join(directory, path.Base(filePath))
+		CopyFile(filePath, newFilePath)
+		relocatedFilesList = append(relocatedFilesList, newFilePath)
+	}
+
+	return relocatedFilesList, nil
+}


Test patch:
diff --git a/e2e/common/run_test.go b/e2e/common/run_test.go
index 78e60a23b..de43ee6b2 100644
--- a/e2e/common/run_test.go
+++ b/e2e/common/run_test.go
@@ -24,13 +24,15 @@ package common
 import (
 	"testing"
 
-	. "github.com/apache/camel-k/e2e/support"
-	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	. "github.com/onsi/gomega"
+
 	v1 "k8s.io/api/core/v1"
+
+	. "github.com/apache/camel-k/e2e/support"
+	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 )
 
-func TestRunExamplesFromGitHUB(t *testing.T) {
+func TestRunExamplesFromGitHub(t *testing.T) {
 	WithNewTestNamespace(t, func(ns string) {
 		Expect(Kamel("install", "-n", ns).Execute()).Should(BeNil())
 
@@ -73,5 +75,8 @@ func TestRunExamplesFromGitHUB(t *testing.T) {
 			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Tick!"))
 			Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil())
 		})
+
+		// Cleanup
+		Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil())
 	})
 }
diff --git a/e2e/common/scale_test.go b/e2e/common/scale_test.go
new file mode 100644
index 000000000..cc77db126
--- /dev/null
+++ b/e2e/common/scale_test.go
@@ -0,0 +1,111 @@
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "knative"
+
+/*
+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 (
+	"testing"
+
+	. "github.com/onsi/gomega"
+	"github.com/onsi/gomega/gstruct"
+	"github.com/stretchr/testify/assert"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"k8s.io/client-go/dynamic"
+	"k8s.io/client-go/restmapper"
+	"k8s.io/client-go/scale"
+
+	. "github.com/apache/camel-k/e2e/support"
+	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/client/camel/clientset/versioned"
+)
+
+func TestIntegrationScale(t *testing.T) {
+	WithNewTestNamespace(t, func(ns string) {
+		name := "java"
+		Expect(Kamel("install", "-n", ns).Execute()).Should(BeNil())
+		Expect(Kamel("run", "-n", ns, "files/Java.java", "--name", name).Execute()).Should(BeNil())
+		Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(v1.PodRunning))
+		Eventually(IntegrationCondition(ns, name, camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue))
+		Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+
+		t.Run("Scale integration with polymorphic client", func(t *testing.T) {
+			// Polymorphic scale client
+			groupResources, err := restmapper.GetAPIGroupResources(TestClient().Discovery())
+			assert.Nil(t, err)
+			mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
+			resolver := scale.NewDiscoveryScaleKindResolver(TestClient().Discovery())
+			scaleClient, err := scale.NewForConfig(TestClient().GetConfig(), mapper, dynamic.LegacyAPIPathResolverFunc, resolver)
+			assert.Nil(t, err)
+
+			// Patch the integration scale subresource
+			patch := "{\"spec\":{\"replicas\":2}}"
+			_, err = scaleClient.Scales(ns).Patch(TestContext, camelv1.SchemeGroupVersion.WithResource("integrations"), name, types.MergePatchType, []byte(patch), metav1.PatchOptions{})
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			// Check the Integration scale subresource Spec field
+			Eventually(IntegrationSpecReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 2)))
+			// Then check it cascades into the Deployment scale
+			Eventually(IntegrationPods(ns, name), TestTimeoutMedium).Should(HaveLen(2))
+			// Finally check it cascades into the Integration scale subresource Status field
+			Eventually(IntegrationStatusReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 2)))
+		})
+
+		t.Run("Scale integration with Camel K client", func(t *testing.T) {
+			camel, err := versioned.NewForConfig(TestClient().GetConfig())
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			// Getter
+			integrationScale, err := camel.CamelV1().Integrations(ns).GetScale(TestContext, name, metav1.GetOptions{})
+			Expect(integrationScale).ShouldNot(BeNil())
+			Expect(integrationScale.Spec.Replicas).Should(BeNumerically("==", 2))
+			Expect(integrationScale.Status.Replicas).Should(BeNumerically("==", 2))
+
+			// Setter
+			integrationScale.Spec.Replicas = 1
+			integrationScale, err = camel.CamelV1().Integrations(ns).UpdateScale(TestContext, name, integrationScale, metav1.UpdateOptions{})
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			// Check the Integration scale subresource Spec field
+			Eventually(IntegrationSpecReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 1)))
+			// Then check it cascades into the Deployment scale
+			Eventually(IntegrationPods(ns, name), TestTimeoutMedium).Should(HaveLen(1))
+			// Finally check it cascades into the Integration scale subresource Status field
+			Eventually(IntegrationStatusReplicas(ns, name), TestTimeoutShort).
+				Should(gstruct.PointTo(BeNumerically("==", 1)))
+		})
+
+		// Cleanup
+		Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil())
+	})
+}
diff --git a/e2e/common/upgrade_test.go b/e2e/common/upgrade_test.go
index c747fb95a..00d1c22e2 100644
--- a/e2e/common/upgrade_test.go
+++ b/e2e/common/upgrade_test.go
@@ -34,6 +34,7 @@ import (
 func TestPlatformUpgrade(t *testing.T) {
 	WithNewTestNamespace(t, func(ns string) {
 		Expect(Kamel("install", "-n", ns).Execute()).Should(BeNil())
+		Eventually(OperatorPod(ns)).ShouldNot(BeNil())
 		Eventually(PlatformVersion(ns)).Should(Equal(defaults.Version))
 
 		// Scale the operator down to zero
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 21a85d868..0849e7bb3 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -34,11 +34,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/google/uuid"
 	"github.com/onsi/gomega"
 	"github.com/spf13/cobra"
+
 	appsv1 "k8s.io/api/apps/v1"
 	"k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
@@ -46,6 +45,7 @@ import (
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+
 	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 	eventing "knative.dev/eventing/pkg/apis/eventing/v1beta1"
@@ -57,10 +57,12 @@ import (
 
 	"github.com/apache/camel-k/e2e/support/util"
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/client"
 	"github.com/apache/camel-k/pkg/cmd"
 	"github.com/apache/camel-k/pkg/install"
 	"github.com/apache/camel-k/pkg/util/defaults"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/apache/camel-k/pkg/util/log"
 	"github.com/apache/camel-k/pkg/util/openshift"
 
@@ -260,6 +262,16 @@ func IntegrationPodImage(ns string, name string) func() string {
 
 func IntegrationPod(ns string, name string) func() *corev1.Pod {
 	return func() *corev1.Pod {
+		pods := IntegrationPods(ns, name)()
+		if len(pods) == 0 {
+			return nil
+		}
+		return &pods[0]
+	}
+}
+
+func IntegrationPods(ns string, name string) func() []corev1.Pod {
+	return func() []corev1.Pod {
 		lst := corev1.PodList{
 			TypeMeta: metav1.TypeMeta{
 				Kind:       "Pod",
@@ -274,10 +286,27 @@ func IntegrationPod(ns string, name string) func() *corev1.Pod {
 		if err != nil {
 			panic(err)
 		}
-		if len(lst.Items) == 0 {
+		return lst.Items
+	}
+}
+
+func IntegrationSpecReplicas(ns string, name string) func() *int32 {
+	return func() *int32 {
+		it := Integration(ns, name)()
+		if it == nil {
 			return nil
 		}
-		return &lst.Items[0]
+		return it.Spec.Replicas
+	}
+}
+
+func IntegrationStatusReplicas(ns string, name string) func() *int32 {
+	return func() *int32 {
+		it := Integration(ns, name)()
+		if it == nil {
+			return nil
+		}
+		return it.Status.Replicas
 	}
 }
 
@@ -858,7 +887,7 @@ func CreateKamelPod(ns string, name string, command ...string) error {
 	pod := corev1.Pod{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "Pod",
-			APIVersion: v1.SchemeGroupVersion.String(),
+			APIVersion: corev1.SchemeGroupVersion.String(),
 		},
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: ns,
diff --git a/pkg/cmd/modeline_test.go b/pkg/cmd/modeline_test.go
index da2224a45..e748e63b9 100644
--- a/pkg/cmd/modeline_test.go
+++ b/pkg/cmd/modeline_test.go
@@ -142,10 +142,10 @@ func TestModelineInspectSimple(t *testing.T) {
 	err = ioutil.WriteFile(fileName, []byte(file), 0777)
 	assert.NoError(t, err)
 
-	cmd, flags, err := NewKamelWithModelineCommand(context.TODO(), []string{"kamel", "inspect", fileName})
+	cmd, flags, err := NewKamelWithModelineCommand(context.TODO(), []string{"kamel", "local", "inspect", fileName})
 	assert.NoError(t, err)
 	assert.NotNil(t, cmd)
-	assert.Equal(t, []string{"inspect", fileName, "--dependency=mvn:org.my:lib:1.0"}, flags)
+	assert.Equal(t, []string{"local", "inspect", fileName, "--dependency=mvn:org.my:lib:1.0"}, flags)
 }
 
 func TestModelineInspectMultipleDeps(t *testing.T) {
@@ -161,8 +161,8 @@ func TestModelineInspectMultipleDeps(t *testing.T) {
 	err = ioutil.WriteFile(fileName, []byte(file), 0777)
 	assert.NoError(t, err)
 
-	cmd, flags, err := NewKamelWithModelineCommand(context.TODO(), []string{"kamel", "inspect", fileName})
+	cmd, flags, err := NewKamelWithModelineCommand(context.TODO(), []string{"kamel", "local", "inspect", fileName})
 	assert.NoError(t, err)
 	assert.NotNil(t, cmd)
-	assert.Equal(t, []string{"inspect", fileName, "--dependency=mvn:org.my:lib:1.0", "--dependency=camel-k:camel-dep"}, flags)
+	assert.Equal(t, []string{"local", "inspect", fileName, "--dependency=mvn:org.my:lib:1.0", "--dependency=camel-k:camel-dep"}, flags)
 }
diff --git a/pkg/trait/toleration_test.go b/pkg/trait/toleration_test.go
new file mode 100644
index 000000000..b5b474135
--- /dev/null
+++ b/pkg/trait/toleration_test.go
@@ -0,0 +1,202 @@
+/*
+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 trait
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	serving "knative.dev/serving/pkg/apis/serving/v1"
+
+	appsv1 "k8s.io/api/apps/v1"
+	"k8s.io/api/batch/v1beta1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+)
+
+func TestConfigureTolerationTraitMissingTaint(t *testing.T) {
+	environment, _ := createNominalDeploymentTolerationTest()
+	tolerationTrait := createNominalTolerationTrait()
+
+	success, err := tolerationTrait.Configure(environment)
+
+	assert.Equal(t, false, success)
+	assert.NotNil(t, err)
+}
+
+func TestApplyTolerationTraitMalformedTaint(t *testing.T) {
+	environment, _ := createNominalDeploymentTolerationTest()
+	tolerationTrait := createNominalTolerationTrait()
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "my-toleration-failure")
+
+	err := tolerationTrait.Apply(environment)
+
+	assert.NotNil(t, err)
+}
+
+func TestApplyPodTolerationLabelsDefault(t *testing.T) {
+	tolerationTrait := createNominalTolerationTrait()
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "my-toleration=my-value:NoExecute")
+
+	environment, deployment := createNominalDeploymentTolerationTest()
+	testApplyPodTolerationLabelsDefault(t, tolerationTrait, environment, &deployment.Spec.Template.Spec.Tolerations)
+
+	environment, knativeService := createNominalKnativeServiceTolerationTest()
+	testApplyPodTolerationLabelsDefault(t, tolerationTrait, environment, &knativeService.Spec.Template.Spec.Tolerations)
+
+	environment, cronJob := createNominalCronJobTolerationTest()
+	testApplyPodTolerationLabelsDefault(t, tolerationTrait, environment, &cronJob.Spec.JobTemplate.Spec.Template.Spec.Tolerations)
+}
+
+func testApplyPodTolerationLabelsDefault(t *testing.T, trait *tolerationTrait, environment *Environment, tolerations *[]corev1.Toleration) {
+	err := trait.Apply(environment)
+
+	assert.Nil(t, err)
+	assert.Equal(t, 1, len(*tolerations))
+	toleration := (*tolerations)[0]
+	assert.Equal(t, "my-toleration", toleration.Key)
+	assert.Equal(t, corev1.TolerationOpEqual, toleration.Operator)
+	assert.Equal(t, "my-value", toleration.Value)
+	assert.Equal(t, corev1.TaintEffectNoExecute, toleration.Effect)
+}
+
+func TestApplyPodTolerationLabelsTolerationSeconds(t *testing.T) {
+	tolerationTrait := createNominalTolerationTrait()
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "my-toleration:NoExecute:300")
+
+	environment, deployment := createNominalDeploymentTolerationTest()
+	testApplyPodTolerationLabelsTolerationSeconds(t, tolerationTrait, environment, &deployment.Spec.Template.Spec.Tolerations)
+
+	environment, knativeService := createNominalKnativeServiceTolerationTest()
+	testApplyPodTolerationLabelsTolerationSeconds(t, tolerationTrait, environment, &knativeService.Spec.Template.Spec.Tolerations)
+
+	environment, cronJob := createNominalCronJobTolerationTest()
+	testApplyPodTolerationLabelsTolerationSeconds(t, tolerationTrait, environment, &cronJob.Spec.JobTemplate.Spec.Template.Spec.Tolerations)
+}
+
+func testApplyPodTolerationLabelsTolerationSeconds(t *testing.T, trait *tolerationTrait, environment *Environment, tolerations *[]corev1.Toleration) {
+	err := trait.Apply(environment)
+
+	assert.Nil(t, err)
+	assert.Equal(t, 1, len(*tolerations))
+	toleration := (*tolerations)[0]
+	assert.Equal(t, "my-toleration", toleration.Key)
+	assert.Equal(t, corev1.TolerationOpExists, toleration.Operator)
+	assert.Equal(t, corev1.TaintEffectNoExecute, toleration.Effect)
+	assert.Equal(t, int64(300), *toleration.TolerationSeconds)
+}
+
+func TestTolerationValidTaints(t *testing.T) {
+	environment, _ := createNominalDeploymentTolerationTest()
+	tolerationTrait := createNominalTolerationTrait()
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "my-toleration:NoExecute")
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "my-toleration:NoSchedule")
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "my-toleration:PreferNoSchedule")
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "my-toleration:PreferNoSchedule:100")
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "my-toleration=my-val:NoExecute")
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "my-toleration=my-val:NoExecute:120")
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "org.apache.camel/my-toleration:NoExecute")
+	tolerationTrait.Taints = append(tolerationTrait.Taints, "org.apache.camel/my-toleration=val:NoExecute")
+
+	err := tolerationTrait.Apply(environment)
+
+	assert.Nil(t, err)
+}
+
+func createNominalDeploymentTolerationTest() (*Environment, *appsv1.Deployment) {
+	deployment := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "integration-name",
+		},
+		Spec: appsv1.DeploymentSpec{
+			Template: corev1.PodTemplateSpec{},
+		},
+	}
+
+	environment := &Environment{
+		Integration: &v1.Integration{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "integration-name",
+			},
+			Status: v1.IntegrationStatus{
+				Phase: v1.IntegrationPhaseDeploying,
+			},
+		},
+		Resources: kubernetes.NewCollection(deployment),
+	}
+
+	return environment, deployment
+}
+
+func createNominalKnativeServiceTolerationTest() (*Environment, *serving.Service) {
+	knativeService := &serving.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "integration-name",
+		},
+		Spec: serving.ServiceSpec{},
+	}
+
+	environment := &Environment{
+		Integration: &v1.Integration{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "integration-name",
+			},
+			Status: v1.IntegrationStatus{
+				Phase: v1.IntegrationPhaseDeploying,
+			},
+		},
+		Resources: kubernetes.NewCollection(knativeService),
+	}
+
+	return environment, knativeService
+}
+
+func createNominalCronJobTolerationTest() (*Environment, *v1beta1.CronJob) {
+	cronJob := &v1beta1.CronJob{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "integration-name",
+		},
+		Spec: v1beta1.CronJobSpec{},
+	}
+
+	environment := &Environment{
+		Integration: &v1.Integration{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "integration-name",
+			},
+			Status: v1.IntegrationStatus{
+				Phase: v1.IntegrationPhaseDeploying,
+			},
+		},
+		Resources: kubernetes.NewCollection(cronJob),
+	}
+
+	return environment, cronJob
+}
+
+func createNominalTolerationTrait() *tolerationTrait {
+	tolerationTrait := newTolerationTrait().(*tolerationTrait)
+	tolerationTrait.Enabled = util.BoolP(true)
+	tolerationTrait.Taints = make([]string, 0)
+
+	return tolerationTrait
+}