Repository: apache/camel-k
Pull request #1805 (2020-11-06 19:41:36)

Add local run subcommand with basic support
<!-- Description -->

This patch adds a new subcommand called `local run` which can run an existing integration locally.

<!--
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
Integrations can be executed locally using the "kamel local run" command
```


doru1004: > I get some errors due to the transition to Quarkus (e.g. main class changed):
> 
> ```
> Caused by: java.lang.ClassNotFoundException: org.apache.camel.k.main.Application
> ```

I updated the class to:

```
org.apache.camel.k.quarkus.Application
```

But I am now running into this error:

```
Error: Main method not found in class org.apache.camel.k.quarkus.Application, please define the main method as:
   public static void main(String[] args)
```

It looks like the main function was removed during the update process, is that intentional? And if so what other class should I be using instead. There seems to be no main class in any of the camel-k-runtime files.
nicolaferraro: > > I get some errors due to the transition to Quarkus (e.g. main class changed):
> > ```
> > Caused by: java.lang.ClassNotFoundException: org.apache.camel.k.main.Application
> > ```
> 
> I updated the class to:
> 
> ```
> org.apache.camel.k.quarkus.Application
> ```
> 
> But I am now running into this error:
> 
> ```
> Error: Main method not found in class org.apache.camel.k.quarkus.Application, please define the main method as:
>    public static void main(String[] args)
> ```
> 
> It looks like the main function was removed during the update process, is that intentional? And if so what other class should I be using instead. There seems to be no main method in any of the camel-k-runtime files.

Yes, that changes a bit with Quarkus. If you look at integrations running on Kube (I'm using latest from master), you see it's using: `io.quarkus.runner.GeneratedMain`.

@lburgazzoli may help here.


```
exec java -cp ./resources:/etc/camel/conf:/etc/camel/resources:/etc/camel/sources/i-source-000:dependencies/camel-k-integration-1.3.0-SNAPSHOT-runner.jar:dependencies/io.quarkus.arc.arc-1.9.0.Final.jar:dependencies/io.quarkus.quarkus-arc-1.9.0.Final.jar:dependencies/io.quarkus.quarkus-bootstrap-runner-1.9.0.Final.jar:dependencies/io.quarkus.quarkus-core-1.9.0.Final.jar:dependencies/io.quarkus.quarkus-development-mode-spi-1.9.0.Final.jar:dependencies/io.quarkus.quarkus-ide-launcher-1.9.0.Final.jar:dependencies/io.smallrye.common.smallrye-common-annotation-1.4.0.jar:dependencies/io.smallrye.common.smallrye-common-classloader-1.4.0.jar:dependencies/io.smallrye.common.smallrye-common-constraint-1.4.0.jar:dependencies/io.smallrye.common.smallrye-common-expression-1.4.0.jar:dependencies/io.smallrye.common.smallrye-common-function-1.4.0.jar:dependencies/io.smallrye.config.smallrye-config-1.9.1.jar:dependencies/io.smallrye.config.smallrye-config-common-1.9.1.jar:dependencies/jakarta.annotation.jakarta.annotation-api-1.3.5.jar:dependencies/jakarta.el.jakarta.el-api-3.0.3.jar:dependencies/jakarta.enterprise.jakarta.enterprise.cdi-api-2.0.2.jar:dependencies/jakarta.inject.jakarta.inject-api-1.0.jar:dependencies/jakarta.interceptor.jakarta.interceptor-api-1.2.5.jar:dependencies/jakarta.transaction.jakarta.transaction-api-1.3.3.jar:dependencies/org.apache.camel.camel-api-3.6.0.jar:dependencies/org.apache.camel.camel-base-3.6.0.jar:dependencies/org.apache.camel.camel-bean-3.6.0.jar:dependencies/org.apache.camel.camel-componentdsl-3.6.0.jar:dependencies/org.apache.camel.camel-core-catalog-3.6.0.jar:dependencies/org.apache.camel.camel-core-engine-3.6.0.jar:dependencies/org.apache.camel.camel-core-languages-3.6.0.jar:dependencies/org.apache.camel.camel-endpointdsl-3.6.0.jar:dependencies/org.apache.camel.camel-groovy-3.6.0.jar:dependencies/org.apache.camel.camel-log-3.6.0.jar:dependencies/org.apache.camel.camel-main-3.6.0.jar:dependencies/org.apache.camel.camel-management-api-3.6.0.jar:dependencies/org.apache.camel.camel-microprofile-config-3.6.0.jar:dependencies/org.apache.camel.camel-support-3.6.0.jar:dependencies/org.apache.camel.camel-timer-3.6.0.jar:dependencies/org.apache.camel.camel-tooling-model-3.6.0.jar:dependencies/org.apache.camel.camel-util-3.6.0.jar:dependencies/org.apache.camel.camel-util-json-3.6.0.jar:dependencies/org.apache.camel.k.camel-k-core-1.6.0-SNAPSHOT.jar:dependencies/org.apache.camel.k.camel-k-core-api-1.6.0-SNAPSHOT.jar:dependencies/org.apache.camel.k.camel-k-core-support-1.6.0-SNAPSHOT.jar:dependencies/org.apache.camel.k.camel-k-loader-groovy-1.6.0-SNAPSHOT.jar:dependencies/org.apache.camel.k.camel-k-loader-groovy-impl-1.6.0-SNAPSHOT.jar:dependencies/org.apache.camel.k.camel-k-runtime-1.6.0-SNAPSHOT.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-bean-1.3.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-core-1.3.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-endpointdsl-1.3.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-log-1.3.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-main-1.3.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-support-common-1.3.0.jar:dependencies/org.apache.camel.quarkus.camel-quarkus-timer-1.3.0.jar:dependencies/org.codehaus.groovy.groovy-3.0.5.jar:dependencies/org.eclipse.microprofile.config.microprofile-config-api-1.4.jar:dependencies/org.eclipse.microprofile.context-propagation.microprofile-context-propagation-api-1.0.1.jar:dependencies/org.graalvm.sdk.graal-sdk-20.2.0.jar:dependencies/org.jboss.logging.jboss-logging-3.4.1.Final.jar:dependencies/org.jboss.logging.jboss-logging-annotations-2.1.0.Final.jar:dependencies/org.jboss.logmanager.jboss-logmanager-embedded-1.0.5.jar:dependencies/org.jboss.slf4j.slf4j-jboss-logging-1.2.0.Final.jar:dependencies/org.jboss.threads.jboss-threads-3.1.1.Final.jar:dependencies/org.ow2.asm.asm-8.0.1.jar:dependencies/org.slf4j.slf4j-api-1.7.30.jar:dependencies/org.wildfly.common.wildfly-common-1.5.4.Final-format-001.jar io.quarkus.runner.GeneratedMain
```
lburgazzoli: The main class to use is `io.quarkus.runner.GeneratedMain`
doru1004: > The main class to use is `io.quarkus.runner.GeneratedMain`, you can take the right value from the catalog

Just committed a fix.
lburgazzoli: @nicolaferraro mind doing another review ?
nicolaferraro: Just a minor comment
doru1004: @nicolaferraro I managed to address all comments including making the change to "local run".

I have also included the `local run` subcommand to work with existing modeline support thus enabling all options to be used in as modeline options not just dependencies.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48e1d62a8..7d9c59108 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
 
 **Closed issues:**
 
+- scaling out camel-k integrations are not working correctly [\#1806](https://github.com/apache/camel-k/issues/1806)
 - Remove main runtime from operator codebase [\#1803](https://github.com/apache/camel-k/issues/1803)
 - Scalability of integration components in a non knative environment [\#1798](https://github.com/apache/camel-k/issues/1798)
 - Panic when attempting to convert from unstructured to Integration with traits [\#1794](https://github.com/apache/camel-k/issues/1794)
@@ -757,7 +758,6 @@
 - broken routes.js example [\#1065](https://github.com/apache/camel-k/issues/1065)
 - camel-k installation struggles [\#1059](https://github.com/apache/camel-k/issues/1059)
 - camel-k-maven-plugin must not contain any of these characters \/:"\<\>|?\* but found \> @ line 16, column 18 [\#1056](https://github.com/apache/camel-k/issues/1056)
-- Automatically extract trait configuration for user manual [\#1014](https://github.com/apache/camel-k/issues/1014)
 - Cannot set blockOwnerDeletion in this case because cannot find RESTMapping for APIVersion camel.apache.org/v1alpha1 Kind IntegrationKit [\#764](https://github.com/apache/camel-k/issues/764)
 - Error creating integration context with buildStrategy: routine on minikube [\#665](https://github.com/apache/camel-k/issues/665)
 - Kaniko: Add ability to push to on-premise registries [\#613](https://github.com/apache/camel-k/issues/613)
@@ -884,6 +884,7 @@
 **Closed issues:**
 
 - Failing start the camel-k-operator 1.0.0.-M3-SNAPSHOT with the minishifit [\#1015](https://github.com/apache/camel-k/issues/1015)
+- Automatically extract trait configuration for user manual [\#1014](https://github.com/apache/camel-k/issues/1014)
 - Provide a plain text file with the latest version of Camel K for easier automation of downloading via the tooling [\#1009](https://github.com/apache/camel-k/issues/1009)
 - Cannot use knative-source-yaml loader [\#1005](https://github.com/apache/camel-k/issues/1005)
 - Pubilsh Camel K 1.0.0-M2 to operator hub [\#1004](https://github.com/apache/camel-k/issues/1004)
@@ -1013,7 +1014,6 @@
 - External kamel bin testing [\#894](https://github.com/apache/camel-k/pull/894) ([bouskaJ](https://github.com/bouskaJ))
 - BuildConfig's, ImageStream's and Pod's not labeled with camel-k and can't delete with -l 'app=camel-k' [\#884](https://github.com/apache/camel-k/pull/884) ([dmvolod](https://github.com/dmvolod))
 - Fix embedded resources snapshot version [\#882](https://github.com/apache/camel-k/pull/882) ([dmvolod](https://github.com/dmvolod))
-- Switch to images-dev build on install-mini\<plaform\> [\#881](https://github.com/apache/camel-k/pull/881) ([dmvolod](https://github.com/dmvolod))
 - Added unit tests for ingress\_trait \#255 [\#880](https://github.com/apache/camel-k/pull/880) ([aldettinger](https://github.com/aldettinger))
 - feat: Enable integration scale sub-resource [\#962](https://github.com/apache/camel-k/pull/962) ([astefanutti](https://github.com/astefanutti))
 - fix: Add option to disable Kaniko cache warming [\#922](https://github.com/apache/camel-k/pull/922) ([asifdxtreme](https://github.com/asifdxtreme))
@@ -1072,6 +1072,7 @@
 
 **Merged pull requests:**
 
+- Switch to images-dev build on install-mini\<plaform\> [\#881](https://github.com/apache/camel-k/pull/881) ([dmvolod](https://github.com/dmvolod))
 - Fix make clean for tests and remove binary file from repo [\#879](https://github.com/apache/camel-k/pull/879) ([dmvolod](https://github.com/dmvolod))
 - chore\(js\): fix examples, use arrow function for lambda style processor [\#877](https://github.com/apache/camel-k/pull/877) ([lburgazzoli](https://github.com/lburgazzoli))
 - fix\(GC\): Skip 503 errors when scanning for resources to be GCed [\#876](https://github.com/apache/camel-k/pull/876) ([astefanutti](https://github.com/astefanutti))
@@ -1234,6 +1235,7 @@
 - Initial OLM manifest [\#648](https://github.com/apache/camel-k/pull/648) ([astefanutti](https://github.com/astefanutti))
 - Adding a note about the delay at first Sample run [\#647](https://github.com/apache/camel-k/pull/647) ([rafaeltuelho](https://github.com/rafaeltuelho))
 - Fix \#645: use the right kind of sink in knative trait [\#646](https://github.com/apache/camel-k/pull/646) ([nicolaferraro](https://github.com/nicolaferraro))
+- chore\(doc\): change the description of the project [\#628](https://github.com/apache/camel-k/pull/628) ([nicolaferraro](https://github.com/nicolaferraro))
 
 ## [0.3.3](https://github.com/apache/camel-k/tree/0.3.3) (2019-05-03)
 
@@ -1269,7 +1271,6 @@
 - feat: Add kamel describe command [\#633](https://github.com/apache/camel-k/pull/633) ([jamesnetherton](https://github.com/jamesnetherton))
 - Modified PR \#625 as requested [\#632](https://github.com/apache/camel-k/pull/632) ([valdar](https://github.com/valdar))
 - Use non-caching client in builds scheduling critical section [\#631](https://github.com/apache/camel-k/pull/631) ([astefanutti](https://github.com/astefanutti))
-- chore\(doc\): change the description of the project [\#628](https://github.com/apache/camel-k/pull/628) ([nicolaferraro](https://github.com/nicolaferraro))
 - Updated README for configmap [\#626](https://github.com/apache/camel-k/pull/626) ([stevef1uk](https://github.com/stevef1uk))
 - Build improvements [\#623](https://github.com/apache/camel-k/pull/623) ([astefanutti](https://github.com/astefanutti))
 -  allow to configure default configurations on platform [\#622](https://github.com/apache/camel-k/pull/622) ([lburgazzoli](https://github.com/lburgazzoli))
diff --git a/deploy/traits.yaml b/deploy/traits.yaml
index c4783c7b1..9797ee192 100755
--- a/deploy/traits.yaml
+++ b/deploy/traits.yaml
@@ -456,6 +456,23 @@ traits:
   - name: target-labels
     type: '[]string'
     description: The set of labels to be transferred
+- name: pdb
+  platform: false
+  profiles:
+  - Kubernetes
+  - Knative
+  - OpenShift
+  description: The PDB trait allows to configure the PodDisruptionBudget resource for the Integration pods.
+  properties:
+  - name: enabled
+    type: bool
+    description: Can be used to enable or disable a trait. All traits share this common property.
+  - name: min-available
+    type: string
+    description: The number of pods for the Integration that must still be available after an eviction.It can be either an absolute number or a percentage.Only one of `min-available` and `max-unavailable` can be specified.
+  - name: max-unavailable
+    type: string
+    description: The number of pods for the Integration that can be unavailable after an eviction.It can be either an absolute number or a percentage (default `1` if `min-available` is also not set).Only one of `max-unavailable` and `min-available` can be specified.
 - name: platform
   platform: true
   profiles:
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 73aaacaa0..df04c1dcd 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -47,6 +47,7 @@
 ** xref:traits:master.adoc[Master]
 ** xref:traits:openapi.adoc[Openapi]
 ** xref:traits:owner.adoc[Owner]
+** xref:traits:pdb.adoc[Pdb]
 ** xref:traits:platform.adoc[Platform]
 ** xref:traits:prometheus.adoc[Prometheus]
 ** xref:traits:pull-secret.adoc[Pull Secret]
diff --git a/docs/modules/traits/pages/pdb.adoc b/docs/modules/traits/pages/pdb.adoc
new file mode 100755
index 000000000..56da0c7b6
--- /dev/null
+++ b/docs/modules/traits/pages/pdb.adoc
@@ -0,0 +1,41 @@
+= Pdb Trait
+
+// Start of autogenerated code - DO NOT EDIT! (description)
+The PDB trait allows to configure the PodDisruptionBudget resource for the Integration pods.
+
+
+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:
+```
+kamel run --trait pdb.[key]=[value] --trait pdb.[key2]=[value2] integration.groovy
+```
+The following configuration options are available:
+
+[cols="2,1,5a"]
+|===
+|Property | Type | Description
+
+| pdb.enabled
+| bool
+| Can be used to enable or disable a trait. All traits share this common property.
+
+| pdb.min-available
+| string
+| The number of pods for the Integration that must still be available after an eviction.
+It can be either an absolute number or a percentage.
+Only one of `min-available` and `max-unavailable` can be specified.
+
+| pdb.max-unavailable
+| string
+| The number of pods for the Integration that can be unavailable after an eviction.
+It can be either an absolute number or a percentage (default `1` if `min-available` is also not set).
+Only one of `max-unavailable` and `min-available` can be specified.
+
+|===
+
+// End of autogenerated code - DO NOT EDIT! (configuration)
diff --git a/pkg/cmd/inspect.go b/pkg/cmd/inspect.go
index 1c2cbf4f5..40c0dc11c 100644
--- a/pkg/cmd/inspect.go
+++ b/pkg/cmd/inspect.go
@@ -18,28 +18,11 @@ limitations under the License.
 package cmd
 
 import (
-	"errors"
 	"fmt"
-	"io/ioutil"
-	"os"
-	"path"
-	"strings"
 
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
-	"github.com/apache/camel-k/pkg/builder/runtime"
-	"github.com/apache/camel-k/pkg/trait"
-	"github.com/apache/camel-k/pkg/util"
-	"github.com/apache/camel-k/pkg/util/camel"
-	"github.com/apache/camel-k/pkg/util/defaults"
-	"github.com/apache/camel-k/pkg/util/maven"
-	"github.com/scylladb/go-set/strset"
 	"github.com/spf13/cobra"
 )
 
-var acceptedDependencyTypes = []string{"bom", "camel", "camel-k", "camel-quarkus", "mvn", "github"}
-
-const defaultDependenciesDirectoryName = "dependencies"
-
 func newCmdInspect(rootCmdOptions *RootCmdOptions) (*cobra.Command, *inspectCmdOptions) {
 	options := inspectCmdOptions{
 		RootCmdOptions: rootCmdOptions,
@@ -56,9 +39,15 @@ will be generated by calling Maven and then printed in the selected output forma
 			if err := options.validate(args); err != nil {
 				return err
 			}
+			if err := options.init(); err != nil {
+				return err
+			}
 			if err := options.run(args); err != nil {
 				fmt.Println(err.Error())
 			}
+			if err := options.deinit(); err != nil {
+				return err
+			}
 
 			return nil
 		},
@@ -68,9 +57,7 @@ will be generated by calling Maven and then printed in the selected output forma
 	}
 
 	cmd.Flags().Bool("all-dependencies", false, "Compute transitive dependencies and move them to directory pointed to by the --dependencies-directory flag.")
-	cmd.Flags().StringArrayP("dependency", "d", nil, `Additional top-level dependency with the format:
-<type>:<dependency-name>
-where <type> is one of {`+strings.Join(acceptedDependencyTypes, "|")+`}.`)
+	cmd.Flags().StringArrayP("dependency", "d", nil, additionalDependencyUsageMessage)
 	cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml")
 
 	return &cmd, &options
@@ -84,195 +71,22 @@ type inspectCmdOptions struct {
 }
 
 func (command *inspectCmdOptions) validate(args []string) error {
-	// If no source files have been provided there is nothing to inspect.
-	if len(args) == 0 {
-		return errors.New("no integration files have been provided, nothing to inspect")
-	}
-
-	// Ensure source files exist.
-	for _, arg := range args {
-		// fmt.Printf("Validating file: %v\n", arg)
-		fileExists, err := util.FileExists(arg)
-
-		// Report any error.
-		if err != nil {
-			return err
-		}
-
-		// Signal file not found.
-		if !fileExists {
-			return errors.New("input file " + arg + " file does not exist")
-		}
-	}
-
-	// Validate list of additional dependencies i.e. make sure that each dependency has
-	// a valid type.
-	if command.AdditionalDependencies != nil {
-		for _, additionalDependency := range command.AdditionalDependencies {
-			dependencyComponents := strings.Split(additionalDependency, ":")
-
-			TypeIsValid := false
-			for _, dependencyType := range acceptedDependencyTypes {
-				if dependencyType == dependencyComponents[0] {
-					TypeIsValid = true
-				}
-			}
-
-			if !TypeIsValid {
-				return errors.New("Unexpected type for user-provided dependency: " + additionalDependency + ", check command usage for valid format.")
-			}
-
-		}
-	}
-
-	return nil
-}
-
-func (command *inspectCmdOptions) run(args []string) error {
-	// Fetch existing catalog or create new one if one does not already exist.
-	catalog, err := createCamelCatalog()
-
-	// Get top-level dependencies, this is the default behavior when no other options are provided.
-	// Do not output these options when transitive options are enbled.
-	dependencies, err := getTopLevelDependencies(catalog, command.OutputFormat, args, !command.AllDependencies)
-	if err != nil {
-		return err
-	}
-
-	// Add additional user-provided dependencies.
-	if command.AdditionalDependencies != nil {
-		for _, additionalDependency := range command.AdditionalDependencies {
-			dependencies = append(dependencies, additionalDependency)
-		}
-	}
-
-	// Top level dependencies are printed out.
-	if command.AllDependencies {
-		// If --all-dependencies flag is set, move all transitive dependencies in the --dependencies-directory.
-		err = getTransitiveDependencies(catalog, dependencies, command)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
+	return validateIntegrationForDependencies(args, command.AdditionalDependencies)
 }
 
-func getTopLevelDependencies(catalog *camel.RuntimeCatalog, format string, args []string, printDependencies bool) ([]string, error) {
-	// List of top-level dependencies.
-	dependencies := strset.New()
-
-	// Invoke the dependency inspector code for each source file.
-	for _, source := range args {
-		data, _, err := loadContent(source, false, false)
-		if err != nil {
-			return []string{}, err
-		}
-
-		sourceSpec := v1.SourceSpec{
-			DataSpec: v1.DataSpec{
-				Name:        path.Base(source),
-				Content:     data,
-				Compression: false,
-			},
-		}
-
-		// Extract list of top-level dependencies.
-		dependencies.Merge(trait.AddSourceDependencies(sourceSpec, catalog))
-	}
-
-	if printDependencies {
-		err := outputDependencies(dependencies.List(), format)
-		if err != nil {
-			return []string{}, err
-		}
-	}
-
-	return dependencies.List(), nil
-}
-
-func generateCatalog() (*camel.RuntimeCatalog, error) {
-	// A Camel catalog is requiref for this operatio.
-	settings := ""
-	mvn := v1.MavenSpec{
-		LocalRepository: "",
-	}
-	runtime := v1.RuntimeSpec{
-		Version:  defaults.DefaultRuntimeVersion,
-		Provider: v1.RuntimeProviderQuarkus,
-	}
-	providerDependencies := []maven.Dependency{}
-	catalog, err := camel.GenerateCatalogCommon(settings, mvn, runtime, providerDependencies)
-	if err != nil {
-		return nil, err
-	}
-
-	return catalog, nil
+func (command *inspectCmdOptions) init() error {
+	return createMavenWorkingDirectory()
 }
 
-func getTransitiveDependencies(
-	catalog *camel.RuntimeCatalog,
-	dependencies []string,
-	command *inspectCmdOptions) error {
-
-	mvn := v1.MavenSpec{
-		LocalRepository: "",
-	}
-
-	// Create Maven project.
-	project := runtime.GenerateQuarkusProjectCommon(
-		catalog.CamelCatalogSpec.Runtime.Metadata["camel-quarkus.version"],
-		defaults.DefaultRuntimeVersion, catalog.CamelCatalogSpec.Runtime.Metadata["quarkus.version"])
-
-	// Inject dependencies into Maven project.
-	err := camel.ManageIntegrationDependencies(&project, dependencies, catalog)
-	if err != nil {
-		return err
-	}
-
-	// Create local Maven context.
-	temporaryDirectory, err := ioutil.TempDir(os.TempDir(), "maven-")
-	if err != nil {
-		return err
-	}
-
-	// Maven local context to be used for generating the transitive dependencies.
-	mc := maven.NewContext(temporaryDirectory, project)
-	mc.LocalRepository = mvn.LocalRepository
-	mc.Timeout = mvn.GetTimeout().Duration
-
-	// Make maven command less verbose.
-	mc.AdditionalArguments = append(mc.AdditionalArguments, "-q")
-
-	err = runtime.BuildQuarkusRunnerCommon(mc)
-	if err != nil {
-		return err
-	}
-
-	// Compute dependencies.
-	content, err := runtime.ComputeQuarkusDependenciesCommon(mc, catalog.Runtime.Version)
-	if err != nil {
-		return err
-	}
-
-	// Compose artifacts list.
-	artifacts := []v1.Artifact{}
-	artifacts, err = runtime.ProcessQuarkusTransitiveDependencies(mc, content)
+func (command *inspectCmdOptions) run(args []string) error {
+	// Fetch dependencies.
+	dependencies, err := getDependencies(args, command.AdditionalDependencies, command.AllDependencies)
 	if err != nil {
 		return err
 	}
 
-	// Dump dependencies in the dependencies directory and construct the list of dependencies.
-	transitiveDependencies := []string{}
-	for _, entry := range artifacts {
-		transitiveDependencies = append(transitiveDependencies, entry.Location)
-	}
-
-	// Remove directory used for computing the dependencies.
-	defer os.RemoveAll(temporaryDirectory)
-
-	// Output transitive dependencies only if requested via the output format flag.
-	err = outputDependencies(transitiveDependencies, command.OutputFormat)
+	// Print dependencies.
+	err = outputDependencies(dependencies, command.OutputFormat)
 	if err != nil {
 		return err
 	}
@@ -280,65 +94,6 @@ func getTransitiveDependencies(
 	return nil
 }
 
-func outputDependencies(dependencies []string, format string) error {
-	if format != "" {
-		err := printDependencies(format, dependencies)
-		if err != nil {
-			return err
-		}
-	} else {
-		// Print output in text form.
-		for _, dep := range dependencies {
-			fmt.Printf("%v\n", dep)
-		}
-	}
-
-	return nil
-}
-
-func printDependencies(format string, dependecies []string) error {
-	switch format {
-	case "yaml":
-		data, err := util.DependenciesToYAML(dependecies)
-		if err != nil {
-			return err
-		}
-		fmt.Print(string(data))
-	case "json":
-		data, err := util.DependenciesToJSON(dependecies)
-		if err != nil {
-			return err
-		}
-		fmt.Print(string(data))
-	default:
-		return errors.New("unknown output format: " + format)
-	}
-	return nil
-}
-
-func getWorkingDirectory() (string, error) {
-	currentDirectory, err := os.Getwd()
-	if err != nil {
-		return "", err
-	}
-
-	return currentDirectory, nil
-}
-
-func createCamelCatalog() (*camel.RuntimeCatalog, error) {
-	// Attempt to reuse existing Camel catalog if one is present.
-	catalog, err := camel.DefaultCatalog()
-	if err != nil {
-		return nil, err
-	}
-
-	// Generate catalog if one was not found.
-	if catalog == nil {
-		catalog, err = generateCatalog()
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return catalog, nil
+func (command *inspectCmdOptions) deinit() error {
+	return deleteMavenWorkingDirectory()
 }
diff --git a/pkg/cmd/local.go b/pkg/cmd/local.go
new file mode 100644
index 000000000..89b774044
--- /dev/null
+++ b/pkg/cmd/local.go
@@ -0,0 +1,55 @@
+/*
+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 (
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+)
+
+// NewCmdLocal -- Add local kamel subcommand with several other subcommands of its own.
+func newCmdLocal(rootCmdOptions *RootCmdOptions) *cobra.Command {
+	cmd := cobra.Command{
+		Use:   "local [sub-command]",
+		Short: "Perform integration actions locally.",
+		Long:  `Perform integration actions locally given a set of input integration files.`,
+		Annotations: map[string]string{
+			offlineCommandLabel: "true",
+		},
+	}
+
+	return &cmd
+}
+
+func addLocalSubCommands(cmd *cobra.Command, options *RootCmdOptions) error {
+	var localCmd *cobra.Command
+	for _, c := range cmd.Commands() {
+		if c.Name() == "local" {
+			localCmd = c
+			break
+		}
+	}
+
+	if localCmd == nil {
+		return errors.New("could not find any configured local command")
+	}
+
+	localCmd.AddCommand(cmdOnly(newCmdLocalRun(options)))
+
+	return nil
+}
diff --git a/pkg/cmd/local_run.go b/pkg/cmd/local_run.go
new file mode 100644
index 000000000..f2b86f4c7
--- /dev/null
+++ b/pkg/cmd/local_run.go
@@ -0,0 +1,110 @@
+/*
+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"
+	"strings"
+
+	"github.com/spf13/cobra"
+)
+
+func newCmdLocalRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *localRunCmdOptions) {
+	options := localRunCmdOptions{
+		RootCmdOptions: rootCmdOptions,
+	}
+
+	cmd := cobra.Command{
+		Use:     "run [integration files]",
+		Short:   "Run integration locally.",
+		Long:    `Run integration locally using the input integration files.`,
+		PreRunE: decode(&options),
+		RunE: func(_ *cobra.Command, args []string) error {
+			if err := options.validate(args); err != nil {
+				return err
+			}
+			if err := options.init(); err != nil {
+				return err
+			}
+			if err := options.run(args); err != nil {
+				fmt.Println(err.Error())
+			}
+			if err := options.deinit(); err != nil {
+				return err
+			}
+
+			return nil
+		},
+		Annotations: map[string]string{
+			offlineCommandLabel: "true",
+		},
+	}
+
+	cmd.Flags().StringArray("property-file", nil, "Add a property file to the integration.")
+	cmd.Flags().StringArrayP("dependency", "d", nil, `Additional top-level dependency with the format:
+<type>:<dependency-name>
+where <type> is one of {`+strings.Join(acceptedDependencyTypes, "|")+`}.`)
+
+	return &cmd, &options
+}
+
+type localRunCmdOptions struct {
+	*RootCmdOptions
+	PropertyFiles          []string `mapstructure:"property-files"`
+	AdditionalDependencies []string `mapstructure:"dependencies"`
+}
+
+func (command *localRunCmdOptions) validate(args []string) error {
+	// Validate additional dependencies specified by the user.
+	err := validateIntegrationForDependencies(args, command.AdditionalDependencies)
+	if err != nil {
+		return err
+	}
+
+	// Validate properties file.
+	err = validateFiles(command.PropertyFiles)
+	if err != nil {
+		return nil
+	}
+
+	return nil
+}
+
+func (command *localRunCmdOptions) init() error {
+	return createMavenWorkingDirectory()
+}
+
+func (command *localRunCmdOptions) run(args []string) error {
+	// Fetch dependencies.
+	dependencies, err := getDependencies(args, command.AdditionalDependencies, true)
+	if err != nil {
+		return err
+	}
+
+	// Run the integration locally.
+	err = RunLocalIntegration(command.PropertyFiles, dependencies, args)
+	if err != nil {
+		return nil
+	}
+
+	return nil
+}
+
+func (command *localRunCmdOptions) deinit() error {
+	return deleteMavenWorkingDirectory()
+}
diff --git a/pkg/cmd/modeline.go b/pkg/cmd/modeline.go
index 4eab57a9e..6a3f0c139 100644
--- a/pkg/cmd/modeline.go
+++ b/pkg/cmd/modeline.go
@@ -31,6 +31,7 @@ import (
 
 const (
 	runCmdName        = "run"
+	localCmdName      = "local"
 	runCmdSourcesArgs = "source"
 )
 
@@ -98,9 +99,14 @@ func createKamelWithModelineCommand(ctx context.Context, args []string) (*cobra.
 
 	fg := target.Flags()
 
-	additionalSources, err := fg.GetStringArray(runCmdSourcesArgs)
-	if err != nil {
-		return nil, nil, err
+	// Only the run command has source flag (for now). Remove condition when
+	// local run also supports source.
+	additionalSources := make([]string, 0)
+	if target.Name() == runCmdName && target.Parent().Name() != localCmdName {
+		additionalSources, err = fg.GetStringArray(runCmdSourcesArgs)
+		if err != nil {
+			return nil, nil, err
+		}
 	}
 
 	files := make([]string, 0, len(fg.Args())+len(additionalSources))
@@ -155,27 +161,37 @@ func extractModelineOptions(ctx context.Context, sources []string) ([]modeline.O
 	}
 
 	for _, resolvedSource := range resolvedSources {
-		ops, err := modeline.Parse(resolvedSource.Location, resolvedSource.Content)
+		ops, err := extractModelineOptionsFromSource(resolvedSource)
 		if err != nil {
-			return opts, errors.Wrapf(err, "cannot process file %s", resolvedSource.Location)
+			return opts, err
 		}
-		for i, o := range ops {
-			if disallowedOptions[o.Name] {
-				return opts, fmt.Errorf("option %q is disallowed in modeline", o.Name)
-			}
 
-			if fileOptions[o.Name] && resolvedSource.Local {
-				baseDir := filepath.Dir(resolvedSource.Origin)
-				refPath := o.Value
-				if !filepath.IsAbs(refPath) {
-					full := path.Join(baseDir, refPath)
-					o.Value = full
-					ops[i] = o
-				}
-			}
-		}
 		opts = append(opts, ops...)
 	}
 
 	return opts, nil
 }
+
+func extractModelineOptionsFromSource(resolvedSource Source) ([]modeline.Option, error) {
+	ops, err := modeline.Parse(resolvedSource.Location, resolvedSource.Content)
+	if err != nil {
+		return ops, errors.Wrapf(err, "cannot process file %s", resolvedSource.Location)
+	}
+	for i, o := range ops {
+		if disallowedOptions[o.Name] {
+			return ops, fmt.Errorf("option %q is disallowed in modeline", o.Name)
+		}
+
+		if fileOptions[o.Name] && resolvedSource.Local {
+			baseDir := filepath.Dir(resolvedSource.Origin)
+			refPath := o.Value
+			if !filepath.IsAbs(refPath) {
+				full := path.Join(baseDir, refPath)
+				o.Value = full
+				ops[i] = o
+			}
+		}
+	}
+
+	return ops, nil
+}
diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go
index 2899a3087..c56d3690e 100644
--- a/pkg/cmd/root.go
+++ b/pkg/cmd/root.go
@@ -61,6 +61,10 @@ func NewKamelCommand(ctx context.Context) (*cobra.Command, error) {
 		return cmd, err
 	}
 
+	if err := addLocalSubCommands(cmd, &options); err != nil {
+		return cmd, err
+	}
+
 	err = kamelPostAddCommandInit(cmd)
 
 	return cmd, err
@@ -139,6 +143,7 @@ func addKamelSubcommands(cmd *cobra.Command, options *RootCmdOptions) {
 	cmd.AddCommand(cmdOnly(newCmdInit(options)))
 	cmd.AddCommand(cmdOnly(newCmdDebug(options)))
 	cmd.AddCommand(cmdOnly(newCmdInspect(options)))
+	cmd.AddCommand(newCmdLocal(options))
 }
 
 func addHelpSubCommands(cmd *cobra.Command, options *RootCmdOptions) error {
diff --git a/pkg/cmd/util.go b/pkg/cmd/util.go
index 8d5ac1470..3e364a621 100644
--- a/pkg/cmd/util.go
+++ b/pkg/cmd/util.go
@@ -24,11 +24,12 @@ import (
 	"encoding/csv"
 	"encoding/json"
 	"fmt"
-	"github.com/apache/camel-k/pkg/util/gzip"
 	"log"
 	"reflect"
 	"strings"
 
+	"github.com/apache/camel-k/pkg/util/gzip"
+
 	"github.com/mitchellh/mapstructure"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
diff --git a/pkg/cmd/util_commands.go b/pkg/cmd/util_commands.go
new file mode 100644
index 000000000..b44243a21
--- /dev/null
+++ b/pkg/cmd/util_commands.go
@@ -0,0 +1,95 @@
+/*
+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 (
+	"context"
+	"fmt"
+	"os"
+	"os/exec"
+	"path"
+	"strings"
+)
+
+var (
+	ctx9, cancel9 = context.WithCancel(context.Background()) // preemptive: kill subprocess
+	ctx, cancel   = context.WithCancel(ctx9)                 // cooperative: wait for subprocess
+)
+
+func formatRoutes(files []string) []string {
+	routes := []string{}
+	for _, route := range files {
+		// Split route path.
+		a := strings.Split(route, ".")
+
+		// Extract extension.
+		extension := a[len(a)-1]
+
+		// Add file if extension is supported.
+		routes = append(routes, "file:"+route+"?language="+extension)
+	}
+
+	return routes
+}
+
+func confDirectories(properties []string) []string {
+	confDirs := []string{}
+
+	for _, propertiesPath := range properties {
+		confDirs = append(confDirs, path.Dir(propertiesPath))
+	}
+
+	return confDirs
+}
+
+func assembleClasspatchArgValue(properties []string, dependencies []string, routes []string) string {
+	classpathContents := []string{}
+	classpathContents = append(classpathContents, properties...)
+	classpathContents = append(classpathContents, routes...)
+	classpathContents = append(classpathContents, dependencies...)
+	return strings.Join(classpathContents, ":")
+}
+
+// RunLocalIntegration --
+func RunLocalIntegration(properties []string, dependencies []string, routes []string) error {
+	// Create classpath value.
+	classpathValue := assembleClasspatchArgValue(properties, dependencies, routes)
+
+	// Create java command that runs the integration.
+	javaCmd := "java"
+
+	// Create java command arguments.
+	args := make([]string, 0)
+	args = append(args, "-cp")
+	args = append(args, classpathValue)
+	args = append(args, "io.quarkus.runner.GeneratedMain")
+
+	cmd := exec.CommandContext(ctx, javaCmd, args...)
+	cmd.Stderr = os.Stderr
+	cmd.Stdout = os.Stdout
+
+	fmt.Printf("executing: %s", strings.Join(cmd.Args, " "))
+
+	// Add directory where the properties file resides.
+	cmd.Env = append(cmd.Env, "CAMEL_K_CONF_D="+strings.Join(confDirectories(properties), ","))
+
+	// Add files to the command line under the CAMEL_K_ROUTES flag.
+	cmd.Env = append(cmd.Env, "CAMEL_K_ROUTES="+strings.Join(formatRoutes(routes), ","))
+
+	return cmd.Run()
+}
diff --git a/pkg/cmd/util_dependencies.go b/pkg/cmd/util_dependencies.go
new file mode 100644
index 000000000..30722a76c
--- /dev/null
+++ b/pkg/cmd/util_dependencies.go
@@ -0,0 +1,326 @@
+/*
+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"
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/builder/runtime"
+	"github.com/apache/camel-k/pkg/trait"
+	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/camel"
+	"github.com/apache/camel-k/pkg/util/defaults"
+	"github.com/apache/camel-k/pkg/util/maven"
+	"github.com/pkg/errors"
+	"github.com/scylladb/go-set/strset"
+)
+
+// Directory used by Maven for an invocation of the kamel local command.
+// By default a temporary folder will be used.
+var mavenWorkingDirectory string = ""
+
+var acceptedDependencyTypes = []string{"bom", "camel", "camel-k", "camel-quarkus", "mvn", "github"}
+
+var additionalDependencyUsageMessage = `Additional top-level dependencies are specified with the format:
+<type>:<dependency-name>
+where <type> is one of {` + strings.Join(acceptedDependencyTypes, "|") + `}.`
+
+const defaultDependenciesDirectoryName = "dependencies"
+
+func getDependencies(args []string, additionalDependencies []string, allDependencies bool) ([]string, error) {
+	// Fetch existing catalog or create new one if one does not already exist.
+	catalog, err := createCamelCatalog()
+
+	// Get top-level dependencies.
+	dependencies, err := getTopLevelDependencies(catalog, args)
+	if err != nil {
+		return nil, err
+	}
+
+	// Add additional user-provided dependencies.
+	if additionalDependencies != nil {
+		for _, additionalDependency := range additionalDependencies {
+			dependencies = append(dependencies, additionalDependency)
+		}
+	}
+
+	// Compute transitive dependencies.
+	if allDependencies {
+		dependencies, err = getTransitiveDependencies(catalog, dependencies)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return dependencies, nil
+}
+
+func getTopLevelDependencies(catalog *camel.RuntimeCatalog, args []string) ([]string, error) {
+	// List of top-level dependencies.
+	dependencies := strset.New()
+
+	// Invoke the dependency inspector code for each source file.
+	for _, source := range args {
+		data, _, err := loadContent(source, false, false)
+		if err != nil {
+			return []string{}, err
+		}
+
+		sourceSpec := v1.SourceSpec{
+			DataSpec: v1.DataSpec{
+				Name:        path.Base(source),
+				Content:     data,
+				Compression: false,
+			},
+		}
+
+		// Extract list of top-level dependencies.
+		dependencies.Merge(trait.AddSourceDependencies(sourceSpec, catalog))
+	}
+
+	return dependencies.List(), nil
+}
+
+func getTransitiveDependencies(
+	catalog *camel.RuntimeCatalog,
+	dependencies []string) ([]string, error) {
+
+	mvn := v1.MavenSpec{
+		LocalRepository: "",
+	}
+
+	// Create Maven project.
+	project := runtime.GenerateQuarkusProjectCommon(
+		catalog.CamelCatalogSpec.Runtime.Metadata["camel-quarkus.version"],
+		defaults.DefaultRuntimeVersion, catalog.CamelCatalogSpec.Runtime.Metadata["quarkus.version"])
+
+	// Inject dependencies into Maven project.
+	err := camel.ManageIntegrationDependencies(&project, dependencies, catalog)
+	if err != nil {
+		return nil, err
+	}
+
+	// Maven local context to be used for generating the transitive dependencies.
+	mc := maven.NewContext(mavenWorkingDirectory, project)
+	mc.LocalRepository = mvn.LocalRepository
+	mc.Timeout = mvn.GetTimeout().Duration
+
+	// Make maven command less verbose.
+	mc.AdditionalArguments = append(mc.AdditionalArguments, "-q")
+
+	err = runtime.BuildQuarkusRunnerCommon(mc)
+	if err != nil {
+		return nil, err
+	}
+
+	// Compute dependencies.
+	content, err := runtime.ComputeQuarkusDependenciesCommon(mc, catalog.Runtime.Version)
+	if err != nil {
+		return nil, err
+	}
+
+	// Compose artifacts list.
+	artifacts := []v1.Artifact{}
+	artifacts, err = runtime.ProcessQuarkusTransitiveDependencies(mc, content)
+	if err != nil {
+		return nil, err
+	}
+
+	// Dump dependencies in the dependencies directory and construct the list of dependencies.
+	transitiveDependencies := []string{}
+	for _, entry := range artifacts {
+		transitiveDependencies = append(transitiveDependencies, entry.Location)
+	}
+
+	return transitiveDependencies, nil
+}
+
+func generateCatalog() (*camel.RuntimeCatalog, error) {
+	// A Camel catalog is requiref for this operatio.
+	settings := ""
+	mvn := v1.MavenSpec{
+		LocalRepository: "",
+	}
+	runtime := v1.RuntimeSpec{
+		Version:  defaults.DefaultRuntimeVersion,
+		Provider: v1.RuntimeProviderQuarkus,
+	}
+	providerDependencies := []maven.Dependency{}
+	catalog, err := camel.GenerateCatalogCommon(settings, mvn, runtime, providerDependencies)
+	if err != nil {
+		return nil, err
+	}
+
+	return catalog, nil
+}
+
+func createCamelCatalog() (*camel.RuntimeCatalog, error) {
+	// Attempt to reuse existing Camel catalog if one is present.
+	catalog, err := camel.DefaultCatalog()
+	if err != nil {
+		return nil, err
+	}
+
+	// Generate catalog if one was not found.
+	if catalog == nil {
+		catalog, err = generateCatalog()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return catalog, nil
+}
+
+func outputDependencies(dependencies []string, format string) error {
+	if format != "" {
+		err := printDependencies(format, dependencies)
+		if err != nil {
+			return err
+		}
+	} else {
+		// Print output in text form.
+		for _, dep := range dependencies {
+			fmt.Printf("%v\n", dep)
+		}
+	}
+
+	return nil
+}
+
+func printDependencies(format string, dependecies []string) error {
+	switch format {
+	case "yaml":
+		data, err := util.DependenciesToYAML(dependecies)
+		if err != nil {
+			return err
+		}
+		fmt.Print(string(data))
+	case "json":
+		data, err := util.DependenciesToJSON(dependecies)
+		if err != nil {
+			return err
+		}
+		fmt.Print(string(data))
+	default:
+		return errors.New("unknown output format: " + format)
+	}
+	return nil
+}
+
+func validateFile(file string) error {
+	fileExists, err := util.FileExists(file)
+
+	// Report any error.
+	if err != nil {
+		return err
+	}
+
+	// Signal file not found.
+	if !fileExists {
+		return errors.New("File " + file + " file does not exist")
+	}
+
+	return nil
+}
+
+func validateFiles(args []string) error {
+	// Ensure source files exist.
+	for _, arg := range args {
+		err := validateFile(arg)
+		if err != nil {
+			return nil
+		}
+	}
+
+	return nil
+}
+
+func validateAdditionalDependencies(additionalDependencies []string) error {
+	// Validate list of additional dependencies i.e. make sure that each dependency has
+	// a valid type.
+	if additionalDependencies != nil {
+		for _, additionalDependency := range additionalDependencies {
+			isValid := validateDependency(additionalDependency)
+			if !isValid {
+				return errors.New("Unexpected type for user-provided dependency: " + additionalDependency + ". " + additionalDependencyUsageMessage)
+			}
+		}
+	}
+
+	return nil
+}
+
+func validateDependency(additionalDependency string) bool {
+	dependencyComponents := strings.Split(additionalDependency, ":")
+
+	TypeIsValid := false
+	for _, dependencyType := range acceptedDependencyTypes {
+		if dependencyType == dependencyComponents[0] {
+			TypeIsValid = true
+		}
+	}
+
+	return TypeIsValid
+}
+
+func validateIntegrationForDependencies(args []string, additionalDependencies []string) error {
+	// If no source files have been provided there is nothing to inspect.
+	if len(args) == 0 {
+		return errors.New("no integration files have been provided")
+	}
+
+	// Validate integration files.
+	err := validateFiles(args)
+	if err != nil {
+		return nil
+	}
+
+	// Validate additional dependencies specified by the user.
+	err = validateAdditionalDependencies(additionalDependencies)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func createMavenWorkingDirectory() error {
+	// Create local Maven context.
+	temporaryDirectory, err := ioutil.TempDir(os.TempDir(), "maven-")
+	if err != nil {
+		return err
+	}
+
+	// Set the Maven directory to the default value.
+	mavenWorkingDirectory = temporaryDirectory
+
+	return nil
+}
+
+func deleteMavenWorkingDirectory() error {
+	// Remove directory used for computing the dependencies.
+	defer os.RemoveAll(mavenWorkingDirectory)
+
+	return nil
+}
diff --git a/pkg/cmd/util_sources.go b/pkg/cmd/util_sources.go
index af218f044..50a083859 100644
--- a/pkg/cmd/util_sources.go
+++ b/pkg/cmd/util_sources.go
@@ -66,27 +66,10 @@ func ResolveSources(ctx context.Context, locations []string, compress bool) ([]S
 
 	for _, location := range locations {
 		if isLocal(location) {
-			if _, err := os.Stat(location); err != nil && os.IsNotExist(err) {
-				return sources, errors.Wrapf(err, "file %s does not exist", location)
-			} else if err != nil {
-				return sources, errors.Wrapf(err, "error while accessing file %s", location)
-			}
-
-			answer := Source{
-				Name:     path.Base(location),
-				Origin:   location,
-				Location: location,
-				Compress: compress,
-				Local:    true,
-			}
-
-			content, err := ioutil.ReadFile(location)
+			answer, err := ResolveLocalSource(location, compress)
 			if err != nil {
 				return sources, err
 			}
-			if err := answer.setContent(content); err != nil {
-				return sources, err
-			}
 
 			sources = append(sources, answer)
 		} else {
@@ -199,3 +182,30 @@ func ResolveSources(ctx context.Context, locations []string, compress bool) ([]S
 
 	return sources, nil
 }
+
+// ResolveLocalSource --
+func ResolveLocalSource(location string, compress bool) (Source, error) {
+	if _, err := os.Stat(location); err != nil && os.IsNotExist(err) {
+		return Source{}, errors.Wrapf(err, "file %s does not exist", location)
+	} else if err != nil {
+		return Source{}, errors.Wrapf(err, "error while accessing file %s", location)
+	}
+
+	answer := Source{
+		Name:     path.Base(location),
+		Origin:   location,
+		Location: location,
+		Compress: compress,
+		Local:    true,
+	}
+
+	content, err := ioutil.ReadFile(location)
+	if err != nil {
+		return Source{}, err
+	}
+	if err := answer.setContent(content); err != nil {
+		return Source{}, err
+	}
+
+	return answer, nil
+}
diff --git a/pkg/trait/pdb.go b/pkg/trait/pdb.go
index 7b273ce6f..de1131fe0 100644
--- a/pkg/trait/pdb.go
+++ b/pkg/trait/pdb.go
@@ -19,19 +19,27 @@ package trait
 
 import (
 	"fmt"
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+
 	"k8s.io/api/policy/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
+
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 )
 
-// The Pdb trait allows to configure the PodDisruptionBudget resource.
+// The PDB trait allows to configure the PodDisruptionBudget resource for the Integration pods.
 //
 // +camel-k:trait=pdb
 type pdbTrait struct {
-	BaseTrait      `property:",squash"`
+	BaseTrait `property:",squash"`
+	// The number of pods for the Integration that must still be available after an eviction.
+	// It can be either an absolute number or a percentage.
+	// Only one of `min-available` and `max-unavailable` can be specified.
+	MinAvailable string `property:"min-available" json:"minAvailable,omitempty"`
+	// The number of pods for the Integration that can be unavailable after an eviction.
+	// It can be either an absolute number or a percentage (default `1` if `min-available` is also not set).
+	// Only one of `max-unavailable` and `min-available` can be specified.
 	MaxUnavailable string `property:"max-unavailable" json:"maxUnavailable,omitempty"`
-	MinAvailable   string `property:"min-available" json:"minAvailable,omitempty"`
 }
 
 func newPdbTrait() Trait {
@@ -65,45 +73,39 @@ func (t *pdbTrait) Configure(e *Environment) (bool, error) {
 }
 
 func (t *pdbTrait) Apply(e *Environment) error {
-	if pdb, err := t.generatePodDisruptionBudget(e); err == nil {
-		e.Resources.Add(pdb)
-	} else {
-		return err
-	}
-	return nil
-}
-
-func (t *pdbTrait) generatePodDisruptionBudget(e *Environment) (*v1beta1.PodDisruptionBudget, error) {
 	if t.MaxUnavailable == "" && t.MinAvailable == "" {
 		t.MaxUnavailable = "1"
 	}
 
-	integration := e.Integration
-	spec := v1beta1.PodDisruptionBudgetSpec{
-		Selector: &metav1.LabelSelector{
-			MatchLabels: map[string]string{
-				v1.IntegrationLabel: integration.Name,
+	pdb := t.podDisruptionBudgetFor(e.Integration)
+	e.Resources.Add(pdb)
+
+	return nil
+}
+
+func (t *pdbTrait) podDisruptionBudgetFor(integration *v1.Integration) *v1beta1.PodDisruptionBudget {
+	pdb := &v1beta1.PodDisruptionBudget{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      integration.Name,
+			Namespace: integration.Namespace,
+			Labels:    integration.Labels,
+		},
+		Spec: v1beta1.PodDisruptionBudgetSpec{
+			Selector: &metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					v1.IntegrationLabel: integration.Name,
+				},
 			},
 		},
 	}
 
-	var min, max intstr.IntOrString
-
 	if t.MaxUnavailable != "" {
-		max = intstr.Parse(t.MaxUnavailable)
-		spec.MaxUnavailable = &max
-
+		max := intstr.Parse(t.MaxUnavailable)
+		pdb.Spec.MaxUnavailable = &max
 	} else {
-		min = intstr.Parse(t.MinAvailable)
-		spec.MinAvailable = &min
+		min := intstr.Parse(t.MinAvailable)
+		pdb.Spec.MinAvailable = &min
 	}
 
-	return &v1beta1.PodDisruptionBudget{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      integration.Name,
-			Namespace: integration.Namespace,
-			Labels:    integration.Labels,
-		},
-		Spec: spec,
-	}, nil
+	return pdb
 }


Test patch:
diff --git a/pkg/cmd/local_run_test.go b/pkg/cmd/local_run_test.go
new file mode 100644
index 000000000..72f84f232
--- /dev/null
+++ b/pkg/cmd/local_run_test.go
@@ -0,0 +1,78 @@
+/*
+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 (
+	"testing"
+
+	"github.com/apache/camel-k/pkg/util/test"
+	"github.com/spf13/cobra"
+)
+
+func addTestLocalRunCmd(rootCmdOptions *RootCmdOptions, rootCmd *cobra.Command) *localRunCmdOptions {
+	//add a testing version of run Command
+	localCmd := newCmdLocal(rootCmdOptions)
+	localRunCmd, localRunCmdOptions := newCmdLocalRun(rootCmdOptions)
+	localRunCmd.RunE = func(c *cobra.Command, args []string) error {
+		return nil
+	}
+	localRunCmd.Args = test.ArbitraryArgs
+	localCmd.AddCommand(localRunCmd)
+	rootCmd.AddCommand(localCmd)
+	return localRunCmdOptions
+}
+
+func TestLocalRunPropertyFileFlag(t *testing.T) {
+	options, rootCmd := kamelTestPreAddCommandInit()
+
+	localRunCmdOptions := addTestLocalRunCmd(options, rootCmd)
+
+	kamelTestPostAddCommandInit(t, rootCmd)
+
+	_, err := test.ExecuteCommand(rootCmd, "local", "run", "route.java", "--property-file", "file1.properties", "--property-file", "file2.properties")
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+	}
+
+	if len(localRunCmdOptions.PropertyFiles) != 2 {
+		t.Fatalf("Property files expected to contain: \n %v elements\nGot:\n %v elements\n", 2, len(localRunCmdOptions.PropertyFiles))
+	}
+	if localRunCmdOptions.PropertyFiles[0] != "file1.properties" || localRunCmdOptions.PropertyFiles[1] != "file2.properties" {
+		t.Fatalf("Property files expected to be: \n %v\nGot:\n %v\n", "[file1.properties, file2.properties]", localRunCmdOptions.PropertyFiles)
+	}
+}
+
+func TestLocalRunAdditionalDependenciesFlag(t *testing.T) {
+	options, rootCmd := kamelTestPreAddCommandInit()
+
+	localRunCmdOptions := addTestLocalRunCmd(options, rootCmd)
+
+	kamelTestPostAddCommandInit(t, rootCmd)
+
+	_, err := test.ExecuteCommand(rootCmd, "local", "run", "route.java", "-d", "mvn:camel-component-1", "-d", "mvn:camel-component-2")
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+	}
+
+	if len(localRunCmdOptions.AdditionalDependencies) != 2 {
+		t.Fatalf("Additional dependencies expected to contain: \n %v elements\nGot:\n %v elements\n", 2, len(localRunCmdOptions.AdditionalDependencies))
+	}
+	if localRunCmdOptions.AdditionalDependencies[0] != "mvn:camel-component-1" || localRunCmdOptions.AdditionalDependencies[1] != "mvn:camel-component-2" {
+		t.Fatalf("Additional dependencies expected to be: \n %v\nGot:\n %v\n", "[mvn:camel-component-1, mvn:camel-component-2]", localRunCmdOptions.AdditionalDependencies)
+	}
+}