You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2018/11/26 10:27:43 UTC

[camel-k] 01/03: initial support for spring boot

This is an automated email from the ASF dual-hosted git repository.

nferraro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit e0965651f0b54b80629f93c9b8d0b5da2c74deaf
Author: lburgazzoli <lb...@gmail.com>
AuthorDate: Fri Nov 23 22:04:42 2018 +0100

    initial support for spring boot
---
 pkg/apis/camel/v1alpha1/types.go                   |   1 +
 pkg/builder/builder.go                             |  11 +-
 pkg/builder/builder_steps.go                       |  40 +++--
 pkg/builder/builder_types.go                       |  29 ++--
 pkg/builder/springboot/dependencies.go             |  37 +++++
 pkg/builder/springboot/generator.go                | 161 +++++++++++++++++++
 pkg/builder/springboot/initializer.go              |  34 +++++
 pkg/stub/action/integration/build.go               |   1 +
 pkg/trait/catalog.go                               |  33 +++-
 pkg/trait/deployment.go                            |   5 +
 pkg/trait/springboot.go                            | 103 +++++++++++++
 pkg/trait/trait.go                                 |   1 +
 pkg/trait/types.go                                 |   1 +
 pkg/util/maven/maven_project.go                    |  24 ++-
 pkg/util/tar/appender.go                           |  32 ++++
 runtime/examples/hello.xml                         |   6 +-
 .../org/apache/camel/k/groovy/LoaderTest.groovy    |   5 +-
 .../java/org/apache/camel/k/jvm/Application.java   |   4 -
 .../main/java/org/apache/camel/k/jvm/Runtime.java  |   4 +-
 .../org/apache/camel/k/jvm/RuntimeRegistry.java    |  83 +---------
 .../org/apache/camel/k/jvm/RuntimeSupport.java     |   9 +-
 ...imeRegistry.java => SimpleRuntimeRegistry.java} |   4 +-
 .../org/apache/camel/k/jvm/RoutesLoadersTest.java  |  10 +-
 .../kotlin/org/apache/camel/k/kotlin/LoaderTest.kt |   4 +-
 runtime/pom.xml                                    |   2 +
 runtime/spring-boot-example/.gitignore             |  10 ++
 runtime/spring-boot-example/pom.xml                |  90 +++++++++++
 runtime/spring-boot/.gitignore                     |  10 ++
 runtime/spring-boot/pom.xml                        | 123 +++++++++++++++
 .../apache/camel/k/spring/boot/Application.java    | 170 +++++++++++++++++++++
 test/build_manager_integration_test.go             |   1 -
 31 files changed, 904 insertions(+), 144 deletions(-)

diff --git a/pkg/apis/camel/v1alpha1/types.go b/pkg/apis/camel/v1alpha1/types.go
index acb2270..901c001 100644
--- a/pkg/apis/camel/v1alpha1/types.go
+++ b/pkg/apis/camel/v1alpha1/types.go
@@ -286,4 +286,5 @@ const (
 type Artifact struct {
 	ID       string `json:"id" yaml:"id"`
 	Location string `json:"location,omitempty" yaml:"location,omitempty"`
+	Target   string `json:"target,omitempty" yaml:"target,omitempty"`
 }
diff --git a/pkg/builder/builder.go b/pkg/builder/builder.go
index 1e120f6..a04f3d8 100644
--- a/pkg/builder/builder.go
+++ b/pkg/builder/builder.go
@@ -141,10 +141,12 @@ func (b *defaultBuilder) submit(request Request) {
 	b.request.Store(request.Meta.Name, r)
 
 	c := Context{
-		C:         b.ctx,
-		Path:      builderPath,
-		Namespace: b.namespace,
-		Request:   request,
+		C:                b.ctx,
+		Path:             builderPath,
+		Namespace:        b.namespace,
+		Request:          request,
+		ComputeClasspath: true,
+		Image:            "fabric8/s2i-java:2.3", // TODO: externalize
 	}
 
 	// Sort steps by phase
@@ -152,6 +154,7 @@ func (b *defaultBuilder) submit(request Request) {
 		return request.Steps[i].Phase() < request.Steps[j].Phase()
 	})
 
+	b.log.Infof("steps: %v", request.Steps)
 	for _, step := range request.Steps {
 		if c.Error != nil {
 			break
diff --git a/pkg/builder/builder_steps.go b/pkg/builder/builder_steps.go
index b29e81e..f0eaa58 100644
--- a/pkg/builder/builder_steps.go
+++ b/pkg/builder/builder_steps.go
@@ -154,6 +154,7 @@ func ComputeDependencies(ctx *Context) error {
 		ctx.Artifacts = append(ctx.Artifacts, v1alpha1.Artifact{
 			ID:       e.ID,
 			Location: e.Location,
+			Target:   "dependencies",
 		})
 	}
 
@@ -166,7 +167,7 @@ type ArtifactsSelector func([]v1alpha1.Artifact) (string, []v1alpha1.Artifact, e
 // StandardPackager --
 func StandardPackager(ctx *Context) error {
 	return packager(ctx, func(libraries []v1alpha1.Artifact) (string, []v1alpha1.Artifact, error) {
-		return "fabric8/s2i-java:2.3", libraries, nil
+		return ctx.Image, libraries, nil
 	})
 }
 
@@ -191,15 +192,19 @@ func IncrementalPackager(ctx *Context) error {
 		}
 
 		// return default selection
-		return "fabric8/s2i-java:2.3", libraries, nil
+		return ctx.Image, libraries, nil
 	})
 }
 
+// ClassPathPackager --
 func packager(ctx *Context, selector ArtifactsSelector) error {
 	imageName, selectedArtifacts, err := selector(ctx.Artifacts)
 	if err != nil {
 		return err
 	}
+	if imageName == "" {
+		imageName = ctx.Image
+	}
 
 	tarFileName := path.Join(ctx.Path, "package", "occi.tar")
 	tarFileDir := path.Dir(tarFileName)
@@ -215,38 +220,43 @@ func packager(ctx *Context, selector ArtifactsSelector) error {
 	}
 	defer tarAppender.Close()
 
-	tarDir := "dependencies/"
 	for _, entry := range selectedArtifacts {
 		gav, err := maven.ParseGAV(entry.ID)
 		if err != nil {
 			return err
 		}
 
-		tarPath := path.Join(tarDir, gav.GroupID)
-		_, err = tarAppender.AddFile(entry.Location, tarPath)
+		_, fileName := path.Split(entry.Location)
+
+		_, err = tarAppender.AddFileWithName(gav.GroupID+"."+fileName, entry.Location, entry.Target)
 		if err != nil {
 			return err
 		}
 	}
 
-	cp := ""
-	for _, entry := range ctx.Artifacts {
-		gav, err := maven.ParseGAV(entry.ID)
+	if ctx.ComputeClasspath {
+		cp := ""
+		for _, entry := range ctx.Artifacts {
+			gav, err := maven.ParseGAV(entry.ID)
+			if err != nil {
+				return nil
+			}
+			_, fileName := path.Split(entry.Location)
+			cp += path.Join(entry.Target, gav.GroupID+"."+fileName) + "\n"
+		}
+
+		err = tarAppender.AppendData([]byte(cp), "classpath")
 		if err != nil {
-			return nil
+			return err
 		}
-		tarPath := path.Join(tarDir, gav.GroupID)
-		_, fileName := path.Split(entry.Location)
-		fileName = path.Join(tarPath, fileName)
-		cp += fileName + "\n"
 	}
 
-	err = tarAppender.AppendData([]byte(cp), "classpath")
+	err = tarAppender.AppendData([]byte(""), "marker")
 	if err != nil {
 		return err
 	}
 
-	ctx.Image = imageName //"fabric8/s2i-java:2.3"
+	ctx.Image = imageName
 	ctx.Archive = tarFileName
 
 	return nil
diff --git a/pkg/builder/builder_types.go b/pkg/builder/builder_types.go
index e23dfc0..a81f879 100644
--- a/pkg/builder/builder_types.go
+++ b/pkg/builder/builder_types.go
@@ -31,6 +31,8 @@ import (
 )
 
 const (
+	// IntiPhase --
+	IntiPhase int32 = 0
 	// ProjectGenerationPhase --
 	ProjectGenerationPhase int32 = 10
 	// ProjectBuildPhase --
@@ -59,7 +61,7 @@ type Step interface {
 type stepWrapper struct {
 	id    string
 	phase int32
-	task  func(*Context) error
+	task  StepTask
 }
 
 func (s *stepWrapper) String() string {
@@ -78,8 +80,11 @@ func (s *stepWrapper) Execute(ctx *Context) error {
 	return s.task(ctx)
 }
 
+// StepTask ---
+type StepTask func(*Context) error
+
 // NewStep --
-func NewStep(ID string, phase int32, task func(*Context) error) Step {
+func NewStep(ID string, phase int32, task StepTask) Step {
 	s := stepWrapper{
 		id:    ID,
 		phase: phase,
@@ -123,15 +128,17 @@ type Result struct {
 
 // Context --
 type Context struct {
-	C         context.Context
-	Request   Request
-	Image     string
-	Error     error
-	Namespace string
-	Project   maven.Project
-	Path      string
-	Artifacts []v1alpha1.Artifact
-	Archive   string
+	C                context.Context
+	Request          Request
+	Image            string
+	Error            error
+	Namespace        string
+	Project          maven.Project
+	Path             string
+	Artifacts        []v1alpha1.Artifact
+	Archive          string
+	ComputeClasspath bool
+	MainClass        string
 }
 
 // PublishedImage --
diff --git a/pkg/builder/springboot/dependencies.go b/pkg/builder/springboot/dependencies.go
new file mode 100644
index 0000000..6c3aec1
--- /dev/null
+++ b/pkg/builder/springboot/dependencies.go
@@ -0,0 +1,37 @@
+/*
+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 springboot
+
+import (
+	"strings"
+
+	"github.com/apache/camel-k/pkg/builder"
+)
+
+// ComputeDependencies --
+func ComputeDependencies(ctx *builder.Context) error {
+	for i := 0; i < len(ctx.Artifacts); i++ {
+		if strings.HasPrefix(ctx.Artifacts[i].ID, "org.apache.camel.k:camel-k-runtime-spring-boot:") {
+			// Don't set a target so the jar will be copied to the
+			// deployment root
+			ctx.Artifacts[i].Target = ""
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/builder/springboot/generator.go b/pkg/builder/springboot/generator.go
new file mode 100644
index 0000000..b812046
--- /dev/null
+++ b/pkg/builder/springboot/generator.go
@@ -0,0 +1,161 @@
+/*
+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 springboot
+
+import (
+	"encoding/xml"
+	"fmt"
+	"strings"
+
+	"github.com/apache/camel-k/pkg/builder"
+	"github.com/apache/camel-k/pkg/util/maven"
+	"github.com/apache/camel-k/version"
+)
+
+// GenerateProject --
+func GenerateProject(ctx *builder.Context) error {
+	ctx.Project = maven.Project{
+		XMLName:           xml.Name{Local: "project"},
+		XMLNs:             "http://maven.apache.org/POM/4.0.0",
+		XMLNsXsi:          "http://www.w3.org/2001/XMLSchema-instance",
+		XsiSchemaLocation: "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd",
+		ModelVersion:      "4.0.0",
+		GroupID:           "org.apache.camel.k.integration",
+		ArtifactID:        "camel-k-integration",
+		Version:           version.Version,
+		DependencyManagement: maven.DependencyManagement{
+			Dependencies: maven.Dependencies{
+				Dependencies: []maven.Dependency{
+					{
+						//TODO: camel version should be retrieved from an external request or provided as static version
+						GroupID:    "org.apache.camel",
+						ArtifactID: "camel-bom",
+						Version:    "2.22.2",
+						Type:       "pom",
+						Scope:      "import",
+					},
+				},
+			},
+		},
+		Dependencies: maven.Dependencies{
+			Dependencies: make([]maven.Dependency, 0),
+		},
+	}
+
+	//
+	// set-up dependencies
+	//
+
+	deps := &ctx.Project.Dependencies
+
+	//
+	// common
+	//
+
+	deps.Add(maven.Dependency{
+		GroupID:    "org.apache.camel.k",
+		ArtifactID: "camel-k-runtime-spring-boot",
+		Version:    version.Version,
+		Exclusions: &maven.Exclusions{
+			Exclusions: []maven.Exclusion{
+				{
+					GroupID:    "org.apache.camel",
+					ArtifactID: "*",
+				},
+				{
+					GroupID:    "org.apache.camel.k",
+					ArtifactID: "*",
+				},
+				{
+					GroupID:    "org.springframework.boot",
+					ArtifactID: "*",
+				},
+			},
+		},
+	})
+
+	//
+	// others
+	//
+
+	for _, d := range ctx.Request.Dependencies {
+		if strings.HasPrefix(d, "camel:") {
+			if d == "camel:core" {
+				continue
+			}
+
+			artifactID := strings.TrimPrefix(d, "camel:")
+
+			if !strings.HasPrefix(artifactID, "camel-") {
+				artifactID = "camel-" + artifactID
+			}
+
+			deps.Add(maven.Dependency{
+				GroupID:    "org.apache.camel",
+				ArtifactID: artifactID + "-starter",
+				Version:    "2.22.2",
+				Exclusions: &maven.Exclusions{
+					Exclusions: []maven.Exclusion{
+						{
+							GroupID:    "com.sun.xml.bind",
+							ArtifactID: "*",
+						},
+						{
+							GroupID:    "org.apache.camel",
+							ArtifactID: "camel-core",
+						},
+						{
+							GroupID:    "org.apache.camel",
+							ArtifactID: "camel-core-starter",
+						},
+						{
+							GroupID:    "org.apache.camel",
+							ArtifactID: "camel-spring-boot-starter",
+						},
+						{
+							GroupID:    "org.springframework.boot",
+							ArtifactID: "spring-boot-starter",
+						},
+					},
+				},
+			})
+		} else if strings.HasPrefix(d, "mvn:") {
+			mid := strings.TrimPrefix(d, "mvn:")
+			gav := strings.Replace(mid, "/", ":", -1)
+
+			deps.AddEncodedGAV(gav)
+		} else if strings.HasPrefix(d, "runtime:") {
+			if d == "runtime:jvm" {
+				// common
+				continue
+			}
+			if d == "runtime:spring-boot" {
+				// common
+				continue
+			}
+
+			artifactID := strings.Replace(d, "runtime:", "camel-k-runtime-", 1)
+
+			deps.AddGAV("org.apache.camel.k", artifactID, version.Version)
+		} else {
+			return fmt.Errorf("unknown dependency type: %s", d)
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/builder/springboot/initializer.go b/pkg/builder/springboot/initializer.go
new file mode 100644
index 0000000..de40ea6
--- /dev/null
+++ b/pkg/builder/springboot/initializer.go
@@ -0,0 +1,34 @@
+/*
+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 springboot
+
+import (
+	"github.com/apache/camel-k/pkg/builder"
+)
+
+// Initialize --
+func Initialize(ctx *builder.Context) error {
+	// set the base image
+	//ctx.Image = "kamel-k/s2i-boot:" + version.Version
+
+	// no need to compute classpath as we do use spring boot own
+	// loader: PropertiesLauncher
+	ctx.ComputeClasspath = false
+
+	return nil
+}
diff --git a/pkg/stub/action/integration/build.go b/pkg/stub/action/integration/build.go
index 02387ce..e4660c8 100644
--- a/pkg/stub/action/integration/build.go
+++ b/pkg/stub/action/integration/build.go
@@ -119,6 +119,7 @@ func (action *buildAction) Handle(integration *v1alpha1.Integration) error {
 	platformCtx.Spec = v1alpha1.IntegrationContextSpec{
 		Dependencies: integration.Spec.Dependencies,
 		Repositories: integration.Spec.Repositories,
+		Traits:       integration.Spec.Traits,
 	}
 
 	if err := sdk.Create(&platformCtx); err != nil {
diff --git a/pkg/trait/catalog.go b/pkg/trait/catalog.go
index 9db06ab..3eba945 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/catalog.go
@@ -21,6 +21,8 @@ import (
 	"reflect"
 	"strings"
 
+	"github.com/sirupsen/logrus"
+
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/platform"
 	"github.com/fatih/structs"
@@ -36,6 +38,7 @@ type Catalog struct {
 	tIngress      Trait
 	tOwner        Trait
 	tBuilder      Trait
+	tSpringBoot   Trait
 }
 
 // NewCatalog creates a new trait Catalog
@@ -49,6 +52,7 @@ func NewCatalog() *Catalog {
 		tIngress:      newIngressTrait(),
 		tOwner:        newOwnerTrait(),
 		tBuilder:      newBuilderTrait(),
+		tSpringBoot:   newSpringBootTrait(),
 	}
 }
 
@@ -62,6 +66,7 @@ func (c *Catalog) allTraits() []Trait {
 		c.tIngress,
 		c.tOwner,
 		c.tBuilder,
+		c.tSpringBoot,
 	}
 }
 
@@ -78,20 +83,22 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
 	case v1alpha1.TraitProfileOpenShift:
 		return []Trait{
 			c.tDependencies,
-			c.tDeployment,
 			c.tService,
 			c.tRoute,
 			c.tOwner,
 			c.tBuilder,
+			c.tSpringBoot,
+			c.tDeployment,
 		}
 	case v1alpha1.TraitProfileKubernetes:
 		return []Trait{
 			c.tDependencies,
-			c.tDeployment,
 			c.tService,
 			c.tIngress,
 			c.tOwner,
 			c.tBuilder,
+			c.tSpringBoot,
+			c.tDeployment,
 		}
 	case v1alpha1.TraitProfileKnative:
 		return []Trait{
@@ -99,6 +106,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
 			c.tKnative,
 			c.tOwner,
 			c.tBuilder,
+			c.tSpringBoot,
 		}
 	}
 
@@ -108,6 +116,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
 func (c *Catalog) apply(environment *Environment) error {
 	c.configure(environment)
 	traits := c.traitsFor(environment)
+
 	for _, trait := range traits {
 		if !trait.appliesTo(environment) {
 			continue
@@ -119,6 +128,7 @@ func (c *Catalog) apply(environment *Environment) error {
 			}
 		}
 		if trait.IsEnabled() {
+			logrus.Infof("apply trait: %s", trait.ID())
 			if err := trait.apply(environment); err != nil {
 				return err
 			}
@@ -139,13 +149,20 @@ func (c *Catalog) GetTrait(id string) Trait {
 }
 
 func (c *Catalog) configure(env *Environment) {
-	if env.Integration == nil || env.Integration.Spec.Traits == nil {
-		return
+	if env.Context != nil && env.Context.Spec.Traits != nil {
+		for id, traitSpec := range env.Context.Spec.Traits {
+			catTrait := c.GetTrait(id)
+			if catTrait != nil {
+				traitSpec.Decode(catTrait)
+			}
+		}
 	}
-	for id, traitSpec := range env.Integration.Spec.Traits {
-		catTrait := c.GetTrait(id)
-		if catTrait != nil {
-			traitSpec.Decode(catTrait)
+	if env.Integration != nil && env.Integration.Spec.Traits != nil {
+		for id, traitSpec := range env.Integration.Spec.Traits {
+			catTrait := c.GetTrait(id)
+			if catTrait != nil {
+				traitSpec.Decode(catTrait)
+			}
 		}
 	}
 }
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index bc3f121..8411e3a 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -146,6 +146,11 @@ func getDeploymentFor(e *Environment) *appsv1.Deployment {
 	// optimizations
 	environment["AB_JOLOKIA_OFF"] = "true"
 
+	// add env vars from traits
+	for k, v := range e.EnvVars {
+		environment[k] = v
+	}
+
 	labels := map[string]string{
 		"camel.apache.org/integration": e.Integration.Name,
 	}
diff --git a/pkg/trait/springboot.go b/pkg/trait/springboot.go
new file mode 100644
index 0000000..7069d67
--- /dev/null
+++ b/pkg/trait/springboot.go
@@ -0,0 +1,103 @@
+/*
+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 (
+	"sort"
+
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/apache/camel-k/pkg/builder"
+	"github.com/apache/camel-k/pkg/builder/springboot"
+	"github.com/apache/camel-k/pkg/util"
+)
+
+type springBootTrait struct {
+	BaseTrait `property:",squash"`
+}
+
+func newSpringBootTrait() *springBootTrait {
+	return &springBootTrait{
+		BaseTrait: newBaseTrait("springboot"),
+	}
+}
+
+// IsAuto determines if we should apply automatic configuration
+func (trait *springBootTrait) IsAuto() bool {
+	return false
+}
+
+// IsEnabled is used to determine if the trait needs to be executed
+func (trait *springBootTrait) IsEnabled() bool {
+	if trait.Enabled == nil {
+		return false
+	}
+	return *trait.Enabled
+}
+
+func (trait *springBootTrait) appliesTo(e *Environment) bool {
+	if e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseBuilding {
+		return true
+	}
+	if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying {
+		return true
+	}
+	if e.Integration != nil && e.Integration.Status.Phase == "" {
+		return true
+	}
+
+	return false
+}
+
+func (trait *springBootTrait) apply(e *Environment) error {
+
+	//
+	// Integration
+	//
+
+	if e.Integration != nil && e.Integration.Status.Phase == "" {
+		util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, "runtime:spring-boot")
+
+		// sort the dependencies to get always the same list if they don't change
+		sort.Strings(e.Integration.Spec.Dependencies)
+	}
+
+	if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying {
+		// Override env vars
+		e.EnvVars["JAVA_MAIN_CLASS"] = "org.springframework.boot.loader.PropertiesLauncher"
+		e.EnvVars["LOADER_PATH"] = "/deployments/dependencies/"
+	}
+
+	//
+	// Integration Context
+	//
+
+	if e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseBuilding {
+		// add custom initialization logic
+		e.Steps = append(e.Steps, builder.NewStep("initialize/spring-boot", builder.IntiPhase, springboot.Initialize))
+		e.Steps = append(e.Steps, builder.NewStep("build/compute-boot-dependencies", builder.ProjectBuildPhase+1, springboot.ComputeDependencies))
+
+		// replace project generator
+		for i := 0; i < len(e.Steps); i++ {
+			if e.Steps[i].Phase() == builder.ProjectGenerationPhase {
+				e.Steps[i] = builder.NewStep("generate/spring-boot", builder.ProjectGenerationPhase, springboot.GenerateProject)
+			}
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/trait/trait.go b/pkg/trait/trait.go
index 1ab5f9e..042b4d5 100644
--- a/pkg/trait/trait.go
+++ b/pkg/trait/trait.go
@@ -72,5 +72,6 @@ func newEnvironment(integration *v1alpha1.Integration, ctx *v1alpha1.Integration
 		Integration:    integration,
 		ExecutedTraits: make([]ID, 0),
 		Resources:      kubernetes.NewCollection(),
+		EnvVars:        make(map[string]string),
 	}, nil
 }
diff --git a/pkg/trait/types.go b/pkg/trait/types.go
index 8c1d09a..6b87029 100644
--- a/pkg/trait/types.go
+++ b/pkg/trait/types.go
@@ -100,6 +100,7 @@ type Environment struct {
 	Resources      *kubernetes.Collection
 	Steps          []builder.Step
 	ExecutedTraits []ID
+	EnvVars        map[string]string
 }
 
 // IntegrationInPhase --
diff --git a/pkg/util/maven/maven_project.go b/pkg/util/maven/maven_project.go
index 5c7ec5d..57603a1 100644
--- a/pkg/util/maven/maven_project.go
+++ b/pkg/util/maven/maven_project.go
@@ -73,14 +73,26 @@ func (deps *Dependencies) AddEncodedGAV(gav string) {
 	}
 }
 
-// Dependency represent a maven's dependency
-type Dependency struct {
+// Exclusion represent a maven's dependency exlucsion
+type Exclusion struct {
 	GroupID    string `xml:"groupId"`
 	ArtifactID string `xml:"artifactId"`
-	Version    string `xml:"version,omitempty"`
-	Type       string `xml:"type,omitempty"`
-	Classifier string `xml:"classifier,omitempty"`
-	Scope      string `xml:"scope,omitempty"`
+}
+
+// Exclusions --
+type Exclusions struct {
+	Exclusions []Exclusion `xml:"exclusion"`
+}
+
+// Dependency represent a maven's dependency
+type Dependency struct {
+	GroupID    string      `xml:"groupId"`
+	ArtifactID string      `xml:"artifactId"`
+	Version    string      `xml:"version,omitempty"`
+	Type       string      `xml:"type,omitempty"`
+	Classifier string      `xml:"classifier,omitempty"`
+	Scope      string      `xml:"scope,omitempty"`
+	Exclusions *Exclusions `xml:"exclusions,omitempty"`
 }
 
 // NewDependency create an new dependency from the given gav info
diff --git a/pkg/util/tar/appender.go b/pkg/util/tar/appender.go
index 965bbc7..7ffbfad 100644
--- a/pkg/util/tar/appender.go
+++ b/pkg/util/tar/appender.go
@@ -92,6 +92,38 @@ func (t *Appender) AddFile(filePath string, tarDir string) (string, error) {
 	return fileName, nil
 }
 
+// AddFileWithName adds a file content to the tarDir, using the fiven file name.
+// It returns the full path of the file inside the tar.
+func (t *Appender) AddFileWithName(fileName string, filePath string, tarDir string) (string, error) {
+	info, err := os.Stat(filePath)
+	if err != nil {
+		return "", err
+	}
+	if tarDir != "" {
+		fileName = path.Join(tarDir, fileName)
+	}
+
+	t.writer.WriteHeader(&atar.Header{
+		Name:    fileName,
+		Size:    info.Size(),
+		Mode:    int64(info.Mode()),
+		ModTime: info.ModTime(),
+	})
+
+	file, err := os.Open(filePath)
+	if err != nil {
+		return "", err
+	}
+	defer file.Close()
+
+	_, err = io.Copy(t.writer, file)
+	if err != nil {
+		return "", errors.Wrap(err, "cannot add file to the tar archive")
+	}
+
+	return fileName, nil
+}
+
 // AppendData appends the given content to a file inside the tar, creating it if it does not exist
 func (t *Appender) AppendData(data []byte, tarPath string) error {
 	t.writer.WriteHeader(&atar.Header{
diff --git a/runtime/examples/hello.xml b/runtime/examples/hello.xml
index ff9bc8d..4c0535a 100644
--- a/runtime/examples/hello.xml
+++ b/runtime/examples/hello.xml
@@ -14,8 +14,10 @@
     limitations under the License.
 -->
 <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-              xmlns="http://camel.apache.org/schema/spring"
-              xsi:schemaLocation="http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
+        xmlns="http://camel.apache.org/schema/spring"
+        xsi:schemaLocation="
+            http://camel.apache.org/schema/spring
+            http://camel.apache.org/schema/spring/camel-spring.xsd">
 
     <route id="hello">
         <from uri="timer:hello?period=3s"/>
diff --git a/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/LoaderTest.groovy b/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/LoaderTest.groovy
index 16b4f0f..b7c7c95 100644
--- a/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/LoaderTest.groovy
+++ b/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/LoaderTest.groovy
@@ -16,9 +16,8 @@
  */
 package org.apache.camel.k.groovy
 
-
 import org.apache.camel.k.jvm.RoutesLoaders
-import org.apache.camel.k.jvm.RuntimeRegistry
+import org.apache.camel.k.jvm.SimpleRuntimeRegistry
 import org.apache.camel.model.ToDefinition
 import spock.lang.Specification
 
@@ -30,7 +29,7 @@ class LoaderTest extends Specification {
 
         when:
             def loader = RoutesLoaders.loaderFor(resource, null)
-            def builder = loader.load(new RuntimeRegistry(), resource)
+            def builder = loader.load(new SimpleRuntimeRegistry(), resource)
 
         then:
             loader instanceof GroovyRoutesLoader
diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Application.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Application.java
index d26ddef..69cbc96 100644
--- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Application.java
+++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Application.java
@@ -21,12 +21,8 @@ import org.apache.camel.Component;
 import org.apache.camel.main.MainListenerSupport;
 import org.apache.camel.support.LifecycleStrategySupport;
 import org.apache.camel.util.ObjectHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class Application {
-    private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
-
     static {
         //
         // Load properties as system properties so they are accessible through
diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Runtime.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Runtime.java
index d4a755c..ffa71a0 100644
--- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Runtime.java
+++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Runtime.java
@@ -36,7 +36,7 @@ public final class Runtime extends MainSupport {
     private static final Logger LOGGER = LoggerFactory.getLogger(Runtime.class);
 
     private final ConcurrentMap<String, CamelContext> contextMap;
-    private final RuntimeRegistry registry = new RuntimeRegistry();
+    private final RuntimeRegistry registry = new SimpleRuntimeRegistry();
 
     public Runtime() {
         this.contextMap = new ConcurrentHashMap<>();
@@ -53,7 +53,7 @@ public final class Runtime extends MainSupport {
             final RoutesLoader loader = RoutesLoaders.loaderFor(location, language);
             final RouteBuilder builder = loader.load(registry, location);
 
-            if (routes == null) {
+            if (builder == null) {
                 throw new IllegalStateException("Unable to load route from: " + route);
             }
 
diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeRegistry.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeRegistry.java
index 8fc7589..3b41e2b 100644
--- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeRegistry.java
+++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeRegistry.java
@@ -16,85 +16,12 @@
  */
 package org.apache.camel.k.jvm;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.stream.Collectors;
-
-import org.apache.camel.NoSuchBeanException;
 import org.apache.camel.spi.Registry;
 
-public class RuntimeRegistry implements Registry {
-    private final ConcurrentMap<String, Object> registry;
-
-    public RuntimeRegistry() {
-        this.registry = new ConcurrentHashMap<>();
-    }
-
-    public void bind(String name, Object bean) {
-        this.registry.put(name, bean);
-    }
-
-    @Override
-    public Object lookupByName(String name) {
-        return registry.get(name);
-    }
-
-    @Override
-    public <T> T lookupByNameAndType(String name, Class<T> type) {
-        final Object answer = lookupByName(name);
-
-        if (answer != null) {
-            try {
-                return type.cast(answer);
-            } catch (Throwable t) {
-                throw new NoSuchBeanException(
-                    name,
-                    "Found bean: " + name + " in RuntimeRegistry: " + this + " of type: " + answer.getClass().getName() + " expected type was: " + type,
-                    t
-                );
-            }
-        }
-
-        return null;
-    }
-
-    @Override
-    public <T> Map<String, T> findByTypeWithName(Class<T> type) {
-        final Map<String, T> result = new HashMap<>();
-
-        registry.entrySet().stream()
-            .filter(entry -> type.isInstance(entry.getValue()))
-            .forEach(entry -> result.put(entry.getKey(), type.cast(entry.getValue())));
-
-        return result;
-    }
-
-    @Override
-    public <T> Set<T> findByType(Class<T> type) {
-        return registry.values().stream()
-            .filter(type::isInstance)
-            .map(type::cast)
-            .collect(Collectors.toSet());
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public Object lookup(String name) {
-        return lookupByName(name);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public <T> T lookup(String name, Class<T> type) {
-        return lookupByNameAndType(name, type);
-    }
+public interface RuntimeRegistry extends Registry {
 
-    @SuppressWarnings("deprecation")
-    @Override
-    public <T> Map<String, T> lookupByType(Class<T> type) {
-        return findByTypeWithName(type);
-    }
+    /**
+     *
+     */
+    void bind(String name, Object bean);
 }
diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeSupport.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeSupport.java
index 8b4db47..423ee08 100644
--- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeSupport.java
+++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeSupport.java
@@ -41,7 +41,7 @@ public final class RuntimeSupport {
     private RuntimeSupport() {
     }
 
-    public static void configureSystemProperties() {
+    public static Properties loadProperties() {
         final String conf = System.getenv(Constants.ENV_CAMEL_K_CONF);
         final String confd = System.getenv(Constants.ENV_CAMEL_K_CONF_D);
         final Properties properties = new Properties();
@@ -94,6 +94,13 @@ public final class RuntimeSupport {
             }
         }
 
+        return properties;
+    }
+
+    public static void configureSystemProperties() {
+        final Properties properties = loadProperties();
+
+        // TODO: sensitive info, maybe better to use properties component ...
         System.getProperties().putAll(properties);
     }
 
diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeRegistry.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/SimpleRuntimeRegistry.java
similarity index 96%
copy from runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeRegistry.java
copy to runtime/jvm/src/main/java/org/apache/camel/k/jvm/SimpleRuntimeRegistry.java
index 8fc7589..f09b331 100644
--- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RuntimeRegistry.java
+++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/SimpleRuntimeRegistry.java
@@ -26,10 +26,10 @@ import java.util.stream.Collectors;
 import org.apache.camel.NoSuchBeanException;
 import org.apache.camel.spi.Registry;
 
-public class RuntimeRegistry implements Registry {
+public class SimpleRuntimeRegistry implements RuntimeRegistry {
     private final ConcurrentMap<String, Object> registry;
 
-    public RuntimeRegistry() {
+    public SimpleRuntimeRegistry() {
         this.registry = new ConcurrentHashMap<>();
     }
 
diff --git a/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java b/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java
index 328aaf3..069520a 100644
--- a/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java
+++ b/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java
@@ -32,7 +32,7 @@ public class RoutesLoadersTest {
     public void testLoadClass() throws Exception {
         String resource = "classpath:" + MyRoutes.class.getName() + ".class";
         RoutesLoader loader = RoutesLoaders.loaderFor(resource, null);
-        RouteBuilder builder = loader.load(new RuntimeRegistry(), resource);
+        RouteBuilder builder = loader.load(new SimpleRuntimeRegistry(), resource);
 
         assertThat(loader).isInstanceOf(RoutesLoaders.JavaClass.class);
         assertThat(builder).isNotNull();
@@ -49,7 +49,7 @@ public class RoutesLoadersTest {
     public void testLoadJava() throws Exception {
         String resource = "classpath:MyRoutes.java";
         RoutesLoader loader = RoutesLoaders.loaderFor(resource, null);
-        RouteBuilder builder = loader.load(new RuntimeRegistry(), resource);
+        RouteBuilder builder = loader.load(new SimpleRuntimeRegistry(), resource);
 
         assertThat(loader).isInstanceOf(RoutesLoaders.JavaSource.class);
         assertThat(builder).isNotNull();
@@ -66,7 +66,7 @@ public class RoutesLoadersTest {
     public void testLoadJavaScript() throws Exception {
         String resource = "classpath:routes.js";
         RoutesLoader loader = RoutesLoaders.loaderFor(resource, null);
-        RouteBuilder builder = loader.load(new RuntimeRegistry(), resource);
+        RouteBuilder builder = loader.load(new SimpleRuntimeRegistry(), resource);
 
         assertThat(loader).isInstanceOf(RoutesLoaders.JavaScript.class);
         assertThat(builder).isNotNull();
@@ -83,7 +83,7 @@ public class RoutesLoadersTest {
     public void testLoadJavaScriptWithCustomExtension() throws Exception {
         String resource = "classpath:routes.mytype";
         RoutesLoader loader = RoutesLoaders.loaderFor(resource, "js");
-        RouteBuilder builder = loader.load(new RuntimeRegistry(), resource);
+        RouteBuilder builder = loader.load(new SimpleRuntimeRegistry(), resource);
 
         assertThat(loader).isInstanceOf(RoutesLoaders.JavaScript.class);
         assertThat(builder).isNotNull();
@@ -100,7 +100,7 @@ public class RoutesLoadersTest {
     public void testLoadXml() throws Exception {
         String resource = "classpath:routes.xml";
         RoutesLoader loader = RoutesLoaders.loaderFor(resource, null);
-        RouteBuilder builder = loader.load(new RuntimeRegistry(), resource);
+        RouteBuilder builder = loader.load(new SimpleRuntimeRegistry(), resource);
 
         assertThat(loader).isInstanceOf(RoutesLoaders.Xml.class);
         assertThat(builder).isNotNull();
diff --git a/runtime/kotlin/src/test/kotlin/org/apache/camel/k/kotlin/LoaderTest.kt b/runtime/kotlin/src/test/kotlin/org/apache/camel/k/kotlin/LoaderTest.kt
index da4ab18..00dfcea 100644
--- a/runtime/kotlin/src/test/kotlin/org/apache/camel/k/kotlin/LoaderTest.kt
+++ b/runtime/kotlin/src/test/kotlin/org/apache/camel/k/kotlin/LoaderTest.kt
@@ -17,7 +17,7 @@
 package org.apache.camel.k.kotlin
 
 import org.apache.camel.k.jvm.RoutesLoaders
-import org.apache.camel.k.jvm.RuntimeRegistry
+import org.apache.camel.k.jvm.SimpleRuntimeRegistry
 import org.apache.camel.model.ProcessDefinition
 import org.apache.camel.model.ToDefinition
 import org.assertj.core.api.Assertions.assertThat
@@ -29,7 +29,7 @@ class LoaderTest {
     fun `load route from classpath`() {
         val resource = "classpath:routes.kts"
         val loader = RoutesLoaders.loaderFor(resource, null)
-        val builder = loader.load(RuntimeRegistry(), resource)
+        val builder = loader.load(SimpleRuntimeRegistry(), resource)
 
         assertThat(loader).isInstanceOf(KotlinRoutesLoader::class.java)
         assertThat(builder).isNotNull()
diff --git a/runtime/pom.xml b/runtime/pom.xml
index 3ddf47c..3be5314 100644
--- a/runtime/pom.xml
+++ b/runtime/pom.xml
@@ -48,6 +48,7 @@
         <snakeyaml.version>1.23</snakeyaml.version>
         <spock.version>1.0-groovy-2.4</spock.version>
         <jackson.version>2.9.7</jackson.version>
+        <spring-boot.version>2.0.3.RELEASE</spring-boot.version>
 
         <gmavenplus-plugin.version>1.6.1</gmavenplus-plugin.version>
         <fabric8-maven-plugin.version>3.5.40</fabric8-maven-plugin.version>
@@ -100,6 +101,7 @@
         <module>jvm</module>
         <module>groovy</module>
         <module>kotlin</module>
+        <module>spring-boot</module>
         <module>catalog-builder</module>
         <module>dependency-lister</module>
         <module>camel-knative-http</module>
diff --git a/runtime/spring-boot-example/.gitignore b/runtime/spring-boot-example/.gitignore
new file mode 100644
index 0000000..ed92983
--- /dev/null
+++ b/runtime/spring-boot-example/.gitignore
@@ -0,0 +1,10 @@
+target
+
+*.iml
+
+.idea
+.project
+.metadata
+.settings
+.factorypath
+.classpath
diff --git a/runtime/spring-boot-example/pom.xml b/runtime/spring-boot-example/pom.xml
new file mode 100644
index 0000000..4e98e33
--- /dev/null
+++ b/runtime/spring-boot-example/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.apache.camel.k</groupId>
+        <artifactId>camel-k-runtime-parent</artifactId>
+        <version>0.0.6-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>camel-k-runtime-spring-boot-example</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.camel.k</groupId>
+            <artifactId>camel-k-runtime-spring-boot</artifactId>
+            <version>${project.version}</version>
+           <exclusions>
+                <exclusion>
+                    <groupId>org.apache.camel</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.camel.k</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-dns-starter</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.sun.xml.bind</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.camel</groupId>
+                    <artifactId>camel-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.camel</groupId>
+                    <artifactId>camel-core-starter</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.camel</groupId>
+                    <artifactId>camel-spring-boot-starter</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.camel.k</groupId>
+                <artifactId>camel-k-runtime-dependency-lister</artifactId>
+                <version>${project.version}</version>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/runtime/spring-boot/.gitignore b/runtime/spring-boot/.gitignore
new file mode 100644
index 0000000..ed92983
--- /dev/null
+++ b/runtime/spring-boot/.gitignore
@@ -0,0 +1,10 @@
+target
+
+*.iml
+
+.idea
+.project
+.metadata
+.settings
+.factorypath
+.classpath
diff --git a/runtime/spring-boot/pom.xml b/runtime/spring-boot/pom.xml
new file mode 100644
index 0000000..1e822ff
--- /dev/null
+++ b/runtime/spring-boot/pom.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.apache.camel.k</groupId>
+        <artifactId>camel-k-runtime-parent</artifactId>
+        <version>0.0.6-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>camel-k-runtime-spring-boot</artifactId>
+
+    <properties>
+        <jar_file>${project.build.directory}/${project.build.finalName}.jar</jar_file>
+    </properties>
+
+    <dependencies>
+
+        <!-- ****************************** -->
+        <!--                                -->
+        <!-- RUNTIME                        -->
+        <!--                                -->
+        <!-- ****************************** -->
+
+        <dependency>
+            <groupId>org.apache.camel.k</groupId>
+            <artifactId>camel-k-runtime-jvm</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-slf4j-impl</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-spring-boot-starter</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-log4j2</artifactId>
+            <version>${spring-boot.version}</version>
+        </dependency>
+
+        <!-- ****************************** -->
+        <!--                                -->
+        <!-- TESTS                          -->
+        <!--                                -->
+        <!-- ****************************** -->
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>${junit-jupiter.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>${junit-jupiter.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>${assertj.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <mainClass>org.apache.camel.k.spring.boot.Application</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/runtime/spring-boot/src/main/java/org/apache/camel/k/spring/boot/Application.java b/runtime/spring-boot/src/main/java/org/apache/camel/k/spring/boot/Application.java
new file mode 100644
index 0000000..9ff694c
--- /dev/null
+++ b/runtime/spring-boot/src/main/java/org/apache/camel/k/spring/boot/Application.java
@@ -0,0 +1,170 @@
+/**
+ * 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 org.apache.camel.k.spring.boot;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.k.jvm.Constants;
+import org.apache.camel.k.jvm.RoutesLoader;
+import org.apache.camel.k.jvm.RoutesLoaders;
+import org.apache.camel.k.jvm.RuntimeRegistry;
+import org.apache.camel.k.jvm.RuntimeSupport;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.URISupport;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+
+@SpringBootApplication
+public class Application {
+    private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+
+    // *****************************
+    //
+    // Beans
+    //
+    // *****************************
+
+    @Bean
+    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
+        // load properties using default behaviour
+        final Properties properties = RuntimeSupport.loadProperties();
+
+        // set spring boot specific properties
+        properties.put("camel.springboot.main-run-controller", "true");
+        properties.put("camel.springboot.name", "camel-1");
+        properties.put("camel.springboot.streamCachingEnabled", "true");
+        properties.put("camel.springboot.xml-routes", "false");
+        properties.put("camel.springboot.xml-rests", "false");
+        properties.put("camel.springboot.jmx-enabled", "false");
+
+        // set loaded properties as default properties
+        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
+        configurer.setProperties(properties);
+
+        return configurer;
+    }
+
+    @Bean
+    public RouteBuilder routes(ConfigurableApplicationContext applicationContext) throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                final CamelContext context = getContext();
+                final RuntimeRegistry registry = new RuntimeApplicationContextRegistry(applicationContext, context.getRegistry());
+                final String routes = System.getenv(Constants.ENV_CAMEL_K_ROUTES);
+
+                if (ObjectHelper.isEmpty(routes)) {
+                    throw new IllegalStateException("No valid routes found in " + Constants.ENV_CAMEL_K_ROUTES + " environment variable");
+                }
+
+                for (String route: routes.split(",")) {
+                    // determine location and language
+                    final String location = StringUtils.substringBefore(route, "?");
+                    final String query = StringUtils.substringAfter(route, "?");
+                    final String language = (String) URISupport.parseQuery(query).get("language");
+
+                    // load routes
+                    final RoutesLoader loader = RoutesLoaders.loaderFor(location, language);
+                    final RouteBuilder builder = loader.load(registry, location);
+
+                    if (builder == null) {
+                        throw new IllegalStateException("Unable to load route from: " + route);
+                    }
+
+                    LOGGER.info("Routes: {}", route);
+
+                    context.addRoutes(builder);
+                }
+            }
+        };
+    }
+
+    // *****************************
+    //
+    // Registry
+    //
+    // *****************************
+
+    private static class RuntimeApplicationContextRegistry implements RuntimeRegistry {
+        private final ConfigurableApplicationContext applicationContext;
+        private final Registry registry;
+
+        public RuntimeApplicationContextRegistry(ConfigurableApplicationContext applicationContext, Registry registry) {
+            this.applicationContext = applicationContext;
+            this.registry = registry;
+        }
+
+        @Override
+        public Object lookupByName(String name) {
+            return registry.lookupByName(name);
+        }
+
+        @Override
+        public <T> T lookupByNameAndType(String name, Class<T> type) {
+            return registry.lookupByNameAndType(name, type);
+        }
+
+        @Override
+        public <T> Map<String, T> findByTypeWithName(Class<T> type) {
+            return registry.findByTypeWithName(type);
+        }
+
+        @Override
+        public <T> Set<T> findByType(Class<T> type) {
+            return registry.findByType(type);
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public Object lookup(String name) {
+            return registry.lookup(name);
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public <T> T lookup(String name, Class<T> type) {
+            return registry.lookup(name, type);
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public <T> Map<String, T> lookupByType(Class<T> type) {
+            return registry.lookupByType(type);
+        }
+
+        @Override
+        public void bind(String name, Object bean) {
+            applicationContext.getBeanFactory().registerSingleton(name, bean);
+        }
+    }
+
+}
diff --git a/test/build_manager_integration_test.go b/test/build_manager_integration_test.go
index 09bc0e7..1e8a10c 100644
--- a/test/build_manager_integration_test.go
+++ b/test/build_manager_integration_test.go
@@ -107,5 +107,4 @@ func TestBuildManagerFailedBuild(t *testing.T) {
 
 	assert.Equal(t, builder.StatusError, result.Status)
 	assert.NotEqual(t, builder.StatusCompleted, result.Status)
-	assert.Empty(t, result.Image)
 }