git.net

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GitHub] nicolaferraro closed pull request #235: Support for multiple integration definitions


nicolaferraro closed pull request #235: Support for multiple integration definitions
URL: https://github.com/apache/camel-k/pull/235
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/pkg/apis/camel/v1alpha1/types.go b/pkg/apis/camel/v1alpha1/types.go
index b872ac3c..691b5284 100644
--- a/pkg/apis/camel/v1alpha1/types.go
+++ b/pkg/apis/camel/v1alpha1/types.go
@@ -50,7 +50,7 @@ type Integration struct {
 // IntegrationSpec --
 type IntegrationSpec struct {
 	Replicas      *int32                          `json:"replicas,omitempty"`
-	Source        SourceSpec                      `json:"source,omitempty"`
+	Sources       []SourceSpec                    `json:"sources,omitempty"`
 	Context       string                          `json:"context,omitempty"`
 	Dependencies  []string                        `json:"dependencies,omitempty"`
 	Profile       TraitProfile                    `json:"profile,omitempty"`
@@ -58,6 +58,11 @@ type IntegrationSpec struct {
 	Configuration []ConfigurationSpec             `json:"configuration,omitempty"`
 }
 
+// AddSource --
+func (is *IntegrationSpec) AddSource(name string, content string, language Language) {
+	is.Sources = append(is.Sources, SourceSpec{Name: name, Content: content, Language: language})
+}
+
 // SourceSpec --
 type SourceSpec struct {
 	Name     string   `json:"name,omitempty"`
diff --git a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
index 202fd251..4aa466bd 100644
--- a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
@@ -337,28 +337,32 @@ func (in *IntegrationPlatformStatus) DeepCopy() *IntegrationPlatformStatus {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *IntegrationSpec) DeepCopyInto(out *IntegrationSpec) {
-	*out = *in
-	if in.Replicas != nil {
-		in, out := &in.Replicas, &out.Replicas
+func (is *IntegrationSpec) DeepCopyInto(out *IntegrationSpec) {
+	*out = *is
+	if is.Replicas != nil {
+		in, out := &is.Replicas, &out.Replicas
 		*out = new(int32)
 		**out = **in
 	}
-	out.Source = in.Source
-	if in.Dependencies != nil {
-		in, out := &in.Dependencies, &out.Dependencies
+	if is.Sources != nil {
+		in, out := &is.Sources, &out.Sources
+		*out = make([]SourceSpec, len(*in))
+		copy(*out, *in)
+	}
+	if is.Dependencies != nil {
+		in, out := &is.Dependencies, &out.Dependencies
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
-	if in.Traits != nil {
-		in, out := &in.Traits, &out.Traits
+	if is.Traits != nil {
+		in, out := &is.Traits, &out.Traits
 		*out = make(map[string]IntegrationTraitSpec, len(*in))
 		for key, val := range *in {
 			(*out)[key] = *val.DeepCopy()
 		}
 	}
-	if in.Configuration != nil {
-		in, out := &in.Configuration, &out.Configuration
+	if is.Configuration != nil {
+		in, out := &is.Configuration, &out.Configuration
 		*out = make([]ConfigurationSpec, len(*in))
 		copy(*out, *in)
 	}
@@ -366,12 +370,12 @@ func (in *IntegrationSpec) DeepCopyInto(out *IntegrationSpec) {
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntegrationSpec.
-func (in *IntegrationSpec) DeepCopy() *IntegrationSpec {
-	if in == nil {
+func (is *IntegrationSpec) DeepCopy() *IntegrationSpec {
+	if is == nil {
 		return nil
 	}
 	out := new(IntegrationSpec)
-	in.DeepCopyInto(out)
+	is.DeepCopyInto(out)
 	return out
 }
 
diff --git a/pkg/client/cmd/run.go b/pkg/client/cmd/run.go
index bf2c2683..490adfed 100644
--- a/pkg/client/cmd/run.go
+++ b/pkg/client/cmd/run.go
@@ -20,29 +20,31 @@ package cmd
 import (
 	"encoding/json"
 	"fmt"
-	"github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
-	"gopkg.in/yaml.v2"
 	"io/ioutil"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime"
 	"net/http"
 	"os"
 	"os/signal"
+	"path"
 	"regexp"
 	"strconv"
 	"strings"
 	"syscall"
 
+	"github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
+	"gopkg.in/yaml.v2"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+
 	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util"
 
-	"github.com/apache/camel-k/pkg/util/sync"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/apache/camel-k/pkg/util/log"
+	"github.com/apache/camel-k/pkg/util/sync"
 	"github.com/apache/camel-k/pkg/util/watch"
 	"github.com/operator-framework/operator-sdk/pkg/sdk"
 	"github.com/spf13/cobra"
@@ -67,7 +69,6 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) *cobra.Command {
 		RunE:  options.run,
 	}
 
-	cmd.Flags().StringVarP(&options.Language, "language", "l", "", "Programming Language used to write the file")
 	cmd.Flags().StringVarP(&options.Runtime, "runtime", "r", "", "Runtime used by the integration")
 	cmd.Flags().StringVar(&options.IntegrationName, "name", "", "The integration name")
 	cmd.Flags().StringSliceVarP(&options.Dependencies, "dependency", "d", nil, "The integration dependency")
@@ -93,7 +94,6 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) *cobra.Command {
 type runCmdOptions struct {
 	*RootCmdOptions
 	IntegrationContext string
-	Language           string
 	Runtime            string
 	IntegrationName    string
 	Dependencies       []string
@@ -111,22 +111,27 @@ type runCmdOptions struct {
 }
 
 func (o *runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error {
-	if len(args) != 1 {
-		return errors.New("accepts 1 arg, received " + strconv.Itoa(len(args)))
+	if len(args) < 1 {
+		return errors.New("accepts at least 1 arg, received 0")
 	}
-	fileName := args[0]
-	if !strings.HasPrefix(fileName, "http://";) && !strings.HasPrefix(fileName, "https://";) {
-		if _, err := os.Stat(fileName); err != nil && os.IsNotExist(err) {
-			return errors.Wrap(err, "file "+fileName+" does not exist")
-		} else if err != nil {
-			return errors.Wrap(err, "error while accessing file "+fileName)
-		}
-	} else {
-		resp, err := http.Get(fileName)
-		if err != nil {
-			return errors.Wrap(err, "The URL provided is not reachable")
-		} else if resp.StatusCode != 200 {
-			return errors.New("The URL provided is not reachable " + fileName + " The error code returned is " + strconv.Itoa(resp.StatusCode))
+	if len(args) > 1 && o.IntegrationName == "" {
+		return errors.New("integration name is mandatory when using multiple sources")
+	}
+
+	for _, fileName := range args {
+		if !strings.HasPrefix(fileName, "http://";) && !strings.HasPrefix(fileName, "https://";) {
+			if _, err := os.Stat(fileName); err != nil && os.IsNotExist(err) {
+				return errors.Wrap(err, "file "+fileName+" does not exist")
+			} else if err != nil {
+				return errors.Wrap(err, "error while accessing file "+fileName)
+			}
+		} else {
+			resp, err := http.Get(fileName)
+			if err != nil {
+				return errors.Wrap(err, "The URL provided is not reachable")
+			} else if resp.StatusCode != 200 {
+				return errors.New("The URL provided is not reachable " + fileName + " The error code returned is " + strconv.Itoa(resp.StatusCode))
+			}
 		}
 	}
 
@@ -165,7 +170,7 @@ func (o *runCmdOptions) run(cmd *cobra.Command, args []string) error {
 	}
 
 	if o.Sync || o.Dev {
-		err = o.syncIntegration(args[0])
+		err = o.syncIntegration(args)
 		if err != nil {
 			return err
 		}
@@ -215,36 +220,35 @@ func (o *runCmdOptions) waitForIntegrationReady(integration *v1alpha1.Integratio
 	return watch.HandleStateChanges(o.Context, integration, handler)
 }
 
-func (o *runCmdOptions) syncIntegration(file string) error {
-	changes, err := sync.File(o.Context, file)
-	if err != nil {
-		return err
-	}
-	go func() {
-		for {
-			select {
-			case <-o.Context.Done():
-				return
-			case <-changes:
-				_, err := o.updateIntegrationCode(file)
-				if err != nil {
-					logrus.Error("Unable to sync integration: ", err)
+func (o *runCmdOptions) syncIntegration(sources []string) error {
+	for _, s := range sources {
+		changes, err := sync.File(o.Context, s)
+		if err != nil {
+			return err
+		}
+		go func() {
+			for {
+				select {
+				case <-o.Context.Done():
+					return
+				case <-changes:
+					_, err := o.updateIntegrationCode(sources)
+					if err != nil {
+						logrus.Error("Unable to sync integration: ", err)
+					}
 				}
 			}
-		}
-	}()
+		}()
+	}
+
 	return nil
 }
 
 func (o *runCmdOptions) createIntegration(cmd *cobra.Command, args []string) (*v1alpha1.Integration, error) {
-	return o.updateIntegrationCode(args[0])
+	return o.updateIntegrationCode(args)
 }
 
-func (o *runCmdOptions) updateIntegrationCode(filename string) (*v1alpha1.Integration, error) {
-	code, err := o.loadCode(filename)
-	if err != nil {
-		return nil, err
-	}
+func (o *runCmdOptions) updateIntegrationCode(sources []string) (*v1alpha1.Integration, error) {
 
 	namespace := o.Namespace
 
@@ -252,17 +256,12 @@ func (o *runCmdOptions) updateIntegrationCode(filename string) (*v1alpha1.Integr
 	if o.IntegrationName != "" {
 		name = o.IntegrationName
 		name = kubernetes.SanitizeName(name)
-	} else {
-		name = kubernetes.SanitizeName(filename)
-		if name == "" {
-			name = "integration"
-		}
+	} else if len(sources) == 1 {
+		name = kubernetes.SanitizeName(sources[0])
 	}
 
-	codeName := filename
-
-	if idx := strings.LastIndexByte(filename, os.PathSeparator); idx > -1 {
-		codeName = codeName[idx+1:]
+	if name == "" {
+		return nil, errors.New("unable to determine integration name")
 	}
 
 	integration := v1alpha1.Integration{
@@ -275,11 +274,6 @@ func (o *runCmdOptions) updateIntegrationCode(filename string) (*v1alpha1.Integr
 			Name:      name,
 		},
 		Spec: v1alpha1.IntegrationSpec{
-			Source: v1alpha1.SourceSpec{
-				Name:     codeName,
-				Content:  code,
-				Language: v1alpha1.Language(o.Language),
-			},
 			Dependencies:  make([]string, 0, len(o.Dependencies)),
 			Context:       o.IntegrationContext,
 			Configuration: make([]v1alpha1.ConfigurationSpec, 0),
@@ -287,6 +281,15 @@ func (o *runCmdOptions) updateIntegrationCode(filename string) (*v1alpha1.Integr
 		},
 	}
 
+	for _, source := range sources {
+		code, err := o.loadCode(source)
+		if err != nil {
+			return nil, err
+		}
+
+		integration.Spec.AddSource(path.Base(source), code, "")
+	}
+
 	for _, item := range o.Dependencies {
 		if strings.HasPrefix(item, "mvn:") {
 			integration.Spec.Dependencies = append(integration.Spec.Dependencies, item)
@@ -336,7 +339,7 @@ func (o *runCmdOptions) updateIntegrationCode(filename string) (*v1alpha1.Integr
 	case "":
 		// continue..
 	case "yaml":
-		jsondata, err := toJson(&integration)
+		jsondata, err := toJSON(&integration)
 		if err != nil {
 			return nil, err
 		}
@@ -348,7 +351,7 @@ func (o *runCmdOptions) updateIntegrationCode(filename string) (*v1alpha1.Integr
 		return nil, nil
 
 	case "json":
-		data, err := toJson(&integration)
+		data, err := toJSON(&integration)
 		if err != nil {
 			return nil, err
 		}
@@ -360,7 +363,7 @@ func (o *runCmdOptions) updateIntegrationCode(filename string) (*v1alpha1.Integr
 	}
 
 	existed := false
-	err = sdk.Create(&integration)
+	err := sdk.Create(&integration)
 	if err != nil && k8serrors.IsAlreadyExists(err) {
 		existed = true
 		clone := integration.DeepCopy()
@@ -384,7 +387,7 @@ func (o *runCmdOptions) updateIntegrationCode(filename string) (*v1alpha1.Integr
 	return &integration, nil
 }
 
-func toJson(value runtime.Object) ([]byte, error) {
+func toJSON(value runtime.Object) ([]byte, error) {
 	u, err := k8sutil.UnstructuredFromRuntimeObject(value)
 	if err != nil {
 		return nil, fmt.Errorf("error creating unstructured data: %v", err)
diff --git a/pkg/metadata/metadata.go b/pkg/metadata/metadata.go
index 46a7aefe..6e43b0b5 100644
--- a/pkg/metadata/metadata.go
+++ b/pkg/metadata/metadata.go
@@ -34,3 +34,14 @@ func Extract(source v1alpha1.SourceSpec) IntegrationMetadata {
 		Dependencies: dependencies,
 	}
 }
+
+// Each --
+func Each(sources []v1alpha1.SourceSpec, consumer func(int, IntegrationMetadata) bool) {
+	for i, s := range sources {
+		meta := Extract(s)
+
+		if !consumer(i, meta) {
+			break
+		}
+	}
+}
diff --git a/pkg/stub/action/integration/initialize.go b/pkg/stub/action/integration/initialize.go
index fa023159..b495b11c 100644
--- a/pkg/stub/action/integration/initialize.go
+++ b/pkg/stub/action/integration/initialize.go
@@ -59,9 +59,13 @@ func (action *initializeAction) Handle(integration *v1alpha1.Integration) error
 		var defaultReplicas int32 = 1
 		target.Spec.Replicas = &defaultReplicas
 	}
-	// extract metadata
-	meta := metadata.Extract(target.Spec.Source)
-	target.Spec.Source.Language = meta.Language
+	for i := range target.Spec.Sources {
+		// extract metadata
+		s := &target.Spec.Sources[i]
+
+		meta := metadata.Extract(*s)
+		s.Language = meta.Language
+	}
 
 	// execute custom initialization
 	if _, err := trait.Apply(target, nil); err != nil {
diff --git a/pkg/trait/dependencies.go b/pkg/trait/dependencies.go
index f284678d..b57d0971 100644
--- a/pkg/trait/dependencies.go
+++ b/pkg/trait/dependencies.go
@@ -39,21 +39,23 @@ func (*dependenciesTrait) appliesTo(e *Environment) bool {
 	return e.Integration != nil && e.Integration.Status.Phase == ""
 }
 
-func (d *dependenciesTrait) apply(e *Environment) error {
-	meta := metadata.Extract(e.Integration.Spec.Source)
+func (*dependenciesTrait) apply(e *Environment) error {
+	for _, s := range e.Integration.Spec.Sources {
+		meta := metadata.Extract(s)
 
-	if meta.Language == v1alpha1.LanguageGroovy {
-		util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, "runtime:groovy")
-	} else if meta.Language == v1alpha1.LanguageKotlin {
-		util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, "runtime:kotlin")
-	}
+		if meta.Language == v1alpha1.LanguageGroovy {
+			util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, "runtime:groovy")
+		} else if meta.Language == v1alpha1.LanguageKotlin {
+			util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, "runtime:kotlin")
+		}
 
-	// jvm runtime and camel-core required by default
-	util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, "runtime:jvm")
-	util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, "camel:core")
+		// jvm runtime and camel-core required by default
+		util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, "runtime:jvm")
+		util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, "camel:core")
 
-	for _, d := range meta.Dependencies {
-		util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, d)
+		for _, d := range meta.Dependencies {
+			util.StringSliceUniqueAdd(&e.Integration.Spec.Dependencies, d)
+		}
 	}
 
 	// sort the dependencies to get always the same list if they don't change
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index 80230508..bc3f1213 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -25,6 +25,7 @@ import (
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 )
 
 type deploymentTrait struct {
@@ -42,7 +43,7 @@ func (d *deploymentTrait) appliesTo(e *Environment) bool {
 }
 
 func (d *deploymentTrait) apply(e *Environment) error {
-	e.Resources.Add(getConfigMapFor(e))
+	e.Resources.AddAll(getConfigMapsFor(e))
 	e.Resources.Add(getDeploymentFor(e))
 	return nil
 }
@@ -53,34 +54,60 @@ func (d *deploymentTrait) apply(e *Environment) error {
 //
 // **********************************
 
-func getConfigMapFor(e *Environment) *corev1.ConfigMap {
+func getConfigMapsFor(e *Environment) []runtime.Object {
+	maps := make([]runtime.Object, 0, len(e.Integration.Spec.Sources)+1)
+
 	// combine properties of integration with context, integration
 	// properties have the priority
 	properties := CombineConfigurationAsMap("property", e.Context, e.Integration)
 
-	cm := corev1.ConfigMap{
-		TypeMeta: metav1.TypeMeta{
-			Kind:       "ConfigMap",
-			APIVersion: "v1",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      e.Integration.Name,
-			Namespace: e.Integration.Namespace,
-			Labels: map[string]string{
-				"camel.apache.org/integration": e.Integration.Name,
+	maps = append(
+		maps,
+		&corev1.ConfigMap{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       "ConfigMap",
+				APIVersion: "v1",
 			},
-			Annotations: map[string]string{
-				"camel.apache.org/source.language": string(e.Integration.Spec.Source.Language),
-				"camel.apache.org/source.name":     e.Integration.Spec.Source.Name,
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      e.Integration.Name + "-properties",
+				Namespace: e.Integration.Namespace,
+				Labels: map[string]string{
+					"camel.apache.org/integration": e.Integration.Name,
+				},
+			},
+			Data: map[string]string{
+				"properties": PropertiesString(properties),
 			},
 		},
-		Data: map[string]string{
-			"integration": e.Integration.Spec.Source.Content,
-			"properties":  PropertiesString(properties),
-		},
+	)
+
+	for i, s := range e.Integration.Spec.Sources {
+		maps = append(
+			maps,
+			&corev1.ConfigMap{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ConfigMap",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      fmt.Sprintf("%s-source-%03d", e.Integration.Name, i),
+					Namespace: e.Integration.Namespace,
+					Labels: map[string]string{
+						"camel.apache.org/integration": e.Integration.Name,
+					},
+					Annotations: map[string]string{
+						"camel.apache.org/source.language": string(s.Language),
+						"camel.apache.org/source.name":     s.Name,
+					},
+				},
+				Data: map[string]string{
+					"integration": s.Content,
+				},
+			},
+		)
 	}
 
-	return &cm
+	return maps
 }
 
 // **********************************
@@ -90,7 +117,15 @@ func getConfigMapFor(e *Environment) *corev1.ConfigMap {
 // **********************************
 
 func getDeploymentFor(e *Environment) *appsv1.Deployment {
-	sourceName := strings.TrimPrefix(e.Integration.Spec.Source.Name, "/")
+	sources := make([]string, 0, len(e.Integration.Spec.Sources))
+	for i, s := range e.Integration.Spec.Sources {
+		src := fmt.Sprintf("file:/etc/camel/integrations/%03d/%s", i, strings.TrimPrefix(s.Name, "/"))
+		if s.Language != "" {
+			src = src + "?language=" + string(s.Language)
+		}
+
+		sources = append(sources, src)
+	}
 
 	// combine Environment of integration with context, integration
 	// Environment has the priority
@@ -100,8 +135,7 @@ func getDeploymentFor(e *Environment) *appsv1.Deployment {
 	environment["JAVA_MAIN_CLASS"] = "org.apache.camel.k.jvm.Application"
 
 	// camel-k runtime
-	environment["CAMEL_K_ROUTES_URI"] = "file:/etc/camel/conf/" + sourceName
-	environment["CAMEL_K_ROUTES_LANGUAGE"] = string(e.Integration.Spec.Source.Language)
+	environment["CAMEL_K_ROUTES"] = strings.Join(sources, ",")
 	environment["CAMEL_K_CONF"] = "/etc/camel/conf/application.properties"
 	environment["CAMEL_K_CONF_D"] = "/etc/camel/conf.d"
 
@@ -159,21 +193,18 @@ func getDeploymentFor(e *Environment) *appsv1.Deployment {
 	cnt := 0
 
 	//
-	// Volumes :: Defaults
+	// Volumes :: Properties
 	//
 
 	vols = append(vols, corev1.Volume{
-		Name: "integration",
+		Name: "integration-properties",
 		VolumeSource: corev1.VolumeSource{
 			ConfigMap: &corev1.ConfigMapVolumeSource{
 				LocalObjectReference: corev1.LocalObjectReference{
-					Name: e.Integration.Name,
+					Name: e.Integration.Name + "-properties",
 				},
 				Items: []corev1.KeyToPath{
 					{
-						Key:  "integration",
-						Path: sourceName,
-					}, {
 						Key:  "properties",
 						Path: "application.properties",
 					},
@@ -183,10 +214,38 @@ func getDeploymentFor(e *Environment) *appsv1.Deployment {
 	})
 
 	mnts = append(mnts, corev1.VolumeMount{
-		Name:      "integration",
+		Name:      "integration-properties",
 		MountPath: "/etc/camel/conf",
 	})
 
+	//
+	// Volumes :: Sources
+	//
+
+	for i, s := range e.Integration.Spec.Sources {
+		vols = append(vols, corev1.Volume{
+			Name: fmt.Sprintf("integration-source-%03d", i),
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: fmt.Sprintf("%s-source-%03d", e.Integration.Name, i),
+					},
+					Items: []corev1.KeyToPath{
+						{
+							Key:  "integration",
+							Path: strings.TrimPrefix(s.Name, "/"),
+						},
+					},
+				},
+			},
+		})
+
+		mnts = append(mnts, corev1.VolumeMount{
+			Name:      fmt.Sprintf("integration-source-%03d", i),
+			MountPath: fmt.Sprintf("/etc/camel/integrations/%03d", i),
+		})
+	}
+
 	//
 	// Volumes :: Additional ConfigMaps
 	//
diff --git a/pkg/trait/knative.go b/pkg/trait/knative.go
index b122f8dd..e4637460 100644
--- a/pkg/trait/knative.go
+++ b/pkg/trait/knative.go
@@ -19,6 +19,7 @@ package trait
 
 import (
 	"encoding/json"
+	"fmt"
 	"strings"
 
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
@@ -49,7 +50,7 @@ func (t *knativeTrait) appliesTo(e *Environment) bool {
 
 func (t *knativeTrait) autoconfigure(e *Environment) error {
 	if t.Sources == "" {
-		channels := t.getSourceChannels(e)
+		channels := getSourceChannels(e)
 		t.Sources = strings.Join(channels, ",")
 	}
 	return nil
@@ -72,14 +73,26 @@ func (t *knativeTrait) getServiceFor(e *Environment) *serving.Service {
 	// Environment has the priority
 	environment := CombineConfigurationAsMap("env", e.Context, e.Integration)
 
+	sources := make([]string, 0, len(e.Integration.Spec.Sources))
+	for i, s := range e.Integration.Spec.Sources {
+		envName := fmt.Sprintf("KAMEL_K_ROUTE_%03d", i)
+		environment[envName] = s.Content
+
+		src := fmt.Sprintf("env:%s", envName)
+		if s.Language != "" {
+			src = src + "?language=" + string(s.Language)
+		}
+
+		sources = append(sources, src)
+	}
+
 	// set env vars needed by the runtime
 	environment["JAVA_MAIN_CLASS"] = "org.apache.camel.k.jvm.Application"
 
 	// camel-k runtime
-	environment["CAMEL_K_ROUTES_URI"] = "inline:" + e.Integration.Spec.Source.Content
-	environment["CAMEL_K_ROUTES_LANGUAGE"] = string(e.Integration.Spec.Source.Language)
-	environment["CAMEL_K_CONF"] = "inline:" + PropertiesString(properties)
-	environment["CAMEL_K_CONF_D"] = "/etc/camel/conf.d"
+	environment["CAMEL_K_ROUTES"] = strings.Join(sources, ",")
+	environment["CAMEL_K_CONF"] = "env:CAMEL_K_PROPERTIES"
+	environment["CAMEL_K_PROPERTIES"] = PropertiesString(properties)
 
 	// add a dummy env var to trigger deployment if everything but the code
 	// has been changed
@@ -126,7 +139,7 @@ func (t *knativeTrait) getServiceFor(e *Environment) *serving.Service {
 }
 
 func (t *knativeTrait) getSubscriptionsFor(e *Environment) []*eventing.Subscription {
-	channels := t.getConfiguredSourceChannels()
+	channels := getConfiguredSourceChannels(t.Sources)
 	subs := make([]*eventing.Subscription, 0)
 	for _, ch := range channels {
 		subs = append(subs, t.getSubscriptionFor(e, ch))
@@ -172,7 +185,7 @@ func (t *knativeTrait) getConfigurationSerialized(e *Environment) string {
 }
 
 func (t *knativeTrait) getConfiguration(e *Environment) knativeutil.CamelEnvironment {
-	sourceChannels := t.getConfiguredSourceChannels()
+	sourceChannels := getConfiguredSourceChannels(t.Sources)
 	env := knativeutil.NewCamelEnvironment()
 	for _, ch := range sourceChannels {
 		svc := knativeutil.CamelServiceDefinition{
@@ -202,9 +215,9 @@ func (t *knativeTrait) getConfiguration(e *Environment) knativeutil.CamelEnviron
 	return env
 }
 
-func (t *knativeTrait) getConfiguredSourceChannels() []string {
+func getConfiguredSourceChannels(sources string) []string {
 	channels := make([]string, 0)
-	for _, ch := range strings.Split(t.Sources, ",") {
+	for _, ch := range strings.Split(sources, ",") {
 		cht := strings.Trim(ch, " \t\"")
 		if cht != "" {
 			channels = append(channels, cht)
@@ -213,7 +226,13 @@ func (t *knativeTrait) getConfiguredSourceChannels() []string {
 	return channels
 }
 
-func (*knativeTrait) getSourceChannels(e *Environment) []string {
-	meta := metadata.Extract(e.Integration.Spec.Source)
-	return knativeutil.ExtractChannelNames(meta.FromURIs)
+func getSourceChannels(e *Environment) []string {
+	channels := make([]string, 0)
+
+	metadata.Each(e.Integration.Spec.Sources, func(_ int, meta metadata.IntegrationMetadata) bool {
+		channels = append(channels, knativeutil.ExtractChannelNames(meta.FromURIs)...)
+		return true
+	})
+
+	return channels
 }
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index 17d89097..cb3acd5f 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -38,7 +38,7 @@ func TestOpenShiftTraits(t *testing.T) {
 	assert.NotContains(t, env.ExecutedTraits, ID("route"))
 	assert.Contains(t, env.ExecutedTraits, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
-		return cm.Name == "test"
+		return cm.Name == "test-properties"
 	}))
 	assert.NotNil(t, res.GetDeployment(func(deployment *appsv1.Deployment) bool {
 		return deployment.Name == "test"
@@ -53,7 +53,7 @@ func TestOpenShiftTraitsWithWeb(t *testing.T) {
 	assert.Contains(t, env.ExecutedTraits, ID("route"))
 	assert.Contains(t, env.ExecutedTraits, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
-		return cm.Name == "test"
+		return cm.Name == "test-properties"
 	}))
 	assert.NotNil(t, res.GetDeployment(func(deployment *appsv1.Deployment) bool {
 		return deployment.Name == "test"
@@ -107,7 +107,7 @@ func TestKubernetesTraits(t *testing.T) {
 	assert.NotContains(t, env.ExecutedTraits, ID("route"))
 	assert.Contains(t, env.ExecutedTraits, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
-		return cm.Name == "test"
+		return cm.Name == "test-properties"
 	}))
 	assert.NotNil(t, res.GetDeployment(func(deployment *appsv1.Deployment) bool {
 		return deployment.Name == "test"
@@ -122,7 +122,7 @@ func TestKubernetesTraitsWithWeb(t *testing.T) {
 	assert.NotContains(t, env.ExecutedTraits, ID("route"))
 	assert.Contains(t, env.ExecutedTraits, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
-		return cm.Name == "test"
+		return cm.Name == "test-properties"
 	}))
 	assert.NotNil(t, res.GetDeployment(func(deployment *appsv1.Deployment) bool {
 		return deployment.Name == "test"
diff --git a/pkg/util/digest/digest.go b/pkg/util/digest/digest.go
index 265c6644..2be054f4 100644
--- a/pkg/util/digest/digest.go
+++ b/pkg/util/digest/digest.go
@@ -37,9 +37,12 @@ func ComputeForIntegration(integration *v1alpha1.Integration) string {
 	hash.Write([]byte(integration.Spec.Context))
 
 	// Integration code
-	if integration.Spec.Source.Content != "" {
-		hash.Write([]byte(integration.Spec.Source.Content))
+	for _, s := range integration.Spec.Sources {
+		if s.Content != "" {
+			hash.Write([]byte(s.Content))
+		}
 	}
+
 	// Integration dependencies
 	for _, item := range integration.Spec.Dependencies {
 		hash.Write([]byte(item))
diff --git a/pkg/util/kubernetes/collection.go b/pkg/util/kubernetes/collection.go
index 54b6ba76..fbc90986 100644
--- a/pkg/util/kubernetes/collection.go
+++ b/pkg/util/kubernetes/collection.go
@@ -47,6 +47,11 @@ func (c *Collection) Add(resource runtime.Object) {
 	c.items = append(c.items, resource)
 }
 
+// AddAll adds all resources to the collection
+func (c *Collection) AddAll(resource []runtime.Object) {
+	c.items = append(c.items, resource...)
+}
+
 // VisitDeployment executes the visitor function on all Deployment resources
 func (c *Collection) VisitDeployment(visitor func(*appsv1.Deployment)) {
 	c.Visit(func(res runtime.Object) {
diff --git a/runtime/examples/routes.js b/runtime/examples/routes.js
index a0205114..edb78065 100644
--- a/runtime/examples/routes.js
+++ b/runtime/examples/routes.js
@@ -5,10 +5,10 @@
 //
 // ****************
 
-l = components.get('log')
-l.exchangeFormatter = function(e) {
-    return "log - body=" + e.in.body + ", headers=" + e.in.headers
-}
+//l = components.get('log')
+//l.exchangeFormatter = function(e) {
+//    return "log - body=" + e.in.body + ", headers=" + e.in.headers
+//}
 
 // ****************
 //
diff --git a/runtime/examples/simple.groovy b/runtime/examples/simple.groovy
new file mode 100644
index 00000000..75462074
--- /dev/null
+++ b/runtime/examples/simple.groovy
@@ -0,0 +1,6 @@
+
+from('timer:groovy?period=1s')
+    .routeId('groovy')
+    .setBody()
+        .simple('Hello Camel K from ${routeId}')
+    .to('log:info?showAll=false')
diff --git a/runtime/examples/simple.js b/runtime/examples/simple.js
new file mode 100644
index 00000000..918c5442
--- /dev/null
+++ b/runtime/examples/simple.js
@@ -0,0 +1,6 @@
+
+from('timer:js?period=1s')
+    .routeId('js')
+    .setBody()
+        .simple('Hello Camel K from ${routeId}')
+    .to('log:info?multiline=true')
\ No newline at end of file
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 e24e0cf3..16b4f0fb 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
@@ -29,8 +29,8 @@ class LoaderTest extends Specification {
             def resource = "classpath:routes.groovy"
 
         when:
-            def loader = RoutesLoaders.loaderFor(resource, null);
-            def builder = loader.load(new RuntimeRegistry(), resource);
+            def loader = RoutesLoaders.loaderFor(resource, null)
+            def builder = loader.load(new RuntimeRegistry(), resource)
 
         then:
             loader instanceof GroovyRoutesLoader
diff --git a/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/IntegrationTest.groovy b/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/IntegrationTest.groovy
index 1b6a1e67..32904f16 100644
--- a/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/IntegrationTest.groovy
+++ b/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/IntegrationTest.groovy
@@ -32,7 +32,7 @@ class IntegrationTest extends Specification {
         when:
         def runtime = new Runtime()
         runtime.setDuration(5)
-        runtime.load('classpath:routes-with-rest.groovy', null)
+        runtime.load(['classpath:routes-with-rest.groovy'])
         runtime.addMainListener(new MainListenerSupport() {
             @Override
             void afterStart(MainSupport main) {
@@ -55,7 +55,7 @@ class IntegrationTest extends Specification {
         when:
         def runtime = new Runtime()
         runtime.setDuration(5)
-        runtime.load('classpath:routes-with-bindings.groovy', null)
+        runtime.load(['classpath:routes-with-bindings.groovy'])
         runtime.addMainListener(new MainListenerSupport() {
             @Override
             void afterStart(MainSupport main) {
@@ -82,7 +82,7 @@ class IntegrationTest extends Specification {
         when:
         def runtime = new Runtime()
         runtime.setDuration(5)
-        runtime.load('classpath:routes-with-component-configuration.groovy', null)
+        runtime.load(['classpath:routes-with-component-configuration.groovy'])
         runtime.addMainListener(new MainListenerSupport() {
             @Override
             void afterStart(MainSupport main) {
diff --git a/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/extension/LogExtensionTest.groovy b/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/extension/LogExtensionTest.groovy
index 68bad926..fee6475e 100644
--- a/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/extension/LogExtensionTest.groovy
+++ b/runtime/groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/extension/LogExtensionTest.groovy
@@ -30,7 +30,7 @@ class LogExtensionTest extends Specification {
         when:
         def log = new LogComponent()
         log.formatter {
-            "body: " + it.in.body
+            "body: $it.in.body"
         }
 
         def ex = new DefaultExchange(ctx)
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 0c22f03c..d26ddefd 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
@@ -52,15 +52,14 @@
     // *******************************
 
     public static void main(String[] args) throws Exception {
-        final String resource = System.getenv(Constants.ENV_CAMEL_K_ROUTES_URI);
-        final String language = System.getenv(Constants.ENV_CAMEL_K_ROUTES_LANGUAGE);
+        final String routes = System.getenv(Constants.ENV_CAMEL_K_ROUTES);
 
-        if (ObjectHelper.isEmpty(resource)) {
-            throw new IllegalStateException("No valid resource found in " + Constants.ENV_CAMEL_K_ROUTES_URI + " environment variable");
+        if (ObjectHelper.isEmpty(routes)) {
+            throw new IllegalStateException("No valid routes found in " + Constants.ENV_CAMEL_K_ROUTES + " environment variable");
         }
 
         Runtime runtime = new Runtime();
-        runtime.load(resource, language);
+        runtime.load(routes.split(",", -1));
         runtime.addMainListener(new ComponentPropertiesBinder());
         runtime.run();
     }
diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Constants.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Constants.java
index e5a09edc..d3cb4b7b 100644
--- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Constants.java
+++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Constants.java
@@ -17,13 +17,12 @@
 package org.apache.camel.k.jvm;
 
 public final class Constants {
-    public static final String ENV_CAMEL_K_ROUTES_URI = "CAMEL_K_ROUTES_URI";
-    public static final String ENV_CAMEL_K_ROUTES_LANGUAGE = "CAMEL_K_ROUTES_LANGUAGE";
+    public static final String ENV_CAMEL_K_ROUTES = "CAMEL_K_ROUTES";
     public static final String ENV_CAMEL_K_CONF = "CAMEL_K_CONF";
     public static final String ENV_CAMEL_K_CONF_D = "CAMEL_K_CONF_D";
     public static final String SCHEME_CLASSPATH = "classpath:";
     public static final String SCHEME_FILE = "file:";
-    public static final String SCHEME_INLINE = "inline:";
+    public static final String SCHEME_ENV = "env:";
     public static final String LOGGING_LEVEL_PREFIX = "logging.level.";
 
     private Constants() {
diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RoutesLoaders.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RoutesLoaders.java
index 35cb47da..0d81738e 100644
--- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RoutesLoaders.java
+++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RoutesLoaders.java
@@ -16,6 +16,20 @@
  */
 package org.apache.camel.k.jvm;
 
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.SimpleBindings;
+import javax.xml.bind.UnmarshalException;
+
 import org.apache.camel.CamelContext;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.k.jvm.dsl.Components;
@@ -29,24 +43,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.script.Bindings;
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.SimpleBindings;
-import javax.xml.bind.UnmarshalException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.List;
-import java.util.ServiceLoader;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-import static org.apache.camel.k.jvm.Constants.SCHEME_CLASSPATH;
-import static org.apache.camel.k.jvm.Constants.SCHEME_FILE;
-import static org.apache.camel.k.jvm.Constants.SCHEME_INLINE;
-
 public final class RoutesLoaders {
     private static final Logger LOGGER = LoggerFactory.getLogger(RoutesLoaders.class);
 
@@ -62,7 +58,7 @@ private RoutesLoaders() {
         @Override
         public RouteBuilder load(RuntimeRegistry registry, String resource) throws Exception {
             String path = resource;
-            path = StringUtils.removeStart(path, SCHEME_CLASSPATH);
+            path = StringUtils.removeStart(path, Constants.SCHEME_CLASSPATH);
             path = StringUtils.removeEnd(path, ".class");
 
             Class<?> type = Class.forName(path);
@@ -173,7 +169,9 @@ public void configure() throws Exception {
 
 
     public static RoutesLoader loaderFor(String resource, String languageName) {
-        if (!resource.startsWith(SCHEME_CLASSPATH) && !resource.startsWith(SCHEME_FILE) && !resource.startsWith(SCHEME_INLINE)) {
+        if (!resource.startsWith(Constants.SCHEME_CLASSPATH) &&
+            !resource.startsWith(Constants.SCHEME_FILE) &&
+            !resource.startsWith(Constants.SCHEME_ENV)) {
             throw new IllegalArgumentException("No valid resource format, expected scheme:path, found " + resource);
         }
 
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 72753f77..d4a755ca 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
@@ -27,6 +27,8 @@
 import org.apache.camel.impl.CompositeRegistry;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.main.MainSupport;
+import org.apache.camel.util.URISupport;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -40,18 +42,25 @@ public Runtime() {
         this.contextMap = new ConcurrentHashMap<>();
     }
 
-    public void load(String resource, String language) throws Exception {
-        final RoutesLoader loader = RoutesLoaders.loaderFor(resource, language);
-        final RouteBuilder routes = loader.load(registry, resource);
+    public void load(String[] routes) throws Exception {
+        for (String route: routes) {
+            // 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");
 
-        if (routes == null) {
-            throw new IllegalStateException("Unable to load route from: " + resource);
-        }
+            // load routes
+            final RoutesLoader loader = RoutesLoaders.loaderFor(location, language);
+            final RouteBuilder builder = loader.load(registry, location);
+
+            if (routes == null) {
+                throw new IllegalStateException("Unable to load route from: " + route);
+            }
 
-        LOGGER.info("Routes: {}", resource);
-        LOGGER.info("Language: {}", language);
+            LOGGER.info("Routes: {}", route);
 
-        addRouteBuilder(routes);
+            addRouteBuilder(builder);
+        }
     }
 
     public RuntimeRegistry getRegistry() {
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 44dc3a14..8b4db47f 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
@@ -36,7 +36,6 @@
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.LoggerConfig;
 
-import static org.apache.camel.k.jvm.Constants.SCHEME_INLINE;
 
 public final class RuntimeSupport {
     private RuntimeSupport() {
@@ -49,8 +48,8 @@ public static void configureSystemProperties() {
 
         // Main location
         if (ObjectHelper.isNotEmpty(conf)) {
-            if (conf.startsWith(SCHEME_INLINE)) {
-                try (Reader reader = URIResolver.resolveInline(conf)) {
+            if (conf.startsWith(Constants.SCHEME_ENV)) {
+                try (Reader reader = URIResolver.resolveEnv(conf)) {
                     properties.load(reader);
                 } catch (IOException e) {
                     throw new RuntimeException(e);
diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/URIResolver.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/URIResolver.java
index b52e81c9..69fb6408 100644
--- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/URIResolver.java
+++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/URIResolver.java
@@ -16,36 +16,43 @@
  */
 package org.apache.camel.k.jvm;
 
-import org.apache.camel.CamelContext;
-import org.apache.camel.util.ResourceHelper;
-
 import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
 import java.io.StringReader;
 
-import static org.apache.camel.k.jvm.Constants.SCHEME_INLINE;
+import org.apache.camel.CamelContext;
+import org.apache.camel.util.ResourceHelper;
+import org.apache.camel.util.StringHelper;
+
 
 public class URIResolver {
 
-    public static InputStream resolve(CamelContext ctx, String uri) throws IOException {
+    public static InputStream resolve(CamelContext ctx, String uri) throws Exception {
         if (uri == null) {
             throw new IllegalArgumentException("Cannot resolve null URI");
         }
-        if (uri.startsWith(SCHEME_INLINE)) {
+
+        if (uri.startsWith(Constants.SCHEME_ENV)) {
+            final String envvar = StringHelper.after(uri, ":");
+            final String content = System.getenv(envvar);
+
             // Using platform encoding on purpose
-            return new ByteArrayInputStream(uri.substring(SCHEME_INLINE.length()).getBytes());
+            return new ByteArrayInputStream(content.getBytes());
         }
 
         return ResourceHelper.resolveMandatoryResourceAsInputStream(ctx, uri);
     }
 
-    public static Reader resolveInline(String uri) {
-        if (!uri.startsWith(SCHEME_INLINE)) {
-            throw new IllegalArgumentException("The provided content is not inline: " + uri);
+    public static Reader resolveEnv(String uri) {
+        if (!uri.startsWith(Constants.SCHEME_ENV)) {
+            throw new IllegalArgumentException("The provided content is not env: " + uri);
         }
-        return new StringReader(uri.substring(SCHEME_INLINE.length()));
+
+        final String envvar = StringHelper.after(uri, ":");
+        final String content = System.getenv(envvar);
+
+        return new StringReader(content);
     }
 
 }
diff --git a/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTest.java b/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTest.java
new file mode 100644
index 00000000..909ae233
--- /dev/null
+++ b/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTest.java
@@ -0,0 +1,51 @@
+/**
+ * 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.jvm;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Route;
+import org.apache.camel.util.ObjectHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Java6Assertions.assertThat;
+
+public class RuntimeTest {
+
+    @Test
+    void testLoadMultipleRoutes() throws Exception {
+        Runtime runtime = new Runtime();
+        runtime.load(new String[]{
+            "classpath:r1.js",
+            "classpath:r2.mytype?language=js",
+        });
+
+        try {
+            runtime.start();
+
+            CamelContext context = runtime.getCamelContext();
+            List<Route> routes = context.getRoutes();
+
+            assertThat(routes).hasSize(2);
+            assertThat(routes).anyMatch(p -> ObjectHelper.equal("r1", p.getId()));
+            assertThat(routes).anyMatch(p -> ObjectHelper.equal("r2", p.getId()));
+        } finally {
+            runtime.stop();
+        }
+    }
+}
diff --git a/runtime/jvm/src/test/resources/r1.js b/runtime/jvm/src/test/resources/r1.js
new file mode 100644
index 00000000..f6ca4254
--- /dev/null
+++ b/runtime/jvm/src/test/resources/r1.js
@@ -0,0 +1,4 @@
+
+from('timer:tick1')
+    .id('r1')
+    .to('log:info1')
\ No newline at end of file
diff --git a/runtime/jvm/src/test/resources/r2.mytype b/runtime/jvm/src/test/resources/r2.mytype
new file mode 100644
index 00000000..a0b33cc8
--- /dev/null
+++ b/runtime/jvm/src/test/resources/r2.mytype
@@ -0,0 +1,4 @@
+
+from('timer:tick2')
+    .id('r2')
+    .to('log:info2')
\ No newline at end of file
diff --git a/runtime/kotlin/src/main/kotlin/org/apache/camel/k/kotlin/KotlinRoutesLoader.kt b/runtime/kotlin/src/main/kotlin/org/apache/camel/k/kotlin/KotlinRoutesLoader.kt
index 86027095..afbeb4f4 100644
--- a/runtime/kotlin/src/main/kotlin/org/apache/camel/k/kotlin/KotlinRoutesLoader.kt
+++ b/runtime/kotlin/src/main/kotlin/org/apache/camel/k/kotlin/KotlinRoutesLoader.kt
@@ -85,12 +85,10 @@ class KotlinRoutesLoader : RoutesLoader {
                     )
 
                     for (report in result.reports) {
-                        if (report.severity == ScriptDiagnostic.Severity.ERROR) {
-                            LOGGER.error("{}", report.message, report.exception)
-                        } else if (report.severity == ScriptDiagnostic.Severity.WARNING) {
-                            LOGGER.warn("{}", report.message, report.exception)
-                        } else {
-                            LOGGER.info("{}", report.message)
+                        when {
+                            report.severity == ScriptDiagnostic.Severity.ERROR -> LOGGER.error("{}", report.message, report.exception)
+                            report.severity == ScriptDiagnostic.Severity.WARNING -> LOGGER.warn("{}", report.message, report.exception)
+                            else -> LOGGER.info("{}", report.message)
                         }
                     }
                 }
diff --git a/runtime/kotlin/src/test/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationTest.kt b/runtime/kotlin/src/test/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationTest.kt
index 734f278e..4b6e85f3 100644
--- a/runtime/kotlin/src/test/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationTest.kt
+++ b/runtime/kotlin/src/test/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationTest.kt
@@ -16,7 +16,7 @@ class IntegrationTest {
     fun `load integration with rest`() {
         var runtime = org.apache.camel.k.jvm.Runtime()
         runtime.duration = 5
-        runtime.load("classpath:routes-with-rest.kts", null)
+        runtime.load(arrayOf("classpath:routes-with-rest.kts"))
         runtime.addMainListener(object: MainListenerSupport() {
             override fun afterStart(main: MainSupport) {
                 main.stop()
@@ -37,7 +37,7 @@ class IntegrationTest {
     fun `load integration with binding`() {
         var runtime = org.apache.camel.k.jvm.Runtime()
         runtime.duration = 5
-        runtime.load("classpath:routes-with-bindings.kts", null)
+        runtime.load(arrayOf("classpath:routes-with-bindings.kts"))
         runtime.addMainListener(object: MainListenerSupport() {
             override fun afterStart(main: MainSupport) {
                 main.stop()
@@ -60,7 +60,7 @@ class IntegrationTest {
 
         var runtime = org.apache.camel.k.jvm.Runtime()
         runtime.duration = 5
-        runtime.load("classpath:routes-with-component-configuration.kts", null)
+        runtime.load(arrayOf("classpath:routes-with-component-configuration.kts"))
         runtime.addMainListener(object : MainListenerSupport() {
             override fun afterStart(main: MainSupport) {
                 val seda = runtime.camelContext.getComponent("seda", SedaComponent::class.java)


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@xxxxxxxxxxxxxxxx


With regards,
Apache Git Services