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


The following commit(s) were added to refs/heads/master by this push:
     new d0a228c  feature(knative): support configmap and secrets
d0a228c is described below

commit d0a228c4e8e66af51230384ce63257a45aaffb0c
Author: lburgazzoli <lburgazz...@gmail.com>
AuthorDate: Mon Feb 25 22:08:04 2019 +0100

    feature(knative): support configmap and secrets
---
 Gopkg.lock                                         |   9 +
 docs/traits.adoc                                   |  16 +
 pkg/builder/kaniko/publisher.go                    |  10 +-
 pkg/controller/integration/build_image.go          |  40 +-
 .../integration/build_image_failure_recovery.go    |   3 +-
 .../integrationcontext/build_failure_recovery.go   |   1 -
 pkg/trait/builder_test.go                          |   1 +
 pkg/trait/deployer.go                              |  37 +
 pkg/trait/deployment.go                            | 340 +--------
 pkg/trait/environment_test.go                      |   3 +
 pkg/trait/knative_service.go                       | 169 ++---
 pkg/trait/knative_service_env.go                   | 156 ++++
 ...knative_test.go => knative_service_env_test.go} |  14 +-
 pkg/trait/knative_service_vol.go                   |  43 ++
 pkg/trait/knative_service_vol_test.go              | 272 +++++++
 pkg/trait/service.go                               |   3 +-
 pkg/trait/{catalog.go => trait_catalog.go}         |   6 +
 pkg/trait/trait_test.go                            |   1 +
 pkg/trait/trait_types.go                           | 526 +++++++++++++
 pkg/trait/types.go                                 | 234 ------
 pkg/util/kubernetes/collection.go                  |  16 +
 pkg/util/kubernetes/resolver.go                    | 120 +++
 pkg/util/kubernetes/util.go                        |  25 +
 pkg/util/source/util.go                            |  41 -
 pkg/util/test/assertions.go                        |  46 ++
 pkg/util/util.go                                   |  45 ++
 vendor/github.com/magiconair/properties/LICENSE    |  25 +
 .../magiconair/properties/assert/assert.go         |  90 +++
 vendor/github.com/magiconair/properties/decode.go  | 289 +++++++
 vendor/github.com/magiconair/properties/doc.go     | 156 ++++
 .../github.com/magiconair/properties/integrate.go  |  34 +
 vendor/github.com/magiconair/properties/lex.go     | 407 ++++++++++
 vendor/github.com/magiconair/properties/load.go    | 292 ++++++++
 vendor/github.com/magiconair/properties/parser.go  |  95 +++
 .../github.com/magiconair/properties/properties.go | 833 +++++++++++++++++++++
 .../github.com/magiconair/properties/rangecheck.go |  31 +
 36 files changed, 3699 insertions(+), 730 deletions(-)

diff --git a/Gopkg.lock b/Gopkg.lock
index 6cd009c..72f61a1 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -383,6 +383,14 @@
   version = "v1.0.1"
 
 [[projects]]
+  digest = "1:4244266b65ea535b8ebd109a327720821707b59f9a37bda738946d52ec69442d"
+  name = "github.com/magiconair/properties"
+  packages = ["."]
+  pruneopts = "NT"
+  revision = "c2353362d570a7bfa228149c62842019201cfb71"
+  version = "v1.8.0"
+
+[[projects]]
   branch = "master"
   digest = "1:4925ec3736ef6c299cfcf61597782e3d66ec13114f7476019d04c742a7be55d0"
   name = "github.com/mailru/easyjson"
@@ -1104,6 +1112,7 @@
     "github.com/jpillora/backoff",
     "github.com/knative/eventing/pkg/apis/eventing/v1alpha1",
     "github.com/knative/serving/pkg/apis/serving/v1alpha1",
+    "github.com/magiconair/properties",
     "github.com/mitchellh/mapstructure",
     "github.com/openshift/api/apps/v1",
     "github.com/openshift/api/authorization/v1",
diff --git a/docs/traits.adoc b/docs/traits.adoc
index 1d3baa8..d976d3d 100644
--- a/docs/traits.adoc
+++ b/docs/traits.adoc
@@ -62,6 +62,22 @@ The following is a list of common traits that can be 
configured by the end users
   +
   It's enabled by default.
 
+| deployer
+| Kubernetes, OpenShift
+| Configure deployer behavior.
+  +
+  +
+  It's enabled by default.
+
+
+[cols="m,"]
+!===
+
+! deployer.container-image
+! Generates a container image for the Integration that includes the sources 
and resources in the generated images instead of mounting them to the pod.
+
+!===
+
 | deployment
 | Kubernetes, OpenShift
 | Creates a standard Kubernetes deployment for running the integration.
diff --git a/pkg/builder/kaniko/publisher.go b/pkg/builder/kaniko/publisher.go
index fd7b5b4..b897b74 100644
--- a/pkg/builder/kaniko/publisher.go
+++ b/pkg/builder/kaniko/publisher.go
@@ -18,14 +18,15 @@ limitations under the License.
 package kaniko
 
 import (
+       "fmt"
        "io/ioutil"
        "path"
        "time"
 
        "github.com/apache/camel-k/pkg/builder"
+       "github.com/apache/camel-k/pkg/util/kubernetes"
        "github.com/apache/camel-k/pkg/util/tar"
 
-       "github.com/apache/camel-k/pkg/util/kubernetes"
        "github.com/pkg/errors"
 
        corev1 "k8s.io/api/core/v1"
@@ -81,7 +82,9 @@ func Publisher(ctx *builder.Context) error {
                "--cache",
                "--cache-dir=/workspace/cache",
        }
+
        args := append(baseArgs, "--insecure")
+       args = append(args, "--insecure-pull")
 
        if ctx.Request.Platform.Build.PushSecret != "" {
                volumes = append(volumes, corev1.Volume{
@@ -156,8 +159,9 @@ func Publisher(ctx *builder.Context) error {
                if val, ok := obj.(*corev1.Pod); ok {
                        if val.Status.Phase == corev1.PodSucceeded {
                                return true, nil
-                       } else if val.Status.Phase == corev1.PodFailed {
-                               return false, errors.New("build failed")
+                       }
+                       if val.Status.Phase == corev1.PodFailed {
+                               return false, fmt.Errorf("build failed: %s", 
val.Status.Message)
                        }
                }
                return false, nil
diff --git a/pkg/controller/integration/build_image.go 
b/pkg/controller/integration/build_image.go
index 136b33e..3b9b565 100644
--- a/pkg/controller/integration/build_image.go
+++ b/pkg/controller/integration/build_image.go
@@ -26,8 +26,6 @@ import (
 
        "github.com/apache/camel-k/pkg/util/cancellable"
 
-       "github.com/apache/camel-k/pkg/util/source"
-
        "github.com/pkg/errors"
 
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
@@ -36,7 +34,6 @@ import (
        "github.com/apache/camel-k/pkg/trait"
        "github.com/apache/camel-k/pkg/util/digest"
        "github.com/apache/camel-k/pkg/util/kubernetes"
-       corev1 "k8s.io/api/core/v1"
 )
 
 // NewBuildImageAction create an action that handles integration image build
@@ -130,12 +127,14 @@ func (action *buildImageAction) 
handleBuildImageSubmitted(ctx context.Context, i
                        BuildDir:       env.BuildDir,
                        Platform:       env.Platform.Spec,
                        Image:          ictx.Status.Image,
-                       // Sources are added as part of the standard deployment 
bits
-                       Resources: make([]builder.Resource, 0, 
len(integration.Spec.Sources)),
+                       Resources:      make([]builder.Resource, 0, 
len(integration.Spec.Sources)),
                }
 
-               // inline resources so they are copied over the generated
+               // inline source and resources so they are copied over the 
generated
                // container image
+               if err := action.inlineSources(ctx, integration, &r, env); err 
!= nil {
+                       return err
+               }
                if err := action.inlineResources(ctx, integration, &r, env); 
err != nil {
                        return err
                }
@@ -213,27 +212,32 @@ func (action *buildImageAction) 
handleBuildStateChange(ctx context.Context, res
        return nil
 }
 
-func (action *buildImageAction) inlineResources(ctx context.Context, 
integration *v1alpha1.Integration, r *builder.Request, e *trait.Environment) 
error {
-       sources, err := source.Resolve(integration.Sources(), func(name string) 
(*corev1.ConfigMap, error) {
-               cm := e.Resources.GetConfigMap(func(cm *corev1.ConfigMap) bool {
-                       return cm.Name == name
-               })
+func (action *buildImageAction) inlineSources(ctx context.Context, integration 
*v1alpha1.Integration, r *builder.Request, e *trait.Environment) error {
+       sources, err := kubernetes.ResolveIntegrationSources(ctx, 
action.client, integration, e.Resources)
+       if err != nil {
+               return err
+       }
 
-               if cm != nil {
-                       return cm, nil
-               }
+       for _, data := range sources {
+               r.Resources = append(r.Resources, builder.Resource{
+                       Content: []byte(data.Content),
+                       Target:  path.Join("sources", data.Name),
+               })
+       }
 
-               return kubernetes.GetConfigMap(ctx, action.client, name, 
integration.Namespace)
-       })
+       return nil
+}
 
+func (action *buildImageAction) inlineResources(ctx context.Context, 
integration *v1alpha1.Integration, r *builder.Request, e *trait.Environment) 
error {
+       resources, err := kubernetes.ResolveIntegrationResources(ctx, 
action.client, integration, e.Resources)
        if err != nil {
                return err
        }
 
-       for _, data := range sources {
+       for _, data := range resources {
                r.Resources = append(r.Resources, builder.Resource{
                        Content: []byte(data.Content),
-                       Target:  path.Join("sources", data.Name),
+                       Target:  path.Join("resources", data.Name),
                })
        }
 
diff --git a/pkg/controller/integration/build_image_failure_recovery.go 
b/pkg/controller/integration/build_image_failure_recovery.go
index 920c8aa..c32ce3e 100644
--- a/pkg/controller/integration/build_image_failure_recovery.go
+++ b/pkg/controller/integration/build_image_failure_recovery.go
@@ -92,8 +92,7 @@ func (action *errorRecoveryAction) Handle(ctx 
context.Context, integration *v1al
                target.Status.Failure.Recovery.Attempt = 
integration.Status.Failure.Recovery.Attempt + 1
                target.Status.Failure.Recovery.AttemptTime = metav1.Now()
 
-               action.L.Info("Recovery attempt (%d/%d)",
-                       integration.Name,
+               action.L.Infof("Recovery attempt (%d/%d)",
                        target.Status.Failure.Recovery.Attempt,
                        target.Status.Failure.Recovery.AttemptMax,
                )
diff --git a/pkg/controller/integrationcontext/build_failure_recovery.go 
b/pkg/controller/integrationcontext/build_failure_recovery.go
index a32a751..f719fe3 100644
--- a/pkg/controller/integrationcontext/build_failure_recovery.go
+++ b/pkg/controller/integrationcontext/build_failure_recovery.go
@@ -93,7 +93,6 @@ func (action *errorRecoveryAction) Handle(ctx 
context.Context, ictx *v1alpha1.In
                target.Status.Failure.Recovery.AttemptTime = metav1.Now()
 
                action.L.Info("Recovery attempt (%d/%d)",
-                       ictx.Name,
                        target.Status.Failure.Recovery.Attempt,
                        target.Status.Failure.Recovery.AttemptMax,
                )
diff --git a/pkg/trait/builder_test.go b/pkg/trait/builder_test.go
index 1bd7da6..558d83d 100644
--- a/pkg/trait/builder_test.go
+++ b/pkg/trait/builder_test.go
@@ -126,6 +126,7 @@ func createBuilderTestEnv(cluster 
v1alpha1.IntegrationPlatformCluster, strategy
        return &Environment{
                C:            context.TODO(),
                CamelCatalog: c,
+               Catalog:      NewCatalog(context.TODO(), nil),
                Integration: &v1alpha1.Integration{
                        ObjectMeta: metav1.ObjectMeta{
                                Name:      "test",
diff --git a/pkg/trait/deployer.go b/pkg/trait/deployer.go
new file mode 100644
index 0000000..459ec30
--- /dev/null
+++ b/pkg/trait/deployer.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 trait
+
+type deployerTrait struct {
+       BaseTrait      `property:",squash"`
+       ContainerImage bool `property:"container-image"`
+}
+
+func newDeployerTrait() *deployerTrait {
+       return &deployerTrait{
+               BaseTrait: newBaseTrait("deployer"),
+       }
+}
+
+func (t *deployerTrait) Configure(e *Environment) (bool, error) {
+       return true, nil
+}
+
+func (t *deployerTrait) Apply(e *Environment) error {
+       return nil
+}
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index f91d002..7aebbee 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -18,24 +18,19 @@ limitations under the License.
 package trait
 
 import (
-       "fmt"
-       "path"
-       "strconv"
        "strings"
 
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        "github.com/apache/camel-k/pkg/util/envvar"
-       "github.com/apache/camel-k/pkg/util/kubernetes"
 
        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 {
-       BaseTrait      `property:",squash"`
-       ContainerImage bool `property:"container-image"`
+       BaseTrait `property:",squash"`
+       deployer  deployerTrait
 }
 
 func newDeploymentTrait() *deploymentTrait {
@@ -49,32 +44,38 @@ func (t *deploymentTrait) Configure(e *Environment) (bool, 
error) {
                return false, nil
        }
 
+       enabled := false
+
        if e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying) {
                //
                // Don't deploy when a different strategy is needed (e.g. 
Knative)
                //
-               var strategy ControllerStrategy
-               var err error
-               if strategy, err = e.DetermineControllerStrategy(t.ctx, 
t.client); err != nil {
+               strategy, err := e.DetermineControllerStrategy(t.ctx, t.client)
+               if err != nil {
                        return false, err
                }
-               return strategy == ControllerStrategyDeployment, nil
+
+               enabled = strategy == ControllerStrategyDeployment
+       } else if 
e.IntegrationContextInPhase(v1alpha1.IntegrationContextPhaseReady) &&
+               e.IntegrationInPhase(v1alpha1.IntegrationPhaseBuildingContext, 
v1alpha1.IntegrationPhaseResolvingContext) {
+               enabled = true
        }
 
-       if e.IntegrationContextInPhase(v1alpha1.IntegrationContextPhaseReady) &&
-               (e.IntegrationInPhase(v1alpha1.IntegrationPhaseBuildingContext) 
||
-                       
e.IntegrationInPhase(v1alpha1.IntegrationPhaseResolvingContext)) {
-               return true, nil
+       if enabled {
+               dt := e.Catalog.GetTrait("deployer")
+               if dt != nil {
+                       t.deployer = *dt.(*deployerTrait)
+               }
        }
 
-       return false, nil
+       return enabled, nil
 }
 
 func (t *deploymentTrait) Apply(e *Environment) error {
        if e.IntegrationContextInPhase(v1alpha1.IntegrationContextPhaseReady) &&
-               (e.IntegrationInPhase(v1alpha1.IntegrationPhaseBuildingContext) 
||
-                       
e.IntegrationInPhase(v1alpha1.IntegrationPhaseResolvingContext)) {
-               if t.ContainerImage {
+               e.IntegrationInPhase(v1alpha1.IntegrationPhaseBuildingContext, 
v1alpha1.IntegrationPhaseResolvingContext) {
+
+               if t.deployer.ContainerImage {
                        // trigger container image build
                        e.Integration.Status.Phase = 
v1alpha1.IntegrationPhaseBuildImageSubmitted
                } else {
@@ -84,7 +85,7 @@ func (t *deploymentTrait) Apply(e *Environment) error {
        }
 
        if e.Integration != nil && e.Integration.Status.Phase == 
v1alpha1.IntegrationPhaseDeploying {
-               e.Resources.AddAll(t.getConfigMapsFor(e))
+               
e.Resources.AddAll(e.ComputeConfigMaps(t.deployer.ContainerImage))
                e.Resources.Add(t.getDeploymentFor(e))
        }
 
@@ -93,153 +94,12 @@ func (t *deploymentTrait) Apply(e *Environment) error {
 
 // **********************************
 //
-// ConfigMap
-//
-// **********************************
-
-func (t *deploymentTrait) getConfigMapsFor(e *Environment) []runtime.Object {
-       sources := e.Integration.Sources()
-       maps := make([]runtime.Object, 0, len(sources)+1)
-
-       // combine properties of integration with context, integration
-       // properties have the priority
-       properties := ""
-
-       VisitKeyValConfigurations("property", e.IntegrationContext, 
e.Integration, func(key string, val string) {
-               properties += fmt.Sprintf("%s=%s\n", key, val)
-       })
-
-       maps = append(
-               maps,
-               &corev1.ConfigMap{
-                       TypeMeta: metav1.TypeMeta{
-                               Kind:       "ConfigMap",
-                               APIVersion: "v1",
-                       },
-                       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": properties,
-                       },
-               },
-       )
-
-       if !t.ContainerImage {
-
-               // do not create 'source' or 'resource' ConfigMap if a docker 
images for deployment
-               // is required
-
-               for i, s := range sources {
-                       if s.ContentRef != "" {
-                               continue
-                       }
-
-                       cm := 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.InferLanguage()),
-                                               "camel.apache.org/source.name": 
       s.Name,
-                                               
"camel.apache.org/source.compression": strconv.FormatBool(s.Compression),
-                                       },
-                               },
-                               Data: map[string]string{
-                                       "content": s.Content,
-                               },
-                       }
-
-                       maps = append(maps, &cm)
-               }
-
-               for i, s := range e.Integration.Spec.Resources {
-                       if s.Type != v1alpha1.ResourceTypeData {
-                               continue
-                       }
-
-                       cm := corev1.ConfigMap{
-                               TypeMeta: metav1.TypeMeta{
-                                       Kind:       "ConfigMap",
-                                       APIVersion: "v1",
-                               },
-                               ObjectMeta: metav1.ObjectMeta{
-                                       Name:      
fmt.Sprintf("%s-resource-%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/resource.name":        s.Name,
-                                               
"camel.apache.org/resource.compression": strconv.FormatBool(s.Compression),
-                                       },
-                               },
-                               Data: map[string]string{
-                                       "content": s.Content,
-                               },
-                       }
-
-                       maps = append(maps, &cm)
-               }
-       }
-
-       return maps
-}
-
-// **********************************
-//
 // Deployment
 //
 // **********************************
 
-func (t *deploymentTrait) getSources(e *Environment) []string {
-       sources := e.Integration.Sources()
-       paths := make([]string, 0, len(sources))
-
-       for _, s := range sources {
-               root := "/etc/camel/sources"
-
-               if t.ContainerImage {
-
-                       // assume sources are copied over the standard 
deployments folder
-                       root = "/deployments/sources"
-               }
-
-               srcName := strings.TrimPrefix(s.Name, "/")
-               src := path.Join(root, srcName)
-               src = "file:" + src
-
-               params := make([]string, 0)
-               if s.InferLanguage() != "" {
-                       params = append(params, 
"language="+string(s.InferLanguage()))
-               }
-               if s.Compression {
-                       params = append(params, "compression=true")
-               }
-
-               if len(params) > 0 {
-                       src = fmt.Sprintf("%s?%s", src, strings.Join(params, 
"&"))
-               }
-
-               paths = append(paths, src)
-       }
-
-       return paths
-}
-
 func (t *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment {
-       paths := t.getSources(e)
+       paths := e.ComputeSourcesURI(t.deployer.ContainerImage)
        environment := make([]corev1.EnvVar, 0)
 
        // combine Environment of integration with context, integration
@@ -312,157 +172,11 @@ func (t *deploymentTrait) getDeploymentFor(e 
*Environment) *appsv1.Deployment {
                },
        }
 
-       //
-       // Volumes :: Setup
-       //
-
-       vols := make([]corev1.Volume, 0)
-       mnts := make([]corev1.VolumeMount, 0)
-
-       //
-       // Volumes :: Properties
-       //
-
-       vols = append(vols, corev1.Volume{
-               Name: "integration-properties",
-               VolumeSource: corev1.VolumeSource{
-                       ConfigMap: &corev1.ConfigMapVolumeSource{
-                               LocalObjectReference: 
corev1.LocalObjectReference{
-                                       Name: e.Integration.Name + 
"-properties",
-                               },
-                               Items: []corev1.KeyToPath{
-                                       {
-                                               Key:  "properties",
-                                               Path: "application.properties",
-                                       },
-                               },
-                       },
-               },
-       })
-
-       mnts = append(mnts, corev1.VolumeMount{
-               Name:      "integration-properties",
-               MountPath: "/etc/camel/conf",
-       })
-
-       //
-       // Volumes :: Sources
-       //
-
-       if !t.ContainerImage {
-               // We can configure the operator to generate a container images 
that include
-               // integration sources instead of mounting it at runtime and in 
such case we
-               // do not need to mount any 'source' ConfigMap to the pod
-
-               for i, s := range e.Integration.Sources() {
-                       cmName := fmt.Sprintf("%s-source-%03d", 
e.Integration.Name, i)
-                       refName := fmt.Sprintf("integration-source-%03d", i)
-                       resName := strings.TrimPrefix(s.Name, "/")
-
-                       if s.ContentRef != "" {
-                               cmName = s.ContentRef
-                       }
-
-                       vols = append(vols, corev1.Volume{
-                               Name: refName,
-                               VolumeSource: corev1.VolumeSource{
-                                       ConfigMap: 
&corev1.ConfigMapVolumeSource{
-                                               LocalObjectReference: 
corev1.LocalObjectReference{
-                                                       Name: cmName,
-                                               },
-                                       },
-                               },
-                       })
-
-                       mnts = append(mnts, corev1.VolumeMount{
-                               Name:      refName,
-                               MountPath: path.Join("/etc/camel/sources", 
resName),
-                               SubPath:   "content",
-                       })
-               }
-
-               for i, r := range e.Integration.Spec.Resources {
-                       if r.Type != v1alpha1.ResourceTypeData {
-                               continue
-                       }
-
-                       cmName := fmt.Sprintf("%s-resource-%03d", 
e.Integration.Name, i)
-                       refName := fmt.Sprintf("integration-resource-%03d", i)
-                       resName := strings.TrimPrefix(r.Name, "/")
-
-                       vols = append(vols, corev1.Volume{
-                               Name: refName,
-                               VolumeSource: corev1.VolumeSource{
-                                       ConfigMap: 
&corev1.ConfigMapVolumeSource{
-                                               LocalObjectReference: 
corev1.LocalObjectReference{
-                                                       Name: cmName,
-                                               },
-                                       },
-                               },
-                       })
-
-                       mnts = append(mnts, corev1.VolumeMount{
-                               Name:      refName,
-                               MountPath: path.Join("/etc/camel/resources", 
resName),
-                               SubPath:   "content",
-                       })
-               }
-       }
-
-       //
-       // Volumes :: Additional ConfigMaps
-       //
-
-       VisitConfigurations("configmap", e.IntegrationContext, e.Integration, 
func(cmName string) {
-               refName := kubernetes.SanitizeLabel(cmName)
-               fileName := "integration-cm-" + strings.ToLower(cmName)
-
-               vols = append(vols, corev1.Volume{
-                       Name: refName,
-                       VolumeSource: corev1.VolumeSource{
-                               ConfigMap: &corev1.ConfigMapVolumeSource{
-                                       LocalObjectReference: 
corev1.LocalObjectReference{
-                                               Name: cmName,
-                                       },
-                               },
-                       },
-               })
-
-               mnts = append(mnts, corev1.VolumeMount{
-                       Name:      refName,
-                       MountPath: path.Join("/etc/camel/conf.d", fileName),
-               })
-       })
-
-       //
-       // Volumes :: Additional Secrets
-       //
-
-       VisitConfigurations("secret", e.IntegrationContext, e.Integration, 
func(secretName string) {
-               refName := kubernetes.SanitizeLabel(secretName)
-               fileName := "integration-secret-" + strings.ToLower(secretName)
-
-               vols = append(vols, corev1.Volume{
-                       Name: refName,
-                       VolumeSource: corev1.VolumeSource{
-                               Secret: &corev1.SecretVolumeSource{
-                                       SecretName: secretName,
-                               },
-                       },
-               })
-
-               mnts = append(mnts, corev1.VolumeMount{
-                       Name:      refName,
-                       MountPath: path.Join("/etc/camel/conf.d", fileName),
-               })
-       })
-
-       //
-       // Volumes
-       //
-
-       deployment.Spec.Template.Spec.Volumes = vols
-       deployment.Spec.Template.Spec.Containers[0].VolumeMounts = mnts
+       e.ConfigureVolumesAndMounts(
+               t.deployer.ContainerImage,
+               &deployment.Spec.Template.Spec.Volumes,
+               &deployment.Spec.Template.Spec.Containers[0].VolumeMounts,
+       )
 
        return &deployment
 }
diff --git a/pkg/trait/environment_test.go b/pkg/trait/environment_test.go
index 1f05ede..8e3340f 100644
--- a/pkg/trait/environment_test.go
+++ b/pkg/trait/environment_test.go
@@ -37,6 +37,7 @@ func TestDefaultEnvironment(t *testing.T) {
 
        env := Environment{
                CamelCatalog: catalog,
+               Catalog:      NewCatalog(context.TODO(), nil),
                Integration: &v1alpha1.Integration{
                        Status: v1alpha1.IntegrationStatus{
                                Phase: v1alpha1.IntegrationPhaseDeploying,
@@ -89,6 +90,7 @@ func TestEnabledContainerMetaDataEnvVars(t *testing.T) {
 
        env := Environment{
                CamelCatalog: c,
+               Catalog:      NewCatalog(context.TODO(), nil),
                Integration: &v1alpha1.Integration{
                        Status: v1alpha1.IntegrationStatus{
                                Phase: v1alpha1.IntegrationPhaseDeploying,
@@ -148,6 +150,7 @@ func TestDisabledContainerMetaDataEnvVars(t *testing.T) {
 
        env := Environment{
                CamelCatalog: c,
+               Catalog:      NewCatalog(context.TODO(), nil),
                Integration: &v1alpha1.Integration{
                        Status: v1alpha1.IntegrationStatus{
                                Phase: v1alpha1.IntegrationPhaseDeploying,
diff --git a/pkg/trait/knative_service.go b/pkg/trait/knative_service.go
index 2f16c84..b8df091 100644
--- a/pkg/trait/knative_service.go
+++ b/pkg/trait/knative_service.go
@@ -18,9 +18,9 @@ limitations under the License.
 package trait
 
 import (
-       "fmt"
        "strconv"
-       "strings"
+
+       "github.com/apache/camel-k/pkg/util/kubernetes"
 
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        "github.com/apache/camel-k/pkg/metadata"
@@ -40,13 +40,15 @@ const (
 )
 
 type knativeServiceTrait struct {
-       BaseTrait `property:",squash"`
-       Class     string `property:"autoscaling-class"`
-       Metric    string `property:"autoscaling-metric"`
-       Target    *int   `property:"autoscaling-target"`
-       MinScale  *int   `property:"min-scale"`
-       MaxScale  *int   `property:"max-scale"`
-       Auto      *bool  `property:"auto"`
+       BaseTrait         `property:",squash"`
+       Class             string `property:"autoscaling-class"`
+       Metric            string `property:"autoscaling-metric"`
+       Target            *int   `property:"autoscaling-target"`
+       MinScale          *int   `property:"min-scale"`
+       MaxScale          *int   `property:"max-scale"`
+       Auto              *bool  `property:"auto"`
+       ConfigurationType string `property:"configuration-type"`
+       deployer          deployerTrait
 }
 
 func newKnativeServiceTrait() *knativeServiceTrait {
@@ -64,9 +66,8 @@ func (t *knativeServiceTrait) Configure(e *Environment) 
(bool, error) {
                return false, nil
        }
 
-       var strategy ControllerStrategy
-       var err error
-       if strategy, err = e.DetermineControllerStrategy(t.ctx, t.client); err 
!= nil {
+       strategy, err := e.DetermineControllerStrategy(t.ctx, t.client)
+       if err != nil {
                return false, err
        }
        if strategy != ControllerStrategyKnativeService {
@@ -87,12 +88,12 @@ func (t *knativeServiceTrait) Configure(e *Environment) 
(bool, error) {
        if t.Auto == nil || *t.Auto {
                // Check the right value for minScale, as not all services are 
allowed to scale down to 0
                if t.MinScale == nil {
-                       var sources []v1alpha1.SourceSpec
-                       if sources, err = e.ResolveSources(t.ctx, t.client); 
err != nil {
+                       sources, err := 
kubernetes.ResolveIntegrationSources(t.ctx, t.client, e.Integration, 
e.Resources)
+                       if err != nil {
                                return false, err
                        }
-                       meta := metadata.ExtractAll(e.CamelCatalog, sources)
 
+                       meta := metadata.ExtractAll(e.CamelCatalog, sources)
                        if !meta.RequiresHTTPService || !meta.PassiveEndpoints {
                                single := 1
                                t.MinScale = &single
@@ -100,6 +101,11 @@ func (t *knativeServiceTrait) Configure(e *Environment) 
(bool, error) {
                }
        }
 
+       dt := e.Catalog.GetTrait("deployer")
+       if dt != nil {
+               t.deployer = *dt.(*deployerTrait)
+       }
+
        return true, nil
 }
 
@@ -109,107 +115,15 @@ func (t *knativeServiceTrait) Apply(e *Environment) 
error {
                return err
        }
 
+       maps := e.ComputeConfigMaps(t.deployer.ContainerImage)
+
        e.Resources.Add(svc)
+       e.Resources.AddAll(maps)
 
        return nil
 }
 
 func (t *knativeServiceTrait) getServiceFor(e *Environment) (*serving.Service, 
error) {
-       // combine properties of integration with context, integration
-       // properties have the priority
-       properties := ""
-
-       VisitKeyValConfigurations("property", e.IntegrationContext, 
e.Integration, func(key string, val string) {
-               properties += fmt.Sprintf("%s=%s\n", key, val)
-       })
-
-       environment := make([]corev1.EnvVar, 0)
-
-       // combine Environment of integration with context, integration
-       // Environment has the priority
-       VisitKeyValConfigurations("env", e.IntegrationContext, e.Integration, 
func(key string, value string) {
-               envvar.SetVal(&environment, key, value)
-       })
-
-       sourcesSpecs, err := e.ResolveSources(t.ctx, t.client)
-       if err != nil {
-               return nil, err
-       }
-
-       sources := make([]string, 0, len(e.Integration.Spec.Sources))
-       for i, s := range sourcesSpecs {
-               if s.Content == "" {
-                       t.L.Debug("Source %s has and empty content", s.Name)
-               }
-
-               envName := fmt.Sprintf("CAMEL_K_ROUTE_%03d", i)
-               envvar.SetVal(&environment, envName, s.Content)
-
-               params := make([]string, 0)
-               params = append(params, "name="+s.Name)
-
-               if s.InferLanguage() != "" {
-                       params = append(params, 
"language="+string(s.InferLanguage()))
-               }
-               if s.Compression {
-                       params = append(params, "compression=true")
-               }
-
-               src := fmt.Sprintf("env:%s", envName)
-               if len(params) > 0 {
-                       src = fmt.Sprintf("%s?%s", src, strings.Join(params, 
"&"))
-               }
-
-               sources = append(sources, src)
-       }
-
-       for i, r := range e.Integration.Spec.Resources {
-               if r.Type != v1alpha1.ResourceTypeData {
-                       continue
-               }
-
-               envName := fmt.Sprintf("CAMEL_K_RESOURCE_%03d", i)
-               envvar.SetVal(&environment, envName, r.Content)
-
-               params := make([]string, 0)
-               if r.Compression {
-                       params = append(params, "compression=true")
-               }
-
-               envValue := fmt.Sprintf("env:%s", envName)
-               if len(params) > 0 {
-                       envValue = fmt.Sprintf("%s?%s", envValue, 
strings.Join(params, "&"))
-               }
-
-               envName = r.Name
-               envName = strings.ToUpper(envName)
-               envName = strings.Replace(envName, "-", "_", -1)
-               envName = strings.Replace(envName, ".", "_", -1)
-               envName = strings.Replace(envName, " ", "_", -1)
-
-               envvar.SetVal(&environment, envName, envValue)
-       }
-
-       // set env vars needed by the runtime
-       envvar.SetVal(&environment, "JAVA_MAIN_CLASS", 
"org.apache.camel.k.jvm.Application")
-
-       // camel-k runtime
-       envvar.SetVal(&environment, "CAMEL_K_ROUTES", strings.Join(sources, 
","))
-       envvar.SetVal(&environment, "CAMEL_K_CONF", "env:CAMEL_K_PROPERTIES")
-       envvar.SetVal(&environment, "CAMEL_K_PROPERTIES", properties)
-
-       // add a dummy env var to trigger deployment if everything but the code
-       // has been changed
-       envvar.SetVal(&environment, "CAMEL_K_DIGEST", 
e.Integration.Status.Digest)
-
-       // optimizations
-       envvar.SetVal(&environment, "AB_JOLOKIA_OFF", True)
-
-       // add env vars from traits
-       for _, envVar := range e.EnvVars {
-               envvar.SetVar(&environment, envVar)
-       }
-
        labels := map[string]string{
                "camel.apache.org/integration": e.Integration.Name,
        }
@@ -260,7 +174,7 @@ func (t *knativeServiceTrait) getServiceFor(e *Environment) 
(*serving.Service, e
                                                        ServiceAccountName: 
e.Integration.Spec.ServiceAccountName,
                                                        Container: 
corev1.Container{
                                                                Image: 
e.Integration.Status.Image,
-                                                               Env:   
environment,
+                                                               Env:   
make([]corev1.EnvVar, 0),
                                                        },
                                                },
                                        },
@@ -269,5 +183,38 @@ func (t *knativeServiceTrait) getServiceFor(e 
*Environment) (*serving.Service, e
                },
        }
 
+       environment := 
&svc.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.Env
+
+       // combine Environment of integration with context, integration
+       // Environment has the priority
+       VisitKeyValConfigurations("env", e.IntegrationContext, e.Integration, 
func(key string, value string) {
+               envvar.SetVal(environment, key, value)
+       })
+
+       // set env vars needed by the runtime
+       envvar.SetVal(environment, "JAVA_MAIN_CLASS", 
"org.apache.camel.k.jvm.Application")
+
+       // add a dummy env var to trigger deployment if everything but the code
+       // has been changed
+       envvar.SetVal(environment, "CAMEL_K_DIGEST", 
e.Integration.Status.Digest)
+
+       // optimizations
+       envvar.SetVal(environment, "AB_JOLOKIA_OFF", True)
+
+       if t.ConfigurationType == "volume" {
+               if err := t.bindToVolumes(e, &svc); err != nil {
+                       return nil, err
+               }
+       } else {
+               if err := t.bindToEnvVar(e, &svc); err != nil {
+                       return nil, err
+               }
+       }
+
+       // add env vars from traits
+       for _, envVar := range e.EnvVars {
+               
envvar.SetVar(&svc.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.Env,
 envVar)
+       }
+
        return &svc, nil
 }
diff --git a/pkg/trait/knative_service_env.go b/pkg/trait/knative_service_env.go
new file mode 100644
index 0000000..04a181f
--- /dev/null
+++ b/pkg/trait/knative_service_env.go
@@ -0,0 +1,156 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package trait
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/apache/camel-k/pkg/util"
+
+       "github.com/apache/camel-k/pkg/util/kubernetes"
+
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+       "github.com/apache/camel-k/pkg/util/envvar"
+       serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
+)
+
+func (t *knativeServiceTrait) bindToEnvVar(e *Environment, service 
*serving.Service) error {
+       environment := 
&service.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.Env
+
+       //
+       // Properties
+       //
+
+       properties := make(map[string]string)
+
+       VisitKeyValConfigurations("property", e.IntegrationContext, 
e.Integration, func(key string, val string) {
+               properties[key] = val
+       })
+
+       VisitConfigurations("configmap", e.IntegrationContext, e.Integration, 
func(cmName string) {
+               cm, err := kubernetes.GetConfigMap(e.C, e.Client, 
e.Integration.Namespace, cmName)
+               if err != nil {
+                       t.L.Errorf(err, "failed to lookup ConfigMap %s", cmName)
+               }
+               if cm != nil {
+                       util.ExtractApplicationProperties(cm.Data, func(key 
string, val string) {
+                               properties[key] = val
+                       })
+               }
+       })
+
+       VisitConfigurations("secret", e.IntegrationContext, e.Integration, 
func(secretName string) {
+               cm, err := kubernetes.GetSecret(e.C, e.Client, 
e.Integration.Namespace, secretName)
+               if err != nil {
+                       t.L.Errorf(err, "failed to lookup Secret %s", 
secretName)
+               }
+               if cm != nil {
+                       util.ExtractEncodedApplicationProperties(cm.Data, 
func(key string, val string) {
+                               properties[key] = val
+                       })
+               }
+       })
+
+       p := ""
+
+       for k, v := range properties {
+               p += fmt.Sprintf("%s=%s\n", k, v)
+       }
+
+       envvar.SetVal(environment, "CAMEL_K_CONF", "env:CAMEL_K_PROPERTIES")
+       envvar.SetVal(environment, "CAMEL_K_PROPERTIES", p)
+
+       //
+       // Sources
+       //
+
+       sourcesSpecs, err := kubernetes.ResolveIntegrationSources(t.ctx, 
t.client, e.Integration, e.Resources)
+       if err != nil {
+               return err
+       }
+
+       sources := make([]string, 0, len(e.Integration.Spec.Sources))
+
+       for i, s := range sourcesSpecs {
+               if s.Content == "" {
+                       t.L.Debug("Source %s has and empty content", s.Name)
+               }
+
+               envName := fmt.Sprintf("CAMEL_K_ROUTE_%03d", i)
+               envvar.SetVal(environment, envName, s.Content)
+
+               params := make([]string, 0)
+               params = append(params, "name="+s.Name)
+
+               if s.InferLanguage() != "" {
+                       params = append(params, 
"language="+string(s.InferLanguage()))
+               }
+               if s.Compression {
+                       params = append(params, "compression=true")
+               }
+
+               src := fmt.Sprintf("env:%s", envName)
+               if len(params) > 0 {
+                       src = fmt.Sprintf("%s?%s", src, strings.Join(params, 
"&"))
+               }
+
+               sources = append(sources, src)
+       }
+
+       // camel-k runtime
+       envvar.SetVal(environment, "CAMEL_K_ROUTES", strings.Join(sources, ","))
+
+       //
+       // Resources
+       //
+
+       resourcesSpecs, err := kubernetes.ResolveIntegrationResources(t.ctx, 
t.client, e.Integration, e.Resources)
+       if err != nil {
+               return err
+       }
+
+       for i, r := range resourcesSpecs {
+               if r.Type != v1alpha1.ResourceTypeData {
+                       continue
+               }
+
+               envName := fmt.Sprintf("CAMEL_K_RESOURCE_%03d", i)
+               envvar.SetVal(environment, envName, r.Content)
+
+               params := make([]string, 0)
+               if r.Compression {
+                       params = append(params, "compression=true")
+               }
+
+               envValue := fmt.Sprintf("env:%s", envName)
+               if len(params) > 0 {
+                       envValue = fmt.Sprintf("%s?%s", envValue, 
strings.Join(params, "&"))
+               }
+
+               envName = r.Name
+               envName = strings.ToUpper(envName)
+               envName = strings.Replace(envName, "-", "_", -1)
+               envName = strings.Replace(envName, ".", "_", -1)
+               envName = strings.Replace(envName, " ", "_", -1)
+
+               envvar.SetVal(environment, envName, envValue)
+       }
+
+       return nil
+}
diff --git a/pkg/trait/knative_test.go b/pkg/trait/knative_service_env_test.go
similarity index 96%
rename from pkg/trait/knative_test.go
rename to pkg/trait/knative_service_env_test.go
index fcb8a48..51af21c 100644
--- a/pkg/trait/knative_test.go
+++ b/pkg/trait/knative_service_env_test.go
@@ -40,8 +40,11 @@ func TestKnativeTraitWithCompressedSources(t *testing.T) {
        catalog, err := test.DefaultCatalog()
        assert.Nil(t, err)
 
+       traitCatalog := NewCatalog(context.TODO(), nil)
+
        environment := Environment{
                CamelCatalog: catalog,
+               Catalog:      traitCatalog,
                Integration: &v1alpha1.Integration{
                        ObjectMeta: metav1.ObjectMeta{
                                Name:      "test",
@@ -96,7 +99,7 @@ func TestKnativeTraitWithCompressedSources(t *testing.T) {
                Resources:      kubernetes.NewCollection(),
        }
 
-       err = NewKnativeTestCatalog().apply(&environment)
+       err = traitCatalog.apply(&environment)
 
        assert.Nil(t, err)
        assert.NotEmpty(t, environment.ExecutedTraits)
@@ -144,8 +147,11 @@ func TestKnativeTraitWithConfigMapSources(t *testing.T) {
        catalog, err := test.DefaultCatalog()
        assert.Nil(t, err)
 
+       traitCatalog := NewCatalog(context.TODO(), nil)
+
        environment := Environment{
                CamelCatalog: catalog,
+               Catalog:      traitCatalog,
                Integration: &v1alpha1.Integration{
                        ObjectMeta: metav1.ObjectMeta{
                                Name:      "test",
@@ -194,7 +200,7 @@ func TestKnativeTraitWithConfigMapSources(t *testing.T) {
                }),
        }
 
-       err = NewKnativeTestCatalog().apply(&environment)
+       err = traitCatalog.apply(&environment)
 
        assert.Nil(t, err)
        assert.NotEmpty(t, environment.ExecutedTraits)
@@ -219,7 +225,3 @@ func TestKnativeTraitWithConfigMapSources(t *testing.T) {
        assert.True(t, services > 0)
        assert.True(t, environment.Resources.Size() > 0)
 }
-
-func NewKnativeTestCatalog() *Catalog {
-       return NewCatalog(context.TODO(), nil)
-}
diff --git a/pkg/trait/knative_service_vol.go b/pkg/trait/knative_service_vol.go
new file mode 100644
index 0000000..9bf1ee7
--- /dev/null
+++ b/pkg/trait/knative_service_vol.go
@@ -0,0 +1,43 @@
+/*
+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 (
+       "strings"
+
+       "github.com/apache/camel-k/pkg/util/envvar"
+       serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
+)
+
+func (t *knativeServiceTrait) bindToVolumes(e *Environment, service 
*serving.Service) error {
+       e.ConfigureVolumesAndMounts(
+               t.deployer.ContainerImage,
+               
&service.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Volumes,
+               
&service.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.VolumeMounts,
+       )
+
+       paths := e.ComputeSourcesURI(t.deployer.ContainerImage)
+
+       environment := 
&service.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.Env
+
+       envvar.SetVal(environment, "CAMEL_K_ROUTES", strings.Join(paths, ","))
+       envvar.SetVal(environment, "CAMEL_K_CONF", 
"/etc/camel/conf/application.properties")
+       envvar.SetVal(environment, "CAMEL_K_CONF_D", "/etc/camel/conf.d")
+
+       return nil
+}
diff --git a/pkg/trait/knative_service_vol_test.go 
b/pkg/trait/knative_service_vol_test.go
new file mode 100644
index 0000000..01c8a7b
--- /dev/null
+++ b/pkg/trait/knative_service_vol_test.go
@@ -0,0 +1,272 @@
+/*
+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 (
+       "context"
+       "testing"
+
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+       "github.com/apache/camel-k/pkg/util/envvar"
+       "github.com/apache/camel-k/pkg/util/kubernetes"
+       "github.com/apache/camel-k/pkg/util/test"
+
+       serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func TestKnativeWithVolumeBinding(t *testing.T) {
+       catalog, err := test.DefaultCatalog()
+       assert.Nil(t, err)
+
+       traitCatalog := NewCatalog(context.TODO(), nil)
+
+       environment := Environment{
+               CamelCatalog: catalog,
+               Catalog:      traitCatalog,
+               Integration: &v1alpha1.Integration{
+                       ObjectMeta: metav1.ObjectMeta{
+                               Name:      "test",
+                               Namespace: "ns",
+                       },
+                       Status: v1alpha1.IntegrationStatus{
+                               Phase: v1alpha1.IntegrationPhaseDeploying,
+                       },
+                       Spec: v1alpha1.IntegrationSpec{
+                               Profile: v1alpha1.TraitProfileKnative,
+                               Sources: []v1alpha1.SourceSpec{
+                                       {
+                                               DataSpec: v1alpha1.DataSpec{
+                                                       Name:        
"routes.js",
+                                                       Content:     
`from("undertow:test").log("hello")`,
+                                                       Compression: true,
+                                               },
+                                               Language: 
v1alpha1.LanguageJavaScript,
+                                       },
+                               },
+                               Resources: []v1alpha1.ResourceSpec{
+                                       {
+                                               DataSpec: v1alpha1.DataSpec{
+                                                       Name:        
"my-resource.txt",
+                                                       Content:     "",
+                                                       Compression: false,
+                                               },
+                                               Type: v1alpha1.ResourceTypeData,
+                                       },
+                               },
+                               Configuration: []v1alpha1.ConfigurationSpec{
+                                       {Type: "configmap", Value: "my-cm"},
+                                       {Type: "secret", Value: "my-secret"},
+                                       {Type: "property", Value: 
"my-property=my-property-value"},
+                               },
+                               Traits: 
map[string]v1alpha1.IntegrationTraitSpec{
+                                       "knative-service": {
+                                               Configuration: 
map[string]string{
+                                                       "configuration-type": 
"volume",
+                                               },
+                                       },
+                               },
+                       },
+               },
+               Platform: &v1alpha1.IntegrationPlatform{
+                       Spec: v1alpha1.IntegrationPlatformSpec{
+                               Cluster: 
v1alpha1.IntegrationPlatformClusterOpenShift,
+                               Build: v1alpha1.IntegrationPlatformBuildSpec{
+                                       PublishStrategy: 
v1alpha1.IntegrationPlatformBuildPublishStrategyS2I,
+                                       Registry:        "registry",
+                               },
+                       },
+               },
+               EnvVars:        make([]corev1.EnvVar, 0),
+               ExecutedTraits: make([]Trait, 0),
+               Resources:      kubernetes.NewCollection(),
+       }
+
+       err = traitCatalog.apply(&environment)
+
+       assert.Nil(t, err)
+       assert.NotEmpty(t, environment.ExecutedTraits)
+       assert.NotNil(t, environment.GetTrait(ID("knative")))
+       assert.NotNil(t, envvar.Get(environment.EnvVars, 
"CAMEL_KNATIVE_CONFIGURATION"))
+       assert.Equal(t, 4, environment.Resources.Size())
+
+       s := environment.Resources.GetKnativeService(func(service 
*serving.Service) bool {
+               return service.Name == "test"
+       })
+
+       assert.NotNil(t, s)
+
+       spec := s.Spec.RunLatest.Configuration.RevisionTemplate.Spec
+
+       assert.Len(t, spec.Container.VolumeMounts, 5)
+       assert.Len(t, spec.Volumes, 5)
+
+       assert.Condition(t, func() bool {
+               for _, v := range spec.Container.VolumeMounts {
+                       if v.Name == "integration-properties" {
+                               return true
+                       }
+               }
+               return false
+       })
+       assert.Condition(t, func() bool {
+               for _, v := range spec.Container.VolumeMounts {
+                       if v.Name == "my-cm" {
+                               return true
+                       }
+               }
+               return false
+       })
+       assert.Condition(t, func() bool {
+               for _, v := range spec.Volumes {
+                       if v.Name == "my-secret" {
+                               return true
+                       }
+               }
+               return false
+       })
+
+       names := make([]string, 0)
+       environment.Resources.VisitConfigMap(func(cm *corev1.ConfigMap) {
+               names = append(names, cm.Name)
+       })
+
+       assert.Contains(t, names, "test-properties")
+       assert.Contains(t, names, "test-source-000")
+       assert.Contains(t, names, "test-resource-000")
+
+       environment.Resources.VisitConfigMap(func(cm *corev1.ConfigMap) {
+               if cm.Name == "test-properties" {
+                       _, ok := cm.Data["application.properties"]
+                       assert.True(t, ok)
+               }
+       })
+
+       test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_ROUTES", 
"file:/etc/camel/sources/i-source-000/routes.js?language=js&compression=true")
+       test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_CONF", 
"/etc/camel/conf/application.properties")
+       test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_CONF_D", 
"/etc/camel/conf.d")
+}
+
+func TestKnativeWithVolumeBindingAndContainerImage(t *testing.T) {
+       catalog, err := test.DefaultCatalog()
+       assert.Nil(t, err)
+
+       traitCatalog := NewCatalog(context.TODO(), nil)
+
+       environment := Environment{
+               CamelCatalog: catalog,
+               Catalog:      traitCatalog,
+               Integration: &v1alpha1.Integration{
+                       ObjectMeta: metav1.ObjectMeta{
+                               Name:      "test",
+                               Namespace: "ns",
+                       },
+                       Status: v1alpha1.IntegrationStatus{
+                               Phase: v1alpha1.IntegrationPhaseDeploying,
+                       },
+                       Spec: v1alpha1.IntegrationSpec{
+                               Profile: v1alpha1.TraitProfileKnative,
+                               Sources: []v1alpha1.SourceSpec{
+                                       {
+                                               DataSpec: v1alpha1.DataSpec{
+                                                       Name:        
"routes.js",
+                                                       Content:     
`from("undertow:test").log("hello")`,
+                                                       Compression: true,
+                                               },
+                                               Language: 
v1alpha1.LanguageJavaScript,
+                                       },
+                               },
+                               Resources: []v1alpha1.ResourceSpec{
+                                       {
+                                               DataSpec: v1alpha1.DataSpec{
+                                                       Name:        
"my-resource.txt",
+                                                       Content:     "",
+                                                       Compression: false,
+                                               },
+                                               Type: v1alpha1.ResourceTypeData,
+                                       },
+                               },
+                               Configuration: []v1alpha1.ConfigurationSpec{
+                                       {Type: "configmap", Value: "my-cm"},
+                                       {Type: "secret", Value: "my-secret"},
+                               },
+                               Traits: 
map[string]v1alpha1.IntegrationTraitSpec{
+                                       "deployer": {
+                                               Configuration: 
map[string]string{
+                                                       "container-image": 
"true",
+                                               },
+                                       },
+                                       "knative-service": {
+                                               Configuration: 
map[string]string{
+                                                       "configuration-type": 
"volume",
+                                               },
+                                       },
+                               },
+                       },
+               },
+               Platform: &v1alpha1.IntegrationPlatform{
+                       Spec: v1alpha1.IntegrationPlatformSpec{
+                               Cluster: 
v1alpha1.IntegrationPlatformClusterOpenShift,
+                               Build: v1alpha1.IntegrationPlatformBuildSpec{
+                                       PublishStrategy: 
v1alpha1.IntegrationPlatformBuildPublishStrategyS2I,
+                                       Registry:        "registry",
+                               },
+                       },
+               },
+               EnvVars:        make([]corev1.EnvVar, 0),
+               ExecutedTraits: make([]Trait, 0),
+               Resources:      kubernetes.NewCollection(),
+       }
+
+       err = traitCatalog.apply(&environment)
+
+       assert.Nil(t, err)
+       assert.NotEmpty(t, environment.ExecutedTraits)
+       assert.NotNil(t, environment.GetTrait(ID("knative")))
+       assert.NotNil(t, envvar.Get(environment.EnvVars, 
"CAMEL_KNATIVE_CONFIGURATION"))
+       assert.Equal(t, 2, environment.Resources.Size())
+
+       names := make([]string, 0)
+       environment.Resources.VisitConfigMap(func(cm *corev1.ConfigMap) {
+               names = append(names, cm.Name)
+       })
+
+       assert.Contains(t, names, "test-properties")
+
+       s := environment.Resources.GetKnativeService(func(service 
*serving.Service) bool {
+               return service.Name == "test"
+       })
+
+       assert.NotNil(t, s)
+
+       spec := s.Spec.RunLatest.Configuration.RevisionTemplate.Spec
+
+       assert.Len(t, spec.Container.VolumeMounts, 3)
+       assert.Len(t, spec.Volumes, 3)
+
+       test.HasVolume(t, spec.Volumes, "my-cm")
+       test.HasVolume(t, spec.Volumes, "my-secret")
+       test.HasVolume(t, spec.Volumes, "integration-properties")
+
+       test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_ROUTES", 
"file:/deployments/sources/routes.js?language=js&compression=true")
+       test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_CONF", 
"/etc/camel/conf/application.properties")
+       test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_CONF_D", 
"/etc/camel/conf.d")
+}
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index b0903fe..8e91366 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -20,6 +20,7 @@ package trait
 import (
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        "github.com/apache/camel-k/pkg/metadata"
+       "github.com/apache/camel-k/pkg/util/kubernetes"
        "github.com/pkg/errors"
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -52,7 +53,7 @@ func (t *serviceTrait) Configure(e *Environment) (bool, 
error) {
        }
 
        if t.Auto == nil || *t.Auto {
-               sources, err := e.ResolveSources(t.ctx, t.client)
+               sources, err := kubernetes.ResolveIntegrationSources(t.ctx, 
t.client, e.Integration, e.Resources)
                if err != nil {
                        return false, err
                }
diff --git a/pkg/trait/catalog.go b/pkg/trait/trait_catalog.go
similarity index 98%
rename from pkg/trait/catalog.go
rename to pkg/trait/trait_catalog.go
index 68336df..9c5c9c2 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/trait_catalog.go
@@ -34,6 +34,7 @@ type Catalog struct {
        tCamel            Trait
        tDebug            Trait
        tDependencies     Trait
+       tDeployer         Trait
        tDeployment       Trait
        tGarbageCollector Trait
        tKnativeService   Trait
@@ -62,6 +63,7 @@ func NewCatalog(ctx context.Context, c client.Client) 
*Catalog {
                tRestDsl:          newRestDslTrait(),
                tKnative:          newKnativeTrait(),
                tDependencies:     newDependenciesTrait(),
+               tDeployer:         newDeployerTrait(),
                tDeployment:       newDeploymentTrait(),
                tGarbageCollector: newGarbageCollectorTrait(),
                tKnativeService:   newKnativeServiceTrait(),
@@ -97,6 +99,7 @@ func (c *Catalog) allTraits() []Trait {
                c.tRestDsl,
                c.tKnative,
                c.tDependencies,
+               c.tDeployer,
                c.tDeployment,
                c.tGarbageCollector,
                c.tKnativeService,
@@ -133,6 +136,7 @@ func (c *Catalog) traitsFor(environment *Environment) 
[]Trait {
                        c.tSpringBoot,
                        c.tJolokia,
                        c.tPrometheus,
+                       c.tDeployer,
                        c.tDeployment,
                        c.tService,
                        c.tRoute,
@@ -152,6 +156,7 @@ func (c *Catalog) traitsFor(environment *Environment) 
[]Trait {
                        c.tSpringBoot,
                        c.tJolokia,
                        c.tPrometheus,
+                       c.tDeployer,
                        c.tDeployment,
                        c.tService,
                        c.tIngress,
@@ -170,6 +175,7 @@ func (c *Catalog) traitsFor(environment *Environment) 
[]Trait {
                        c.tEnvironment,
                        c.tClasspath,
                        c.tSpringBoot,
+                       c.tDeployer,
                        c.tDeployment,
                        c.tKnativeService,
                        c.tIstio,
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index 0145e23..5d1d3da 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -175,6 +175,7 @@ func createTestEnv(t *testing.T, cluster 
v1alpha1.IntegrationPlatformCluster, sc
 
        return &Environment{
                CamelCatalog: catalog,
+               Catalog:      NewCatalog(context.TODO(), nil),
                Integration: &v1alpha1.Integration{
                        ObjectMeta: metav1.ObjectMeta{
                                Name:      TestDeployment,
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
new file mode 100644
index 0000000..364fd3b
--- /dev/null
+++ b/pkg/trait/trait_types.go
@@ -0,0 +1,526 @@
+/*
+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 (
+       "context"
+       "fmt"
+       "path"
+       "strconv"
+       "strings"
+
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+
+       corev1 "k8s.io/api/core/v1"
+
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+       "github.com/apache/camel-k/pkg/builder"
+       "github.com/apache/camel-k/pkg/client"
+       "github.com/apache/camel-k/pkg/metadata"
+       "github.com/apache/camel-k/pkg/platform"
+       "github.com/apache/camel-k/pkg/util/camel"
+       "github.com/apache/camel-k/pkg/util/kubernetes"
+       "github.com/apache/camel-k/pkg/util/log"
+)
+
+// Identifiable represent an identifiable type
+type Identifiable interface {
+       ID() ID
+}
+
+// ID uniquely identifies a trait
+type ID string
+
+// Trait is the interface of all traits
+type Trait interface {
+       Identifiable
+       client.Injectable
+
+       // InjectContext to inject a context
+       InjectContext(context.Context)
+
+       // Configure the trait
+       Configure(environment *Environment) (bool, error)
+
+       // Apply executes a customization of the Environment
+       Apply(environment *Environment) error
+}
+
+/* Base trait */
+
+func newBaseTrait(id string) BaseTrait {
+       return BaseTrait{
+               id: ID(id),
+               L:  log.Log.WithName("traits").WithValues("trait", id),
+       }
+}
+
+// BaseTrait is the root trait with noop implementations for hooks
+type BaseTrait struct {
+       id      ID
+       Enabled *bool `property:"enabled"`
+       client  client.Client
+       ctx     context.Context
+       L       log.Logger
+}
+
+// ID returns the identifier of the trait
+func (trait *BaseTrait) ID() ID {
+       return trait.id
+}
+
+// InjectClient implements client.ClientInject and allows to inject a client 
into the trait
+func (trait *BaseTrait) InjectClient(c client.Client) {
+       trait.client = c
+}
+
+// InjectContext allows to inject a context into the trait
+func (trait *BaseTrait) InjectContext(ctx context.Context) {
+       trait.ctx = ctx
+}
+
+/* Environment */
+
+// A Environment provides the context where the trait is executed
+type Environment struct {
+       CamelCatalog       *camel.RuntimeCatalog
+       RuntimeVersion     string
+       Catalog            *Catalog
+       C                  context.Context
+       Client             client.Client
+       Platform           *v1alpha1.IntegrationPlatform
+       IntegrationContext *v1alpha1.IntegrationContext
+       Integration        *v1alpha1.Integration
+       Resources          *kubernetes.Collection
+       PostActions        []func(*Environment) error
+       PostProcessors     []func(*Environment) error
+       Steps              []builder.Step
+       BuildDir           string
+       ExecutedTraits     []Trait
+       EnvVars            []corev1.EnvVar
+}
+
+// ControllerStrategy is used to determine the kind of controller that needs 
to be created for the integration
+type ControllerStrategy string
+
+// List of controller strategies
+const (
+       ControllerStrategyDeployment     = "deployment"
+       ControllerStrategyKnativeService = "knative-service"
+)
+
+// GetTrait --
+func (e *Environment) GetTrait(id ID) Trait {
+       for _, t := range e.ExecutedTraits {
+               if t.ID() == id {
+                       return t
+               }
+       }
+
+       return nil
+}
+
+// IntegrationInPhase --
+func (e *Environment) IntegrationInPhase(phases ...v1alpha1.IntegrationPhase) 
bool {
+       if e.Integration == nil {
+               return false
+       }
+
+       for _, phase := range phases {
+               if e.Integration.Status.Phase == phase {
+                       return true
+               }
+       }
+
+       return false
+}
+
+// IntegrationContextInPhase --
+func (e *Environment) IntegrationContextInPhase(phases 
...v1alpha1.IntegrationContextPhase) bool {
+       if e.IntegrationContext == nil {
+               return false
+       }
+
+       for _, phase := range phases {
+               if e.IntegrationContext.Status.Phase == phase {
+                       return true
+               }
+       }
+
+       return false
+}
+
+// InPhase --
+func (e *Environment) InPhase(c v1alpha1.IntegrationContextPhase, i 
v1alpha1.IntegrationPhase) bool {
+       return e.IntegrationContextInPhase(c) && e.IntegrationInPhase(i)
+}
+
+// DetermineProfile determines the TraitProfile of the environment.
+// First looking at the Integration.Spec for a Profile,
+// next looking at the IntegrationContext.Spec
+// and lastly the Platform Profile
+func (e *Environment) DetermineProfile() v1alpha1.TraitProfile {
+       if e.Integration != nil && e.Integration.Spec.Profile != "" {
+               return e.Integration.Spec.Profile
+       }
+
+       if e.IntegrationContext != nil && e.IntegrationContext.Spec.Profile != 
"" {
+               return e.IntegrationContext.Spec.Profile
+       }
+
+       return platform.GetProfile(e.Platform)
+}
+
+// DetermineControllerStrategy determines the type of controller that should 
be used for the integration
+func (e *Environment) DetermineControllerStrategy(ctx context.Context, c 
client.Client) (ControllerStrategy, error) {
+       if e.DetermineProfile() != v1alpha1.TraitProfileKnative {
+               return ControllerStrategyDeployment, nil
+       }
+
+       var sources []v1alpha1.SourceSpec
+       var err error
+       if sources, err = kubernetes.ResolveIntegrationSources(ctx, c, 
e.Integration, e.Resources); err != nil {
+               return "", err
+       }
+
+       // In Knative profile: use knative service only if needed
+       meta := metadata.ExtractAll(e.CamelCatalog, sources)
+       if !meta.RequiresHTTPService {
+               return ControllerStrategyDeployment, nil
+       }
+
+       return ControllerStrategyKnativeService, nil
+}
+
+// DetermineCamelVersion --
+func (e *Environment) DetermineCamelVersion() string {
+       var version string
+
+       if e.Integration != nil {
+               version = e.Integration.Status.CamelVersion
+       }
+       if e.IntegrationContext != nil && version == "" {
+               version = e.IntegrationContext.Status.CamelVersion
+       }
+       if version == "" {
+               version = e.Platform.Spec.Build.CamelVersion
+       }
+
+       return version
+}
+
+// DetermineRuntimeVersion --
+func (e *Environment) DetermineRuntimeVersion() string {
+       var version string
+
+       if e.Integration != nil {
+               version = e.Integration.Status.RuntimeVersion
+       }
+       if e.IntegrationContext != nil && version == "" {
+               version = e.IntegrationContext.Status.RuntimeVersion
+       }
+       if version == "" {
+               version = e.Platform.Spec.Build.RuntimeVersion
+       }
+
+       return version
+}
+
+// ComputeConfigMaps --
+func (e *Environment) ComputeConfigMaps(container bool) []runtime.Object {
+       sources := e.Integration.Sources()
+       maps := make([]runtime.Object, 0, len(sources)+1)
+
+       // combine properties of integration with context, integration
+       // properties have the priority
+       properties := ""
+
+       VisitKeyValConfigurations("property", e.IntegrationContext, 
e.Integration, func(key string, val string) {
+               properties += fmt.Sprintf("%s=%s\n", key, val)
+       })
+
+       maps = append(
+               maps,
+               &corev1.ConfigMap{
+                       TypeMeta: metav1.TypeMeta{
+                               Kind:       "ConfigMap",
+                               APIVersion: "v1",
+                       },
+                       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{
+                               "application.properties": properties,
+                       },
+               },
+       )
+
+       if !container {
+               for i, s := range sources {
+                       if s.ContentRef != "" {
+                               continue
+                       }
+
+                       cm := 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.InferLanguage()),
+                                               "camel.apache.org/source.name": 
       s.Name,
+                                               
"camel.apache.org/source.compression": strconv.FormatBool(s.Compression),
+                                       },
+                               },
+                               Data: map[string]string{
+                                       "content": s.Content,
+                               },
+                       }
+
+                       maps = append(maps, &cm)
+               }
+
+               for i, s := range e.Integration.Spec.Resources {
+                       if s.Type != v1alpha1.ResourceTypeData {
+                               continue
+                       }
+
+                       cm := corev1.ConfigMap{
+                               TypeMeta: metav1.TypeMeta{
+                                       Kind:       "ConfigMap",
+                                       APIVersion: "v1",
+                               },
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:      
fmt.Sprintf("%s-resource-%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/resource.name":        s.Name,
+                                               
"camel.apache.org/resource.compression": strconv.FormatBool(s.Compression),
+                                       },
+                               },
+                               Data: map[string]string{
+                                       "content": s.Content,
+                               },
+                       }
+
+                       maps = append(maps, &cm)
+               }
+       }
+
+       return maps
+}
+
+// ComputeSourcesURI --
+func (e *Environment) ComputeSourcesURI(container bool) []string {
+       sources := e.Integration.Sources()
+       paths := make([]string, 0, len(sources))
+
+       for i, s := range sources {
+               root := "/etc/camel/sources"
+
+               if container {
+                       // assume sources are copied over the standard 
deployments folder
+                       root = "/deployments/sources"
+               } else {
+                       root = path.Join(root, fmt.Sprintf("i-source-%03d", i))
+               }
+
+               srcName := strings.TrimPrefix(s.Name, "/")
+               src := path.Join(root, srcName)
+               src = "file:" + src
+
+               params := make([]string, 0)
+               if s.InferLanguage() != "" {
+                       params = append(params, 
"language="+string(s.InferLanguage()))
+               }
+               if s.Compression {
+                       params = append(params, "compression=true")
+               }
+
+               if len(params) > 0 {
+                       src = fmt.Sprintf("%s?%s", src, strings.Join(params, 
"&"))
+               }
+
+               paths = append(paths, src)
+       }
+
+       return paths
+}
+
+// ConfigureVolumesAndMounts --
+func (e *Environment) ConfigureVolumesAndMounts(container bool, vols 
*[]corev1.Volume, mnts *[]corev1.VolumeMount) {
+
+       if !container {
+
+               //
+               // Volumes :: Sources
+               //
+
+               for i, s := range e.Integration.Sources() {
+                       cmName := fmt.Sprintf("%s-source-%03d", 
e.Integration.Name, i)
+                       refName := fmt.Sprintf("i-source-%03d", i)
+                       resName := strings.TrimPrefix(s.Name, "/")
+
+                       if s.ContentRef != "" {
+                               cmName = s.ContentRef
+                       }
+
+                       *vols = append(*vols, corev1.Volume{
+                               Name: refName,
+                               VolumeSource: corev1.VolumeSource{
+                                       ConfigMap: 
&corev1.ConfigMapVolumeSource{
+                                               LocalObjectReference: 
corev1.LocalObjectReference{
+                                                       Name: cmName,
+                                               },
+                                               Items: []corev1.KeyToPath{
+                                                       {
+                                                               Key:  "content",
+                                                               Path: resName,
+                                                       },
+                                               },
+                                       },
+                               },
+                       })
+
+                       *mnts = append(*mnts, corev1.VolumeMount{
+                               Name:      refName,
+                               MountPath: path.Join("/etc/camel/sources", 
refName),
+                       })
+               }
+
+               for i, r := range e.Integration.Spec.Resources {
+                       if r.Type != v1alpha1.ResourceTypeData {
+                               continue
+                       }
+
+                       cmName := fmt.Sprintf("%s-resource-%03d", 
e.Integration.Name, i)
+                       refName := fmt.Sprintf("i-resource-%03d", i)
+                       resName := strings.TrimPrefix(r.Name, "/")
+
+                       *vols = append(*vols, corev1.Volume{
+                               Name: refName,
+                               VolumeSource: corev1.VolumeSource{
+                                       ConfigMap: 
&corev1.ConfigMapVolumeSource{
+                                               LocalObjectReference: 
corev1.LocalObjectReference{
+                                                       Name: cmName,
+                                               },
+                                               Items: []corev1.KeyToPath{
+                                                       {
+                                                               Key:  "content",
+                                                               Path: resName,
+                                                       },
+                                               },
+                                       },
+                               },
+                       })
+
+                       *mnts = append(*mnts, corev1.VolumeMount{
+                               Name:      refName,
+                               MountPath: path.Join("/etc/camel/resources", 
refName),
+                       })
+               }
+       }
+
+       //
+       // Volumes :: Properties
+       //
+
+       *vols = append(*vols, corev1.Volume{
+               Name: "integration-properties",
+               VolumeSource: corev1.VolumeSource{
+                       ConfigMap: &corev1.ConfigMapVolumeSource{
+                               LocalObjectReference: 
corev1.LocalObjectReference{
+                                       Name: e.Integration.Name + 
"-properties",
+                               },
+                               Items: []corev1.KeyToPath{
+                                       {
+                                               Key:  "application.properties",
+                                               Path: "application.properties",
+                                       },
+                               },
+                       },
+               },
+       })
+
+       *mnts = append(*mnts, corev1.VolumeMount{
+               Name:      "integration-properties",
+               MountPath: "/etc/camel/conf",
+       })
+
+       //
+       // Volumes :: Additional ConfigMaps
+       //
+
+       VisitConfigurations("configmap", e.IntegrationContext, e.Integration, 
func(cmName string) {
+               refName := kubernetes.SanitizeLabel(cmName)
+               fileName := "integration-cm-" + strings.ToLower(cmName)
+
+               *vols = append(*vols, corev1.Volume{
+                       Name: refName,
+                       VolumeSource: corev1.VolumeSource{
+                               ConfigMap: &corev1.ConfigMapVolumeSource{
+                                       LocalObjectReference: 
corev1.LocalObjectReference{
+                                               Name: cmName,
+                                       },
+                               },
+                       },
+               })
+
+               *mnts = append(*mnts, corev1.VolumeMount{
+                       Name:      refName,
+                       MountPath: path.Join("/etc/camel/conf.d", fileName),
+               })
+       })
+
+       //
+       // Volumes :: Additional Secrets
+       //
+
+       VisitConfigurations("secret", e.IntegrationContext, e.Integration, 
func(secretName string) {
+               refName := kubernetes.SanitizeLabel(secretName)
+               fileName := "integration-secret-" + strings.ToLower(secretName)
+
+               *vols = append(*vols, corev1.Volume{
+                       Name: refName,
+                       VolumeSource: corev1.VolumeSource{
+                               Secret: &corev1.SecretVolumeSource{
+                                       SecretName: secretName,
+                               },
+                       },
+               })
+
+               *mnts = append(*mnts, corev1.VolumeMount{
+                       Name:      refName,
+                       MountPath: path.Join("/etc/camel/conf.d", fileName),
+               })
+       })
+}
diff --git a/pkg/trait/types.go b/pkg/trait/types.go
deleted file mode 100644
index d91e72c..0000000
--- a/pkg/trait/types.go
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements.  See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You under the Apache License, Version 2.0
-(the "License"); you may not use this file except in compliance with
-the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package trait
-
-import (
-       "context"
-
-       corev1 "k8s.io/api/core/v1"
-
-       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-       "github.com/apache/camel-k/pkg/builder"
-       "github.com/apache/camel-k/pkg/client"
-       "github.com/apache/camel-k/pkg/metadata"
-       "github.com/apache/camel-k/pkg/platform"
-       "github.com/apache/camel-k/pkg/util/camel"
-       "github.com/apache/camel-k/pkg/util/kubernetes"
-       "github.com/apache/camel-k/pkg/util/log"
-       "github.com/apache/camel-k/pkg/util/source"
-)
-
-// Identifiable represent an identifiable type
-type Identifiable interface {
-       ID() ID
-}
-
-// ID uniquely identifies a trait
-type ID string
-
-// Trait is the interface of all traits
-type Trait interface {
-       Identifiable
-       client.Injectable
-
-       // InjectContext to inject a context
-       InjectContext(context.Context)
-
-       // Configure the trait
-       Configure(environment *Environment) (bool, error)
-
-       // Apply executes a customization of the Environment
-       Apply(environment *Environment) error
-}
-
-/* Base trait */
-
-func newBaseTrait(id string) BaseTrait {
-       return BaseTrait{
-               id: ID(id),
-               L:  log.Log.WithName("traits").WithValues("trait", id),
-       }
-}
-
-// BaseTrait is the root trait with noop implementations for hooks
-type BaseTrait struct {
-       id      ID
-       Enabled *bool `property:"enabled"`
-       client  client.Client
-       ctx     context.Context
-       L       log.Logger
-}
-
-// ID returns the identifier of the trait
-func (trait *BaseTrait) ID() ID {
-       return trait.id
-}
-
-// InjectClient implements client.ClientInject and allows to inject a client 
into the trait
-func (trait *BaseTrait) InjectClient(c client.Client) {
-       trait.client = c
-}
-
-// InjectContext allows to inject a context into the trait
-func (trait *BaseTrait) InjectContext(ctx context.Context) {
-       trait.ctx = ctx
-}
-
-/* Environment */
-
-// A Environment provides the context where the trait is executed
-type Environment struct {
-       CamelCatalog       *camel.RuntimeCatalog
-       RuntimeVersion     string
-       Catalog            *Catalog
-       C                  context.Context
-       Client             client.Client
-       Platform           *v1alpha1.IntegrationPlatform
-       IntegrationContext *v1alpha1.IntegrationContext
-       Integration        *v1alpha1.Integration
-       Resources          *kubernetes.Collection
-       PostActions        []func(*Environment) error
-       PostProcessors     []func(*Environment) error
-       Steps              []builder.Step
-       BuildDir           string
-       ExecutedTraits     []Trait
-       EnvVars            []corev1.EnvVar
-}
-
-// ControllerStrategy is used to determine the kind of controller that needs 
to be created for the integration
-type ControllerStrategy string
-
-// List of controller strategies
-const (
-       ControllerStrategyDeployment     = "deployment"
-       ControllerStrategyKnativeService = "knative-service"
-)
-
-// GetTrait --
-func (e *Environment) GetTrait(id ID) Trait {
-       for _, t := range e.ExecutedTraits {
-               if t.ID() == id {
-                       return t
-               }
-       }
-
-       return nil
-}
-
-// IntegrationInPhase --
-func (e *Environment) IntegrationInPhase(phase v1alpha1.IntegrationPhase) bool 
{
-       return e.Integration != nil && e.Integration.Status.Phase == phase
-}
-
-// IntegrationContextInPhase --
-func (e *Environment) IntegrationContextInPhase(phase 
v1alpha1.IntegrationContextPhase) bool {
-       return e.IntegrationContext != nil && e.IntegrationContext.Status.Phase 
== phase
-}
-
-// InPhase --
-func (e *Environment) InPhase(c v1alpha1.IntegrationContextPhase, i 
v1alpha1.IntegrationPhase) bool {
-       return e.IntegrationContextInPhase(c) && e.IntegrationInPhase(i)
-}
-
-// DetermineProfile determines the TraitProfile of the environment.
-// First looking at the Integration.Spec for a Profile,
-// next looking at the IntegrationContext.Spec
-// and lastly the Platform Profile
-func (e *Environment) DetermineProfile() v1alpha1.TraitProfile {
-       if e.Integration != nil && e.Integration.Spec.Profile != "" {
-               return e.Integration.Spec.Profile
-       }
-
-       if e.IntegrationContext != nil && e.IntegrationContext.Spec.Profile != 
"" {
-               return e.IntegrationContext.Spec.Profile
-       }
-
-       return platform.GetProfile(e.Platform)
-}
-
-// ResolveSources --
-func (e *Environment) ResolveSources(context context.Context, client 
client.Client) ([]v1alpha1.SourceSpec, error) {
-       return source.Resolve(e.Integration.Sources(), func(name string) 
(*corev1.ConfigMap, error) {
-               // the config map could be part of the resources created
-               // by traits
-               cm := e.Resources.GetConfigMap(func(m *corev1.ConfigMap) bool {
-                       return m.Name == name
-               })
-
-               if cm != nil {
-                       return cm, nil
-               }
-
-               return kubernetes.GetConfigMap(context, client, name, 
e.Integration.Namespace)
-       })
-}
-
-// DetermineControllerStrategy determines the type of controller that should 
be used for the integration
-func (e *Environment) DetermineControllerStrategy(ctx context.Context, c 
client.Client) (ControllerStrategy, error) {
-       if e.DetermineProfile() != v1alpha1.TraitProfileKnative {
-               return ControllerStrategyDeployment, nil
-       }
-
-       var sources []v1alpha1.SourceSpec
-       var err error
-       if sources, err = e.ResolveSources(ctx, c); err != nil {
-               return "", err
-       }
-
-       // In Knative profile: use knative service only if needed
-       meta := metadata.ExtractAll(e.CamelCatalog, sources)
-       if !meta.RequiresHTTPService {
-               return ControllerStrategyDeployment, nil
-       }
-
-       return ControllerStrategyKnativeService, nil
-}
-
-// DetermineCamelVersion --
-func (e *Environment) DetermineCamelVersion() string {
-       var version string
-
-       if e.Integration != nil {
-               version = e.Integration.Status.CamelVersion
-       }
-       if e.IntegrationContext != nil && version == "" {
-               version = e.IntegrationContext.Status.CamelVersion
-       }
-       if version == "" {
-               version = e.Platform.Spec.Build.CamelVersion
-       }
-
-       return version
-}
-
-// DetermineRuntimeVersion --
-func (e *Environment) DetermineRuntimeVersion() string {
-       var version string
-
-       if e.Integration != nil {
-               version = e.Integration.Status.RuntimeVersion
-       }
-       if e.IntegrationContext != nil && version == "" {
-               version = e.IntegrationContext.Status.RuntimeVersion
-       }
-       if version == "" {
-               version = e.Platform.Spec.Build.RuntimeVersion
-       }
-
-       return version
-}
diff --git a/pkg/util/kubernetes/collection.go 
b/pkg/util/kubernetes/collection.go
index e023229..6eff785 100644
--- a/pkg/util/kubernetes/collection.go
+++ b/pkg/util/kubernetes/collection.go
@@ -101,6 +101,11 @@ func (c *Collection) GetDeployment(filter 
func(*appsv1.Deployment) bool) *appsv1
        return retValue
 }
 
+// HasDeployment returns true if a deployment matching the given condition is 
present
+func (c *Collection) HasDeployment(filter func(*appsv1.Deployment) bool) bool {
+       return c.GetDeployment(filter) != nil
+}
+
 // RemoveDeployment removes and returns a Deployment that matches the given 
function
 func (c *Collection) RemoveDeployment(filter func(*appsv1.Deployment) bool) 
*appsv1.Deployment {
        res := c.Remove(func(res runtime.Object) bool {
@@ -169,6 +174,17 @@ func (c *Collection) GetService(filter 
func(*corev1.Service) bool) *corev1.Servi
        return retValue
 }
 
+// GetKnativeService returns a knative Service that matches the given function
+func (c *Collection) GetKnativeService(filter func(*serving.Service) bool) 
*serving.Service {
+       var retValue *serving.Service
+       c.VisitKnativeService(func(re *serving.Service) {
+               if filter(re) {
+                       retValue = re
+               }
+       })
+       return retValue
+}
+
 // VisitRoute executes the visitor function on all Route resources
 func (c *Collection) VisitRoute(visitor func(*routev1.Route)) {
        c.Visit(func(res runtime.Object) {
diff --git a/pkg/util/kubernetes/resolver.go b/pkg/util/kubernetes/resolver.go
new file mode 100644
index 0000000..5330e58
--- /dev/null
+++ b/pkg/util/kubernetes/resolver.go
@@ -0,0 +1,120 @@
+/*
+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 kubernetes
+
+import (
+       "context"
+       "fmt"
+
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+       "github.com/apache/camel-k/pkg/client"
+       corev1 "k8s.io/api/core/v1"
+)
+
+// ResolveSources --
+func ResolveSources(elements []v1alpha1.SourceSpec, mapLookup func(string) 
(*corev1.ConfigMap, error)) ([]v1alpha1.SourceSpec, error) {
+       for i := 0; i < len(elements); i++ {
+               r := &elements[i]
+
+               if err := Resolve(&r.DataSpec, mapLookup); err != nil {
+                       return nil, err
+               }
+       }
+
+       return elements, nil
+}
+
+// ResolveResource --
+func ResolveResource(elements []v1alpha1.ResourceSpec, mapLookup func(string) 
(*corev1.ConfigMap, error)) ([]v1alpha1.ResourceSpec, error) {
+       for i := 0; i < len(elements); i++ {
+               r := &elements[i]
+
+               if err := Resolve(&r.DataSpec, mapLookup); err != nil {
+                       return nil, err
+               }
+       }
+
+       return elements, nil
+}
+
+// Resolve --
+func Resolve(data *v1alpha1.DataSpec, mapLookup func(string) 
(*corev1.ConfigMap, error)) error {
+       // if it is a reference, get the content from the
+       // referenced ConfigMap
+       if data.ContentRef != "" {
+               //look up the ConfigMap from the kubernetes cluster
+               cm, err := mapLookup(data.ContentRef)
+               if err != nil {
+                       return err
+               }
+
+               if cm == nil {
+                       return fmt.Errorf("unable to find a ConfigMap with 
name: %s ", data.ContentRef)
+               }
+
+               //
+               // Replace ref source content with real content
+               //
+               data.Content = cm.Data["content"]
+               data.ContentRef = ""
+       }
+
+       return nil
+}
+
+// ResolveIntegrationSources --
+func ResolveIntegrationSources(context context.Context, client client.Client, 
integration *v1alpha1.Integration, resources *Collection) 
([]v1alpha1.SourceSpec, error) {
+       if integration == nil {
+               return nil, nil
+       }
+
+       return ResolveSources(integration.Sources(), func(name string) 
(*corev1.ConfigMap, error) {
+               // the config map could be part of the resources created
+               // by traits
+               cm := resources.GetConfigMap(func(m *corev1.ConfigMap) bool {
+                       return m.Name == name
+               })
+
+               if cm != nil {
+                       return cm, nil
+               }
+
+               return GetConfigMap(context, client, name, 
integration.Namespace)
+       })
+}
+
+// ResolveIntegrationResources --
+func ResolveIntegrationResources(context context.Context, client 
client.Client, integration *v1alpha1.Integration, resources *Collection) 
([]v1alpha1.ResourceSpec, error) {
+       if integration == nil {
+               return nil, nil
+       }
+
+       return ResolveResource(integration.Spec.Resources, func(name string) 
(*corev1.ConfigMap, error) {
+               // the config map could be part of the resources created
+               // by traits
+               cm := resources.GetConfigMap(func(m *corev1.ConfigMap) bool {
+                       return m.Name == name
+               })
+
+               if cm != nil {
+                       return cm, nil
+               }
+
+               return GetConfigMap(context, client, name, 
integration.Namespace)
+       })
+}
diff --git a/pkg/util/kubernetes/util.go b/pkg/util/kubernetes/util.go
index 2114dba..1de3bd3 100644
--- a/pkg/util/kubernetes/util.go
+++ b/pkg/util/kubernetes/util.go
@@ -94,6 +94,31 @@ func GetConfigMap(context context.Context, client 
client.Client, name string, na
        return &answer, nil
 }
 
+// GetSecret --
+func GetSecret(context context.Context, client client.Client, name string, 
namespace string) (*corev1.Secret, error) {
+       key := k8sclient.ObjectKey{
+               Name:      name,
+               Namespace: namespace,
+       }
+
+       answer := corev1.Secret{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "Secret",
+                       APIVersion: "v1",
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      name,
+                       Namespace: namespace,
+               },
+       }
+
+       if err := client.Get(context, key, &answer); err != nil {
+               return nil, err
+       }
+
+       return &answer, nil
+}
+
 // GetIntegrationContext --
 func GetIntegrationContext(context context.Context, client client.Client, name 
string, namespace string) (*v1alpha1.IntegrationContext, error) {
        key := k8sclient.ObjectKey{
diff --git a/pkg/util/source/util.go b/pkg/util/source/util.go
index 93f805e..63c5f51 100644
--- a/pkg/util/source/util.go
+++ b/pkg/util/source/util.go
@@ -16,44 +16,3 @@ limitations under the License.
 */
 
 package source
-
-import (
-       "fmt"
-
-       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-
-       corev1 "k8s.io/api/core/v1"
-)
-
-// Resolve --
-func Resolve(sources []v1alpha1.SourceSpec, mapLookup func(string) 
(*corev1.ConfigMap, error)) ([]v1alpha1.SourceSpec, error) {
-       for i := 0; i < len(sources); i++ {
-               // copy the source to avoid modifications to the
-               // original source
-               s := sources[i].DeepCopy()
-
-               // if it is a reference, get the content from the
-               // referenced ConfigMap
-               if s.ContentRef != "" {
-                       //look up the ConfigMap from the kubernetes cluster
-                       cm, err := mapLookup(s.ContentRef)
-                       if err != nil {
-                               return []v1alpha1.SourceSpec{}, err
-                       }
-
-                       if cm == nil {
-                               return []v1alpha1.SourceSpec{}, 
fmt.Errorf("unable to find a ConfigMap with name: %s ", s.ContentRef)
-                       }
-
-                       //
-                       // Replace ref source content with real content
-                       //
-                       s.Content = cm.Data["content"]
-                       s.ContentRef = ""
-               }
-
-               sources[i] = *s
-       }
-
-       return sources, nil
-}
diff --git a/pkg/util/test/assertions.go b/pkg/util/test/assertions.go
new file mode 100644
index 0000000..52c8a9f
--- /dev/null
+++ b/pkg/util/test/assertions.go
@@ -0,0 +1,46 @@
+/*
+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 test
+
+import (
+       "testing"
+
+       "github.com/apache/camel-k/pkg/util/envvar"
+       "github.com/stretchr/testify/assert"
+
+       corev1 "k8s.io/api/core/v1"
+)
+
+// EnvVarHasValue --
+func EnvVarHasValue(t *testing.T, env []corev1.EnvVar, name string, val 
string) {
+       ev := envvar.Get(env, name)
+       assert.NotNil(t, ev)
+       assert.Equal(t, val, ev.Value)
+}
+
+// HasVolume --
+func HasVolume(t *testing.T, volumes []corev1.Volume, name string) {
+       assert.Condition(t, func() bool {
+               for _, v := range volumes {
+                       if v.Name == name {
+                               return true
+                       }
+               }
+               return false
+       })
+}
diff --git a/pkg/util/util.go b/pkg/util/util.go
index b7790b0..519b9f6 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -18,12 +18,15 @@ limitations under the License.
 package util
 
 import (
+       "encoding/base64"
        "os"
        "os/signal"
        "path"
        "regexp"
        "syscall"
 
+       "github.com/magiconair/properties"
+
        "github.com/scylladb/go-set/strset"
 
        corev1 "k8s.io/api/core/v1"
@@ -153,3 +156,45 @@ func FindAllDistinctStringSubmatch(data string, regexps 
...*regexp.Regexp) []str
        }
        return submatchs.List()
 }
+
+// ExtractApplicationProperties --
+func ExtractApplicationProperties(data map[string]string, consumer 
func(string, string)) error {
+       pstr, ok := data["application.properties"]
+       if !ok {
+               return nil
+       }
+
+       p, err := properties.LoadString(pstr)
+       if err != nil {
+               return err
+       }
+
+       for _, k := range p.Keys() {
+               consumer(k, p.MustGet(k))
+       }
+
+       return nil
+}
+
+// ExtractEncodedApplicationProperties --
+func ExtractEncodedApplicationProperties(data map[string][]byte, consumer 
func(string, string)) error {
+       encoded, ok := data["application.properties"]
+       if !ok {
+               return nil
+       }
+       decoded, err := base64.StdEncoding.DecodeString(string(encoded))
+       if err != nil {
+               return err
+       }
+
+       p, err := properties.Load(decoded, properties.UTF8)
+       if err != nil {
+               return err
+       }
+
+       for _, k := range p.Keys() {
+               consumer(k, p.MustGet(k))
+       }
+
+       return nil
+}
diff --git a/vendor/github.com/magiconair/properties/LICENSE 
b/vendor/github.com/magiconair/properties/LICENSE
new file mode 100644
index 0000000..b387087
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/LICENSE
@@ -0,0 +1,25 @@
+goproperties - properties file decoder for Go
+
+Copyright (c) 2013-2018 - Frank Schroeder
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/magiconair/properties/assert/assert.go 
b/vendor/github.com/magiconair/properties/assert/assert.go
new file mode 100644
index 0000000..d0f2704
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/assert/assert.go
@@ -0,0 +1,90 @@
+// Copyright 2018 Frank Schroeder. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package assert provides helper functions for testing.
+package assert
+
+import (
+       "fmt"
+       "path/filepath"
+       "reflect"
+       "regexp"
+       "runtime"
+       "strings"
+       "testing"
+)
+
+// skip defines the default call depth
+const skip = 2
+
+// Equal asserts that got and want are equal as defined by
+// reflect.DeepEqual. The test fails with msg if they are not equal.
+func Equal(t *testing.T, got, want interface{}, msg ...string) {
+       if x := equal(2, got, want, msg...); x != "" {
+               fmt.Println(x)
+               t.Fail()
+       }
+}
+
+func equal(skip int, got, want interface{}, msg ...string) string {
+       if !reflect.DeepEqual(got, want) {
+               return fail(skip, "got %v want %v %s", got, want, 
strings.Join(msg, " "))
+       }
+       return ""
+}
+
+// Panic asserts that function fn() panics.
+// It assumes that recover() either returns a string or
+// an error and fails if the message does not match
+// the regular expression in 'matches'.
+func Panic(t *testing.T, fn func(), matches string) {
+       if x := doesPanic(2, fn, matches); x != "" {
+               fmt.Println(x)
+               t.Fail()
+       }
+}
+
+func doesPanic(skip int, fn func(), expr string) (err string) {
+       defer func() {
+               r := recover()
+               if r == nil {
+                       err = fail(skip, "did not panic")
+                       return
+               }
+               var v string
+               switch r.(type) {
+               case error:
+                       v = r.(error).Error()
+               case string:
+                       v = r.(string)
+               }
+               err = matches(skip, v, expr)
+       }()
+       fn()
+       return ""
+}
+
+// Matches asserts that a value matches a given regular expression.
+func Matches(t *testing.T, value, expr string) {
+       if x := matches(2, value, expr); x != "" {
+               fmt.Println(x)
+               t.Fail()
+       }
+}
+
+func matches(skip int, value, expr string) string {
+       ok, err := regexp.MatchString(expr, value)
+       if err != nil {
+               return fail(skip, "invalid pattern %q. %s", expr, err)
+       }
+       if !ok {
+               return fail(skip, "got %s which does not match %s", value, expr)
+       }
+       return ""
+}
+
+func fail(skip int, format string, args ...interface{}) string {
+       _, file, line, _ := runtime.Caller(skip)
+       return fmt.Sprintf("\t%s:%d: %s\n", filepath.Base(file), line, 
fmt.Sprintf(format, args...))
+}
diff --git a/vendor/github.com/magiconair/properties/decode.go 
b/vendor/github.com/magiconair/properties/decode.go
new file mode 100644
index 0000000..3ebf804
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/decode.go
@@ -0,0 +1,289 @@
+// Copyright 2018 Frank Schroeder. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package properties
+
+import (
+       "fmt"
+       "reflect"
+       "strconv"
+       "strings"
+       "time"
+)
+
+// Decode assigns property values to exported fields of a struct.
+//
+// Decode traverses v recursively and returns an error if a value cannot be
+// converted to the field type or a required value is missing for a field.
+//
+// The following type dependent decodings are used:
+//
+// String, boolean, numeric fields have the value of the property key assigned.
+// The property key name is the name of the field. A different key and a 
default
+// value can be set in the field's tag. Fields without default value are
+// required. If the value cannot be converted to the field type an error is
+// returned.
+//
+// time.Duration fields have the result of time.ParseDuration() assigned.
+//
+// time.Time fields have the vaule of time.Parse() assigned. The default layout
+// is time.RFC3339 but can be set in the field's tag.
+//
+// Arrays and slices of string, boolean, numeric, time.Duration and time.Time
+// fields have the value interpreted as a comma separated list of values. The
+// individual values are trimmed of whitespace and empty values are ignored. A
+// default value can be provided as a semicolon separated list in the field's
+// tag.
+//
+// Struct fields are decoded recursively using the field name plus "." as
+// prefix. The prefix (without dot) can be overridden in the field's tag.
+// Default values are not supported in the field's tag. Specify them on the
+// fields of the inner struct instead.
+//
+// Map fields must have a key of type string and are decoded recursively by
+// using the field's name plus ".' as prefix and the next element of the key
+// name as map key. The prefix (without dot) can be overridden in the field's
+// tag. Default values are not supported.
+//
+// Examples:
+//
+//     // Field is ignored.
+//     Field int `properties:"-"`
+//
+//     // Field is assigned value of 'Field'.
+//     Field int
+//
+//     // Field is assigned value of 'myName'.
+//     Field int `properties:"myName"`
+//
+//     // Field is assigned value of key 'myName' and has a default
+//     // value 15 if the key does not exist.
+//     Field int `properties:"myName,default=15"`
+//
+//     // Field is assigned value of key 'Field' and has a default
+//     // value 15 if the key does not exist.
+//     Field int `properties:",default=15"`
+//
+//     // Field is assigned value of key 'date' and the date
+//     // is in format 2006-01-02
+//     Field time.Time `properties:"date,layout=2006-01-02"`
+//
+//     // Field is assigned the non-empty and whitespace trimmed
+//     // values of key 'Field' split by commas.
+//     Field []string
+//
+//     // Field is assigned the non-empty and whitespace trimmed
+//     // values of key 'Field' split by commas and has a default
+//     // value ["a", "b", "c"] if the key does not exist.
+//     Field []string `properties:",default=a;b;c"`
+//
+//     // Field is decoded recursively with "Field." as key prefix.
+//     Field SomeStruct
+//
+//     // Field is decoded recursively with "myName." as key prefix.
+//     Field SomeStruct `properties:"myName"`
+//
+//     // Field is decoded recursively with "Field." as key prefix
+//     // and the next dotted element of the key as map key.
+//     Field map[string]string
+//
+//     // Field is decoded recursively with "myName." as key prefix
+//     // and the next dotted element of the key as map key.
+//     Field map[string]string `properties:"myName"`
+func (p *Properties) Decode(x interface{}) error {
+       t, v := reflect.TypeOf(x), reflect.ValueOf(x)
+       if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct {
+               return fmt.Errorf("not a pointer to struct: %s", t)
+       }
+       if err := dec(p, "", nil, nil, v); err != nil {
+               return err
+       }
+       return nil
+}
+
+func dec(p *Properties, key string, def *string, opts map[string]string, v 
reflect.Value) error {
+       t := v.Type()
+
+       // value returns the property value for key or the default if provided.
+       value := func() (string, error) {
+               if val, ok := p.Get(key); ok {
+                       return val, nil
+               }
+               if def != nil {
+                       return *def, nil
+               }
+               return "", fmt.Errorf("missing required key %s", key)
+       }
+
+       // conv converts a string to a value of the given type.
+       conv := func(s string, t reflect.Type) (val reflect.Value, err error) {
+               var v interface{}
+
+               switch {
+               case isDuration(t):
+                       v, err = time.ParseDuration(s)
+
+               case isTime(t):
+                       layout := opts["layout"]
+                       if layout == "" {
+                               layout = time.RFC3339
+                       }
+                       v, err = time.Parse(layout, s)
+
+               case isBool(t):
+                       v, err = boolVal(s), nil
+
+               case isString(t):
+                       v, err = s, nil
+
+               case isFloat(t):
+                       v, err = strconv.ParseFloat(s, 64)
+
+               case isInt(t):
+                       v, err = strconv.ParseInt(s, 10, 64)
+
+               case isUint(t):
+                       v, err = strconv.ParseUint(s, 10, 64)
+
+               default:
+                       return reflect.Zero(t), fmt.Errorf("unsupported type 
%s", t)
+               }
+               if err != nil {
+                       return reflect.Zero(t), err
+               }
+               return reflect.ValueOf(v).Convert(t), nil
+       }
+
+       // keydef returns the property key and the default value based on the
+       // name of the struct field and the options in the tag.
+       keydef := func(f reflect.StructField) (string, *string, 
map[string]string) {
+               _key, _opts := parseTag(f.Tag.Get("properties"))
+
+               var _def *string
+               if d, ok := _opts["default"]; ok {
+                       _def = &d
+               }
+               if _key != "" {
+                       return _key, _def, _opts
+               }
+               return f.Name, _def, _opts
+       }
+
+       switch {
+       case isDuration(t) || isTime(t) || isBool(t) || isString(t) || 
isFloat(t) || isInt(t) || isUint(t):
+               s, err := value()
+               if err != nil {
+                       return err
+               }
+               val, err := conv(s, t)
+               if err != nil {
+                       return err
+               }
+               v.Set(val)
+
+       case isPtr(t):
+               return dec(p, key, def, opts, v.Elem())
+
+       case isStruct(t):
+               for i := 0; i < v.NumField(); i++ {
+                       fv := v.Field(i)
+                       fk, def, opts := keydef(t.Field(i))
+                       if !fv.CanSet() {
+                               return fmt.Errorf("cannot set %s", 
t.Field(i).Name)
+                       }
+                       if fk == "-" {
+                               continue
+                       }
+                       if key != "" {
+                               fk = key + "." + fk
+                       }
+                       if err := dec(p, fk, def, opts, fv); err != nil {
+                               return err
+                       }
+               }
+               return nil
+
+       case isArray(t):
+               val, err := value()
+               if err != nil {
+                       return err
+               }
+               vals := split(val, ";")
+               a := reflect.MakeSlice(t, 0, len(vals))
+               for _, s := range vals {
+                       val, err := conv(s, t.Elem())
+                       if err != nil {
+                               return err
+                       }
+                       a = reflect.Append(a, val)
+               }
+               v.Set(a)
+
+       case isMap(t):
+               valT := t.Elem()
+               m := reflect.MakeMap(t)
+               for postfix := range p.FilterStripPrefix(key + ".").m {
+                       pp := strings.SplitN(postfix, ".", 2)
+                       mk, mv := pp[0], reflect.New(valT)
+                       if err := dec(p, key+"."+mk, nil, nil, mv); err != nil {
+                               return err
+                       }
+                       m.SetMapIndex(reflect.ValueOf(mk), mv.Elem())
+               }
+               v.Set(m)
+
+       default:
+               return fmt.Errorf("unsupported type %s", t)
+       }
+       return nil
+}
+
+// split splits a string on sep, trims whitespace of elements
+// and omits empty elements
+func split(s string, sep string) []string {
+       var a []string
+       for _, v := range strings.Split(s, sep) {
+               if v = strings.TrimSpace(v); v != "" {
+                       a = append(a, v)
+               }
+       }
+       return a
+}
+
+// parseTag parses a "key,k=v,k=v,..."
+func parseTag(tag string) (key string, opts map[string]string) {
+       opts = map[string]string{}
+       for i, s := range strings.Split(tag, ",") {
+               if i == 0 {
+                       key = s
+                       continue
+               }
+
+               pp := strings.SplitN(s, "=", 2)
+               if len(pp) == 1 {
+                       opts[pp[0]] = ""
+               } else {
+                       opts[pp[0]] = pp[1]
+               }
+       }
+       return key, opts
+}
+
+func isArray(t reflect.Type) bool    { return t.Kind() == reflect.Array || 
t.Kind() == reflect.Slice }
+func isBool(t reflect.Type) bool     { return t.Kind() == reflect.Bool }
+func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) 
}
+func isMap(t reflect.Type) bool      { return t.Kind() == reflect.Map }
+func isPtr(t reflect.Type) bool      { return t.Kind() == reflect.Ptr }
+func isString(t reflect.Type) bool   { return t.Kind() == reflect.String }
+func isStruct(t reflect.Type) bool   { return t.Kind() == reflect.Struct }
+func isTime(t reflect.Type) bool     { return t == reflect.TypeOf(time.Time{}) 
}
+func isFloat(t reflect.Type) bool {
+       return t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64
+}
+func isInt(t reflect.Type) bool {
+       return t.Kind() == reflect.Int || t.Kind() == reflect.Int8 || t.Kind() 
== reflect.Int16 || t.Kind() == reflect.Int32 || t.Kind() == reflect.Int64
+}
+func isUint(t reflect.Type) bool {
+       return t.Kind() == reflect.Uint || t.Kind() == reflect.Uint8 || 
t.Kind() == reflect.Uint16 || t.Kind() == reflect.Uint32 || t.Kind() == 
reflect.Uint64
+}
diff --git a/vendor/github.com/magiconair/properties/doc.go 
b/vendor/github.com/magiconair/properties/doc.go
new file mode 100644
index 0000000..f8822da
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/doc.go
@@ -0,0 +1,156 @@
+// Copyright 2018 Frank Schroeder. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package properties provides functions for reading and writing
+// ISO-8859-1 and UTF-8 encoded .properties files and has
+// support for recursive property expansion.
+//
+// Java properties files are ISO-8859-1 encoded and use Unicode
+// literals for characters outside the ISO character set. Unicode
+// literals can be used in UTF-8 encoded properties files but
+// aren't necessary.
+//
+// To load a single properties file use MustLoadFile():
+//
+//   p := properties.MustLoadFile(filename, properties.UTF8)
+//
+// To load multiple properties files use MustLoadFiles()
+// which loads the files in the given order and merges the
+// result. Missing properties files can be ignored if the
+// 'ignoreMissing' flag is set to true.
+//
+// Filenames can contain environment variables which are expanded
+// before loading.
+//
+//   f1 := "/etc/myapp/myapp.conf"
+//   f2 := "/home/${USER}/myapp.conf"
+//   p := MustLoadFiles([]string{f1, f2}, properties.UTF8, true)
+//
+// All of the different key/value delimiters ' ', ':' and '=' are
+// supported as well as the comment characters '!' and '#' and
+// multi-line values.
+//
+//   ! this is a comment
+//   # and so is this
+//
+//   # the following expressions are equal
+//   key value
+//   key=value
+//   key:value
+//   key = value
+//   key : value
+//   key = val\
+//         ue
+//
+// Properties stores all comments preceding a key and provides
+// GetComments() and SetComments() methods to retrieve and
+// update them. The convenience functions GetComment() and
+// SetComment() allow access to the last comment. The
+// WriteComment() method writes properties files including
+// the comments and with the keys in the original order.
+// This can be used for sanitizing properties files.
+//
+// Property expansion is recursive and circular references
+// and malformed expressions are not allowed and cause an
+// error. Expansion of environment variables is supported.
+//
+//   # standard property
+//   key = value
+//
+//   # property expansion: key2 = value
+//   key2 = ${key}
+//
+//   # recursive expansion: key3 = value
+//   key3 = ${key2}
+//
+//   # circular reference (error)
+//   key = ${key}
+//
+//   # malformed expression (error)
+//   key = ${ke
+//
+//   # refers to the users' home dir
+//   home = ${HOME}
+//
+//   # local key takes precedence over env var: u = foo
+//   USER = foo
+//   u = ${USER}
+//
+// The default property expansion format is ${key} but can be
+// changed by setting different pre- and postfix values on the
+// Properties object.
+//
+//   p := properties.NewProperties()
+//   p.Prefix = "#["
+//   p.Postfix = "]#"
+//
+// Properties provides convenience functions for getting typed
+// values with default values if the key does not exist or the
+// type conversion failed.
+//
+//   # Returns true if the value is either "1", "on", "yes" or "true"
+//   # Returns false for every other value and the default value if
+//   # the key does not exist.
+//   v = p.GetBool("key", false)
+//
+//   # Returns the value if the key exists and the format conversion
+//   # was successful. Otherwise, the default value is returned.
+//   v = p.GetInt64("key", 999)
+//   v = p.GetUint64("key", 999)
+//   v = p.GetFloat64("key", 123.0)
+//   v = p.GetString("key", "def")
+//   v = p.GetDuration("key", 999)
+//
+// As an alternative properties may be applied with the standard
+// library's flag implementation at any time.
+//
+//   # Standard configuration
+//   v = flag.Int("key", 999, "help message")
+//   flag.Parse()
+//
+//   # Merge p into the flag set
+//   p.MustFlag(flag.CommandLine)
+//
+// Properties provides several MustXXX() convenience functions
+// which will terminate the app if an error occurs. The behavior
+// of the failure is configurable and the default is to call
+// log.Fatal(err). To have the MustXXX() functions panic instead
+// of logging the error set a different ErrorHandler before
+// you use the Properties package.
+//
+//   properties.ErrorHandler = properties.PanicHandler
+//
+//   # Will panic instead of logging an error
+//   p := properties.MustLoadFile("config.properties")
+//
+// You can also provide your own ErrorHandler function. The only requirement
+// is that the error handler function must exit after handling the error.
+//
+//   properties.ErrorHandler = func(err error) {
+//          fmt.Println(err)
+//       os.Exit(1)
+//   }
+//
+//   # Will write to stdout and then exit
+//   p := properties.MustLoadFile("config.properties")
+//
+// Properties can also be loaded into a struct via the `Decode`
+// method, e.g.
+//
+//   type S struct {
+//       A string        `properties:"a,default=foo"`
+//       D time.Duration `properties:"timeout,default=5s"`
+//       E time.Time     
`properties:"expires,layout=2006-01-02,default=2015-01-01"`
+//   }
+//
+// See `Decode()` method for the full documentation.
+//
+// The following documents provide a description of the properties
+// file format.
+//
+// http://en.wikipedia.org/wiki/.properties
+//
+// 
http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29
+//
+package properties
diff --git a/vendor/github.com/magiconair/properties/integrate.go 
b/vendor/github.com/magiconair/properties/integrate.go
new file mode 100644
index 0000000..74d38dc
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/integrate.go
@@ -0,0 +1,34 @@
+// Copyright 2018 Frank Schroeder. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package properties
+
+import "flag"
+
+// MustFlag sets flags that are skipped by dst.Parse when p contains
+// the respective key for flag.Flag.Name.
+//
+// It's use is recommended with command line arguments as in:
+//     flag.Parse()
+//     p.MustFlag(flag.CommandLine)
+func (p *Properties) MustFlag(dst *flag.FlagSet) {
+       m := make(map[string]*flag.Flag)
+       dst.VisitAll(func(f *flag.Flag) {
+               m[f.Name] = f
+       })
+       dst.Visit(func(f *flag.Flag) {
+               delete(m, f.Name) // overridden
+       })
+
+       for name, f := range m {
+               v, ok := p.Get(name)
+               if !ok {
+                       continue
+               }
+
+               if err := f.Value.Set(v); err != nil {
+                       ErrorHandler(err)
+               }
+       }
+}
diff --git a/vendor/github.com/magiconair/properties/lex.go 
b/vendor/github.com/magiconair/properties/lex.go
new file mode 100644
index 0000000..367166d
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/lex.go
@@ -0,0 +1,407 @@
+// Copyright 2018 Frank Schroeder. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// Parts of the lexer are from the template/text/parser package
+// For these parts the following applies:
+//
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file of the go 1.2
+// distribution.
+
+package properties
+
+import (
+       "fmt"
+       "strconv"
+       "strings"
+       "unicode/utf8"
+)
+
+// item represents a token or text string returned from the scanner.
+type item struct {
+       typ itemType // The type of this item.
+       pos int      // The starting position, in bytes, of this item in the 
input string.
+       val string   // The value of this item.
+}
+
+func (i item) String() string {
+       switch {
+       case i.typ == itemEOF:
+               return "EOF"
+       case i.typ == itemError:
+               return i.val
+       case len(i.val) > 10:
+               return fmt.Sprintf("%.10q...", i.val)
+       }
+       return fmt.Sprintf("%q", i.val)
+}
+
+// itemType identifies the type of lex items.
+type itemType int
+
+const (
+       itemError itemType = iota // error occurred; value is text of error
+       itemEOF
+       itemKey     // a key
+       itemValue   // a value
+       itemComment // a comment
+)
+
+// defines a constant for EOF
+const eof = -1
+
+// permitted whitespace characters space, FF and TAB
+const whitespace = " \f\t"
+
+// stateFn represents the state of the scanner as a function that returns the 
next state.
+type stateFn func(*lexer) stateFn
+
+// lexer holds the state of the scanner.
+type lexer struct {
+       input   string    // the string being scanned
+       state   stateFn   // the next lexing function to enter
+       pos     int       // current position in the input
+       start   int       // start position of this item
+       width   int       // width of last rune read from input
+       lastPos int       // position of most recent item returned by nextItem
+       runes   []rune    // scanned runes for this item
+       items   chan item // channel of scanned items
+}
+
+// next returns the next rune in the input.
+func (l *lexer) next() rune {
+       if l.pos >= len(l.input) {
+               l.width = 0
+               return eof
+       }
+       r, w := utf8.DecodeRuneInString(l.input[l.pos:])
+       l.width = w
+       l.pos += l.width
+       return r
+}
+
+// peek returns but does not consume the next rune in the input.
+func (l *lexer) peek() rune {
+       r := l.next()
+       l.backup()
+       return r
+}
+
+// backup steps back one rune. Can only be called once per call of next.
+func (l *lexer) backup() {
+       l.pos -= l.width
+}
+
+// emit passes an item back to the client.
+func (l *lexer) emit(t itemType) {
+       i := item{t, l.start, string(l.runes)}
+       l.items <- i
+       l.start = l.pos
+       l.runes = l.runes[:0]
+}
+
+// ignore skips over the pending input before this point.
+func (l *lexer) ignore() {
+       l.start = l.pos
+}
+
+// appends the rune to the current value
+func (l *lexer) appendRune(r rune) {
+       l.runes = append(l.runes, r)
+}
+
+// accept consumes the next rune if it's from the valid set.
+func (l *lexer) accept(valid string) bool {
+       if strings.ContainsRune(valid, l.next()) {
+               return true
+       }
+       l.backup()
+       return false
+}
+
+// acceptRun consumes a run of runes from the valid set.
+func (l *lexer) acceptRun(valid string) {
+       for strings.ContainsRune(valid, l.next()) {
+       }
+       l.backup()
+}
+
+// acceptRunUntil consumes a run of runes up to a terminator.
+func (l *lexer) acceptRunUntil(term rune) {
+       for term != l.next() {
+       }
+       l.backup()
+}
+
+// hasText returns true if the current parsed text is not empty.
+func (l *lexer) isNotEmpty() bool {
+       return l.pos > l.start
+}
+
+// lineNumber reports which line we're on, based on the position of
+// the previous item returned by nextItem. Doing it this way
+// means we don't have to worry about peek double counting.
+func (l *lexer) lineNumber() int {
+       return 1 + strings.Count(l.input[:l.lastPos], "\n")
+}
+
+// errorf returns an error token and terminates the scan by passing
+// back a nil pointer that will be the next state, terminating l.nextItem.
+func (l *lexer) errorf(format string, args ...interface{}) stateFn {
+       l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
+       return nil
+}
+
+// nextItem returns the next item from the input.
+func (l *lexer) nextItem() item {
+       i := <-l.items
+       l.lastPos = i.pos
+       return i
+}
+
+// lex creates a new scanner for the input string.
+func lex(input string) *lexer {
+       l := &lexer{
+               input: input,
+               items: make(chan item),
+               runes: make([]rune, 0, 32),
+       }
+       go l.run()
+       return l
+}
+
+// run runs the state machine for the lexer.
+func (l *lexer) run() {
+       for l.state = lexBeforeKey(l); l.state != nil; {
+               l.state = l.state(l)
+       }
+}
+
+// state functions
+
+// lexBeforeKey scans until a key begins.
+func lexBeforeKey(l *lexer) stateFn {
+       switch r := l.next(); {
+       case isEOF(r):
+               l.emit(itemEOF)
+               return nil
+
+       case isEOL(r):
+               l.ignore()
+               return lexBeforeKey
+
+       case isComment(r):
+               return lexComment
+
+       case isWhitespace(r):
+               l.ignore()
+               return lexBeforeKey
+
+       default:
+               l.backup()
+               return lexKey
+       }
+}
+
+// lexComment scans a comment line. The comment character has already been 
scanned.
+func lexComment(l *lexer) stateFn {
+       l.acceptRun(whitespace)
+       l.ignore()
+       for {
+               switch r := l.next(); {
+               case isEOF(r):
+                       l.ignore()
+                       l.emit(itemEOF)
+                       return nil
+               case isEOL(r):
+                       l.emit(itemComment)
+                       return lexBeforeKey
+               default:
+                       l.appendRune(r)
+               }
+       }
+}
+
+// lexKey scans the key up to a delimiter
+func lexKey(l *lexer) stateFn {
+       var r rune
+
+Loop:
+       for {
+               switch r = l.next(); {
+
+               case isEscape(r):
+                       err := l.scanEscapeSequence()
+                       if err != nil {
+                               return l.errorf(err.Error())
+                       }
+
+               case isEndOfKey(r):
+                       l.backup()
+                       break Loop
+
+               case isEOF(r):
+                       break Loop
+
+               default:
+                       l.appendRune(r)
+               }
+       }
+
+       if len(l.runes) > 0 {
+               l.emit(itemKey)
+       }
+
+       if isEOF(r) {
+               l.emit(itemEOF)
+               return nil
+       }
+
+       return lexBeforeValue
+}
+
+// lexBeforeValue scans the delimiter between key and value.
+// Leading and trailing whitespace is ignored.
+// We expect to be just after the key.
+func lexBeforeValue(l *lexer) stateFn {
+       l.acceptRun(whitespace)
+       l.accept(":=")
+       l.acceptRun(whitespace)
+       l.ignore()
+       return lexValue
+}
+
+// lexValue scans text until the end of the line. We expect to be just after 
the delimiter.
+func lexValue(l *lexer) stateFn {
+       for {
+               switch r := l.next(); {
+               case isEscape(r):
+                       if isEOL(l.peek()) {
+                               l.next()
+                               l.acceptRun(whitespace)
+                       } else {
+                               err := l.scanEscapeSequence()
+                               if err != nil {
+                                       return l.errorf(err.Error())
+                               }
+                       }
+
+               case isEOL(r):
+                       l.emit(itemValue)
+                       l.ignore()
+                       return lexBeforeKey
+
+               case isEOF(r):
+                       l.emit(itemValue)
+                       l.emit(itemEOF)
+                       return nil
+
+               default:
+                       l.appendRune(r)
+               }
+       }
+}
+
+// scanEscapeSequence scans either one of the escaped characters
+// or a unicode literal. We expect to be after the escape character.
+func (l *lexer) scanEscapeSequence() error {
+       switch r := l.next(); {
+
+       case isEscapedCharacter(r):
+               l.appendRune(decodeEscapedCharacter(r))
+               return nil
+
+       case atUnicodeLiteral(r):
+               return l.scanUnicodeLiteral()
+
+       case isEOF(r):
+               return fmt.Errorf("premature EOF")
+
+       // silently drop the escape character and append the rune as is
+       default:
+               l.appendRune(r)
+               return nil
+       }
+}
+
+// scans a unicode literal in the form \uXXXX. We expect to be after the \u.
+func (l *lexer) scanUnicodeLiteral() error {
+       // scan the digits
+       d := make([]rune, 4)
+       for i := 0; i < 4; i++ {
+               d[i] = l.next()
+               if d[i] == eof || 
!strings.ContainsRune("0123456789abcdefABCDEF", d[i]) {
+                       return fmt.Errorf("invalid unicode literal")
+               }
+       }
+
+       // decode the digits into a rune
+       r, err := strconv.ParseInt(string(d), 16, 0)
+       if err != nil {
+               return err
+       }
+
+       l.appendRune(rune(r))
+       return nil
+}
+
+// decodeEscapedCharacter returns the unescaped rune. We expect to be after 
the escape character.
+func decodeEscapedCharacter(r rune) rune {
+       switch r {
+       case 'f':
+               return '\f'
+       case 'n':
+               return '\n'
+       case 'r':
+               return '\r'
+       case 't':
+               return '\t'
+       default:
+               return r
+       }
+}
+
+// atUnicodeLiteral reports whether we are at a unicode literal.
+// The escape character has already been consumed.
+func atUnicodeLiteral(r rune) bool {
+       return r == 'u'
+}
+
+// isComment reports whether we are at the start of a comment.
+func isComment(r rune) bool {
+       return r == '#' || r == '!'
+}
+
+// isEndOfKey reports whether the rune terminates the current key.
+func isEndOfKey(r rune) bool {
+       return strings.ContainsRune(" \f\t\r\n:=", r)
+}
+
+// isEOF reports whether we are at EOF.
+func isEOF(r rune) bool {
+       return r == eof
+}
+
+// isEOL reports whether we are at a new line character.
+func isEOL(r rune) bool {
+       return r == '\n' || r == '\r'
+}
+
+// isEscape reports whether the rune is the escape character which
+// prefixes unicode literals and other escaped characters.
+func isEscape(r rune) bool {
+       return r == '\\'
+}
+
+// isEscapedCharacter reports whether we are at one of the characters that 
need escaping.
+// The escape character has already been consumed.
+func isEscapedCharacter(r rune) bool {
+       return strings.ContainsRune(" :=fnrt", r)
+}
+
+// isWhitespace reports whether the rune is a whitespace character.
+func isWhitespace(r rune) bool {
+       return strings.ContainsRune(whitespace, r)
+}
diff --git a/vendor/github.com/magiconair/properties/load.go 
b/vendor/github.com/magiconair/properties/load.go
new file mode 100644
index 0000000..c8e1b58
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/load.go
@@ -0,0 +1,292 @@
+// Copyright 2018 Frank Schroeder. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package properties
+
+import (
+       "fmt"
+       "io/ioutil"
+       "net/http"
+       "os"
+       "strings"
+)
+
+// Encoding specifies encoding of the input data.
+type Encoding uint
+
+const (
+       // utf8Default is a private placeholder for the zero value of Encoding 
to
+       // ensure that it has the correct meaning. UTF8 is the default encoding 
but
+       // was assigned a non-zero value which cannot be changed without 
breaking
+       // existing code. Clients should continue to use the public constants.
+       utf8Default Encoding = iota
+
+       // UTF8 interprets the input data as UTF-8.
+       UTF8
+
+       // ISO_8859_1 interprets the input data as ISO-8859-1.
+       ISO_8859_1
+)
+
+type Loader struct {
+       // Encoding determines how the data from files and byte buffers
+       // is interpreted. For URLs the Content-Type header is used
+       // to determine the encoding of the data.
+       Encoding Encoding
+
+       // DisableExpansion configures the property expansion of the
+       // returned property object. When set to true, the property values
+       // will not be expanded and the Property object will not be checked
+       // for invalid expansion expressions.
+       DisableExpansion bool
+
+       // IgnoreMissing configures whether missing files or URLs which return
+       // 404 are reported as errors. When set to true, missing files and 404
+       // status codes are not reported as errors.
+       IgnoreMissing bool
+}
+
+// Load reads a buffer into a Properties struct.
+func (l *Loader) LoadBytes(buf []byte) (*Properties, error) {
+       return l.loadBytes(buf, l.Encoding)
+}
+
+// LoadAll reads the content of multiple URLs or files in the given order into
+// a Properties struct. If IgnoreMissing is true then a 404 status code or
+// missing file will not be reported as error. Encoding sets the encoding for
+// files. For the URLs see LoadURL for the Content-Type header and the
+// encoding.
+func (l *Loader) LoadAll(names []string) (*Properties, error) {
+       all := NewProperties()
+       for _, name := range names {
+               n, err := expandName(name)
+               if err != nil {
+                       return nil, err
+               }
+
+               var p *Properties
+               switch {
+               case strings.HasPrefix(n, "http://";):
+                       p, err = l.LoadURL(n)
+               case strings.HasPrefix(n, "https://";):
+                       p, err = l.LoadURL(n)
+               default:
+                       p, err = l.LoadFile(n)
+               }
+               if err != nil {
+                       return nil, err
+               }
+               all.Merge(p)
+       }
+
+       all.DisableExpansion = l.DisableExpansion
+       if all.DisableExpansion {
+               return all, nil
+       }
+       return all, all.check()
+}
+
+// LoadFile reads a file into a Properties struct.
+// If IgnoreMissing is true then a missing file will not be
+// reported as error.
+func (l *Loader) LoadFile(filename string) (*Properties, error) {
+       data, err := ioutil.ReadFile(filename)
+       if err != nil {
+               if l.IgnoreMissing && os.IsNotExist(err) {
+                       LogPrintf("properties: %s not found. skipping", 
filename)
+                       return NewProperties(), nil
+               }
+               return nil, err
+       }
+       return l.loadBytes(data, l.Encoding)
+}
+
+// LoadURL reads the content of the URL into a Properties struct.
+//
+// The encoding is determined via the Content-Type header which
+// should be set to 'text/plain'. If the 'charset' parameter is
+// missing, 'iso-8859-1' or 'latin1' the encoding is set to
+// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the
+// encoding is set to UTF-8. A missing content type header is
+// interpreted as 'text/plain; charset=utf-8'.
+func (l *Loader) LoadURL(url string) (*Properties, error) {
+       resp, err := http.Get(url)
+       if err != nil {
+               return nil, fmt.Errorf("properties: error fetching %q. %s", 
url, err)
+       }
+
+       if resp.StatusCode == 404 && l.IgnoreMissing {
+               LogPrintf("properties: %s returned %d. skipping", url, 
resp.StatusCode)
+               return NewProperties(), nil
+       }
+
+       if resp.StatusCode != 200 {
+               return nil, fmt.Errorf("properties: %s returned %d", url, 
resp.StatusCode)
+       }
+
+       body, err := ioutil.ReadAll(resp.Body)
+       if err != nil {
+               return nil, fmt.Errorf("properties: %s error reading response. 
%s", url, err)
+       }
+       defer resp.Body.Close()
+
+       ct := resp.Header.Get("Content-Type")
+       var enc Encoding
+       switch strings.ToLower(ct) {
+       case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; 
charset=latin1":
+               enc = ISO_8859_1
+       case "", "text/plain; charset=utf-8":
+               enc = UTF8
+       default:
+               return nil, fmt.Errorf("properties: invalid content type %s", 
ct)
+       }
+
+       return l.loadBytes(body, enc)
+}
+
+func (l *Loader) loadBytes(buf []byte, enc Encoding) (*Properties, error) {
+       p, err := parse(convert(buf, enc))
+       if err != nil {
+               return nil, err
+       }
+       p.DisableExpansion = l.DisableExpansion
+       if p.DisableExpansion {
+               return p, nil
+       }
+       return p, p.check()
+}
+
+// Load reads a buffer into a Properties struct.
+func Load(buf []byte, enc Encoding) (*Properties, error) {
+       l := &Loader{Encoding: enc}
+       return l.LoadBytes(buf)
+}
+
+// LoadString reads an UTF8 string into a properties struct.
+func LoadString(s string) (*Properties, error) {
+       l := &Loader{Encoding: UTF8}
+       return l.LoadBytes([]byte(s))
+}
+
+// LoadMap creates a new Properties struct from a string map.
+func LoadMap(m map[string]string) *Properties {
+       p := NewProperties()
+       for k, v := range m {
+               p.Set(k, v)
+       }
+       return p
+}
+
+// LoadFile reads a file into a Properties struct.
+func LoadFile(filename string, enc Encoding) (*Properties, error) {
+       l := &Loader{Encoding: enc}
+       return l.LoadAll([]string{filename})
+}
+
+// LoadFiles reads multiple files in the given order into
+// a Properties struct. If 'ignoreMissing' is true then
+// non-existent files will not be reported as error.
+func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) 
(*Properties, error) {
+       l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing}
+       return l.LoadAll(filenames)
+}
+
+// LoadURL reads the content of the URL into a Properties struct.
+// See Loader#LoadURL for details.
+func LoadURL(url string) (*Properties, error) {
+       l := &Loader{Encoding: UTF8}
+       return l.LoadAll([]string{url})
+}
+
+// LoadURLs reads the content of multiple URLs in the given order into a
+// Properties struct. If IgnoreMissing is true then a 404 status code will
+// not be reported as error. See Loader#LoadURL for the Content-Type header
+// and the encoding.
+func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) {
+       l := &Loader{Encoding: UTF8, IgnoreMissing: ignoreMissing}
+       return l.LoadAll(urls)
+}
+
+// LoadAll reads the content of multiple URLs or files in the given order into 
a
+// Properties struct. If 'ignoreMissing' is true then a 404 status code or 
missing file will
+// not be reported as error. Encoding sets the encoding for files. For the 
URLs please see
+// LoadURL for the Content-Type header and the encoding.
+func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, 
error) {
+       l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing}
+       return l.LoadAll(names)
+}
+
+// MustLoadString reads an UTF8 string into a Properties struct and
+// panics on error.
+func MustLoadString(s string) *Properties {
+       return must(LoadString(s))
+}
+
+// MustLoadFile reads a file into a Properties struct and
+// panics on error.
+func MustLoadFile(filename string, enc Encoding) *Properties {
+       return must(LoadFile(filename, enc))
+}
+
+// MustLoadFiles reads multiple files in the given order into
+// a Properties struct and panics on error. If 'ignoreMissing'
+// is true then non-existent files will not be reported as error.
+func MustLoadFiles(filenames []string, enc Encoding, ignoreMissing bool) 
*Properties {
+       return must(LoadFiles(filenames, enc, ignoreMissing))
+}
+
+// MustLoadURL reads the content of a URL into a Properties struct and
+// panics on error.
+func MustLoadURL(url string) *Properties {
+       return must(LoadURL(url))
+}
+
+// MustLoadURLs reads the content of multiple URLs in the given order into a
+// Properties struct and panics on error. If 'ignoreMissing' is true then a 404
+// status code will not be reported as error.
+func MustLoadURLs(urls []string, ignoreMissing bool) *Properties {
+       return must(LoadURLs(urls, ignoreMissing))
+}
+
+// MustLoadAll reads the content of multiple URLs or files in the given order 
into a
+// Properties struct. If 'ignoreMissing' is true then a 404 status code or 
missing file will
+// not be reported as error. Encoding sets the encoding for files. For the 
URLs please see
+// LoadURL for the Content-Type header and the encoding. It panics on error.
+func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties 
{
+       return must(LoadAll(names, enc, ignoreMissing))
+}
+
+func must(p *Properties, err error) *Properties {
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return p
+}
+
+// expandName expands ${ENV_VAR} expressions in a name.
+// If the environment variable does not exist then it will be replaced
+// with an empty string. Malformed expressions like "${ENV_VAR" will
+// be reported as error.
+func expandName(name string) (string, error) {
+       return expand(name, []string{}, "${", "}", make(map[string]string))
+}
+
+// Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string.
+// For ISO-8859-1 we can convert each byte straight into a rune since the
+// first 256 unicode code points cover ISO-8859-1.
+func convert(buf []byte, enc Encoding) string {
+       switch enc {
+       case utf8Default, UTF8:
+               return string(buf)
+       case ISO_8859_1:
+               runes := make([]rune, len(buf))
+               for i, b := range buf {
+                       runes[i] = rune(b)
+               }
+               return string(runes)
+       default:
+               ErrorHandler(fmt.Errorf("unsupported encoding %v", enc))
+       }
+       panic("ErrorHandler should exit")
+}
diff --git a/vendor/github.com/magiconair/properties/parser.go 
b/vendor/github.com/magiconair/properties/parser.go
new file mode 100644
index 0000000..cdc4a80
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/parser.go
@@ -0,0 +1,95 @@
+// Copyright 2018 Frank Schroeder. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package properties
+
+import (
+       "fmt"
+       "runtime"
+)
+
+type parser struct {
+       lex *lexer
+}
+
+func parse(input string) (properties *Properties, err error) {
+       p := &parser{lex: lex(input)}
+       defer p.recover(&err)
+
+       properties = NewProperties()
+       key := ""
+       comments := []string{}
+
+       for {
+               token := p.expectOneOf(itemComment, itemKey, itemEOF)
+               switch token.typ {
+               case itemEOF:
+                       goto done
+               case itemComment:
+                       comments = append(comments, token.val)
+                       continue
+               case itemKey:
+                       key = token.val
+                       if _, ok := properties.m[key]; !ok {
+                               properties.k = append(properties.k, key)
+                       }
+               }
+
+               token = p.expectOneOf(itemValue, itemEOF)
+               if len(comments) > 0 {
+                       properties.c[key] = comments
+                       comments = []string{}
+               }
+               switch token.typ {
+               case itemEOF:
+                       properties.m[key] = ""
+                       goto done
+               case itemValue:
+                       properties.m[key] = token.val
+               }
+       }
+
+done:
+       return properties, nil
+}
+
+func (p *parser) errorf(format string, args ...interface{}) {
+       format = fmt.Sprintf("properties: Line %d: %s", p.lex.lineNumber(), 
format)
+       panic(fmt.Errorf(format, args...))
+}
+
+func (p *parser) expect(expected itemType) (token item) {
+       token = p.lex.nextItem()
+       if token.typ != expected {
+               p.unexpected(token)
+       }
+       return token
+}
+
+func (p *parser) expectOneOf(expected ...itemType) (token item) {
+       token = p.lex.nextItem()
+       for _, v := range expected {
+               if token.typ == v {
+                       return token
+               }
+       }
+       p.unexpected(token)
+       panic("unexpected token")
+}
+
+func (p *parser) unexpected(token item) {
+       p.errorf(token.String())
+}
+
+// recover is the handler that turns panics into returns from the top level of 
Parse.
+func (p *parser) recover(errp *error) {
+       e := recover()
+       if e != nil {
+               if _, ok := e.(runtime.Error); ok {
+                       panic(e)
+               }
+               *errp = e.(error)
+       }
+       return
+}
diff --git a/vendor/github.com/magiconair/properties/properties.go 
b/vendor/github.com/magiconair/properties/properties.go
new file mode 100644
index 0000000..cb3d1a3
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/properties.go
@@ -0,0 +1,833 @@
+// Copyright 2018 Frank Schroeder. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package properties
+
+// BUG(frank): Set() does not check for invalid unicode literals since this is 
currently handled by the lexer.
+// BUG(frank): Write() does not allow to configure the newline character. 
Therefore, on Windows LF is used.
+
+import (
+       "fmt"
+       "io"
+       "log"
+       "os"
+       "regexp"
+       "strconv"
+       "strings"
+       "time"
+       "unicode/utf8"
+)
+
+const maxExpansionDepth = 64
+
+// ErrorHandlerFunc defines the type of function which handles failures
+// of the MustXXX() functions. An error handler function must exit
+// the application after handling the error.
+type ErrorHandlerFunc func(error)
+
+// ErrorHandler is the function which handles failures of the MustXXX()
+// functions. The default is LogFatalHandler.
+var ErrorHandler ErrorHandlerFunc = LogFatalHandler
+
+// LogHandlerFunc defines the function prototype for logging errors.
+type LogHandlerFunc func(fmt string, args ...interface{})
+
+// LogPrintf defines a log handler which uses log.Printf.
+var LogPrintf LogHandlerFunc = log.Printf
+
+// LogFatalHandler handles the error by logging a fatal error and exiting.
+func LogFatalHandler(err error) {
+       log.Fatal(err)
+}
+
+// PanicHandler handles the error by panicking.
+func PanicHandler(err error) {
+       panic(err)
+}
+
+// 
-----------------------------------------------------------------------------
+
+// A Properties contains the key/value pairs from the properties input.
+// All values are stored in unexpanded form and are expanded at runtime
+type Properties struct {
+       // Pre-/Postfix for property expansion.
+       Prefix  string
+       Postfix string
+
+       // DisableExpansion controls the expansion of properties on Get()
+       // and the check for circular references on Set(). When set to
+       // true Properties behaves like a simple key/value store and does
+       // not check for circular references on Get() or on Set().
+       DisableExpansion bool
+
+       // Stores the key/value pairs
+       m map[string]string
+
+       // Stores the comments per key.
+       c map[string][]string
+
+       // Stores the keys in order of appearance.
+       k []string
+}
+
+// NewProperties creates a new Properties struct with the default
+// configuration for "${key}" expressions.
+func NewProperties() *Properties {
+       return &Properties{
+               Prefix:  "${",
+               Postfix: "}",
+               m:       map[string]string{},
+               c:       map[string][]string{},
+               k:       []string{},
+       }
+}
+
+// Load reads a buffer into the given Properties struct.
+func (p *Properties) Load(buf []byte, enc Encoding) error {
+       l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion}
+       newProperties, err := l.LoadBytes(buf)
+       if err != nil {
+               return err
+       }
+       p.Merge(newProperties)
+       return nil
+}
+
+// Get returns the expanded value for the given key if exists.
+// Otherwise, ok is false.
+func (p *Properties) Get(key string) (value string, ok bool) {
+       v, ok := p.m[key]
+       if p.DisableExpansion {
+               return v, ok
+       }
+       if !ok {
+               return "", false
+       }
+
+       expanded, err := p.expand(key, v)
+
+       // we guarantee that the expanded value is free of
+       // circular references and malformed expressions
+       // so we panic if we still get an error here.
+       if err != nil {
+               ErrorHandler(fmt.Errorf("%s in %q", err, key+" = "+v))
+       }
+
+       return expanded, true
+}
+
+// MustGet returns the expanded value for the given key if exists.
+// Otherwise, it panics.
+func (p *Properties) MustGet(key string) string {
+       if v, ok := p.Get(key); ok {
+               return v
+       }
+       ErrorHandler(invalidKeyError(key))
+       panic("ErrorHandler should exit")
+}
+
+// ----------------------------------------------------------------------------
+
+// ClearComments removes the comments for all keys.
+func (p *Properties) ClearComments() {
+       p.c = map[string][]string{}
+}
+
+// ----------------------------------------------------------------------------
+
+// GetComment returns the last comment before the given key or an empty string.
+func (p *Properties) GetComment(key string) string {
+       comments, ok := p.c[key]
+       if !ok || len(comments) == 0 {
+               return ""
+       }
+       return comments[len(comments)-1]
+}
+
+// ----------------------------------------------------------------------------
+
+// GetComments returns all comments that appeared before the given key or nil.
+func (p *Properties) GetComments(key string) []string {
+       if comments, ok := p.c[key]; ok {
+               return comments
+       }
+       return nil
+}
+
+// ----------------------------------------------------------------------------
+
+// SetComment sets the comment for the key.
+func (p *Properties) SetComment(key, comment string) {
+       p.c[key] = []string{comment}
+}
+
+// ----------------------------------------------------------------------------
+
+// SetComments sets the comments for the key. If the comments are nil then
+// all comments for this key are deleted.
+func (p *Properties) SetComments(key string, comments []string) {
+       if comments == nil {
+               delete(p.c, key)
+               return
+       }
+       p.c[key] = comments
+}
+
+// ----------------------------------------------------------------------------
+
+// GetBool checks if the expanded value is one of '1', 'yes',
+// 'true' or 'on' if the key exists. The comparison is case-insensitive.
+// If the key does not exist the default value is returned.
+func (p *Properties) GetBool(key string, def bool) bool {
+       v, err := p.getBool(key)
+       if err != nil {
+               return def
+       }
+       return v
+}
+
+// MustGetBool checks if the expanded value is one of '1', 'yes',
+// 'true' or 'on' if the key exists. The comparison is case-insensitive.
+// If the key does not exist the function panics.
+func (p *Properties) MustGetBool(key string) bool {
+       v, err := p.getBool(key)
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return v
+}
+
+func (p *Properties) getBool(key string) (value bool, err error) {
+       if v, ok := p.Get(key); ok {
+               return boolVal(v), nil
+       }
+       return false, invalidKeyError(key)
+}
+
+func boolVal(v string) bool {
+       v = strings.ToLower(v)
+       return v == "1" || v == "true" || v == "yes" || v == "on"
+}
+
+// ----------------------------------------------------------------------------
+
+// GetDuration parses the expanded value as an time.Duration (in ns) if the
+// key exists. If key does not exist or the value cannot be parsed the default
+// value is returned. In almost all cases you want to use GetParsedDuration().
+func (p *Properties) GetDuration(key string, def time.Duration) time.Duration {
+       v, err := p.getInt64(key)
+       if err != nil {
+               return def
+       }
+       return time.Duration(v)
+}
+
+// MustGetDuration parses the expanded value as an time.Duration (in ns) if
+// the key exists. If key does not exist or the value cannot be parsed the
+// function panics. In almost all cases you want to use 
MustGetParsedDuration().
+func (p *Properties) MustGetDuration(key string) time.Duration {
+       v, err := p.getInt64(key)
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return time.Duration(v)
+}
+
+// ----------------------------------------------------------------------------
+
+// GetParsedDuration parses the expanded value with time.ParseDuration() if 
the key exists.
+// If key does not exist or the value cannot be parsed the default
+// value is returned.
+func (p *Properties) GetParsedDuration(key string, def time.Duration) 
time.Duration {
+       s, ok := p.Get(key)
+       if !ok {
+               return def
+       }
+       v, err := time.ParseDuration(s)
+       if err != nil {
+               return def
+       }
+       return v
+}
+
+// MustGetParsedDuration parses the expanded value with time.ParseDuration() 
if the key exists.
+// If key does not exist or the value cannot be parsed the function panics.
+func (p *Properties) MustGetParsedDuration(key string) time.Duration {
+       s, ok := p.Get(key)
+       if !ok {
+               ErrorHandler(invalidKeyError(key))
+       }
+       v, err := time.ParseDuration(s)
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return v
+}
+
+// ----------------------------------------------------------------------------
+
+// GetFloat64 parses the expanded value as a float64 if the key exists.
+// If key does not exist or the value cannot be parsed the default
+// value is returned.
+func (p *Properties) GetFloat64(key string, def float64) float64 {
+       v, err := p.getFloat64(key)
+       if err != nil {
+               return def
+       }
+       return v
+}
+
+// MustGetFloat64 parses the expanded value as a float64 if the key exists.
+// If key does not exist or the value cannot be parsed the function panics.
+func (p *Properties) MustGetFloat64(key string) float64 {
+       v, err := p.getFloat64(key)
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return v
+}
+
+func (p *Properties) getFloat64(key string) (value float64, err error) {
+       if v, ok := p.Get(key); ok {
+               value, err = strconv.ParseFloat(v, 64)
+               if err != nil {
+                       return 0, err
+               }
+               return value, nil
+       }
+       return 0, invalidKeyError(key)
+}
+
+// ----------------------------------------------------------------------------
+
+// GetInt parses the expanded value as an int if the key exists.
+// If key does not exist or the value cannot be parsed the default
+// value is returned. If the value does not fit into an int the
+// function panics with an out of range error.
+func (p *Properties) GetInt(key string, def int) int {
+       v, err := p.getInt64(key)
+       if err != nil {
+               return def
+       }
+       return intRangeCheck(key, v)
+}
+
+// MustGetInt parses the expanded value as an int if the key exists.
+// If key does not exist or the value cannot be parsed the function panics.
+// If the value does not fit into an int the function panics with
+// an out of range error.
+func (p *Properties) MustGetInt(key string) int {
+       v, err := p.getInt64(key)
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return intRangeCheck(key, v)
+}
+
+// ----------------------------------------------------------------------------
+
+// GetInt64 parses the expanded value as an int64 if the key exists.
+// If key does not exist or the value cannot be parsed the default
+// value is returned.
+func (p *Properties) GetInt64(key string, def int64) int64 {
+       v, err := p.getInt64(key)
+       if err != nil {
+               return def
+       }
+       return v
+}
+
+// MustGetInt64 parses the expanded value as an int if the key exists.
+// If key does not exist or the value cannot be parsed the function panics.
+func (p *Properties) MustGetInt64(key string) int64 {
+       v, err := p.getInt64(key)
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return v
+}
+
+func (p *Properties) getInt64(key string) (value int64, err error) {
+       if v, ok := p.Get(key); ok {
+               value, err = strconv.ParseInt(v, 10, 64)
+               if err != nil {
+                       return 0, err
+               }
+               return value, nil
+       }
+       return 0, invalidKeyError(key)
+}
+
+// ----------------------------------------------------------------------------
+
+// GetUint parses the expanded value as an uint if the key exists.
+// If key does not exist or the value cannot be parsed the default
+// value is returned. If the value does not fit into an int the
+// function panics with an out of range error.
+func (p *Properties) GetUint(key string, def uint) uint {
+       v, err := p.getUint64(key)
+       if err != nil {
+               return def
+       }
+       return uintRangeCheck(key, v)
+}
+
+// MustGetUint parses the expanded value as an int if the key exists.
+// If key does not exist or the value cannot be parsed the function panics.
+// If the value does not fit into an int the function panics with
+// an out of range error.
+func (p *Properties) MustGetUint(key string) uint {
+       v, err := p.getUint64(key)
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return uintRangeCheck(key, v)
+}
+
+// ----------------------------------------------------------------------------
+
+// GetUint64 parses the expanded value as an uint64 if the key exists.
+// If key does not exist or the value cannot be parsed the default
+// value is returned.
+func (p *Properties) GetUint64(key string, def uint64) uint64 {
+       v, err := p.getUint64(key)
+       if err != nil {
+               return def
+       }
+       return v
+}
+
+// MustGetUint64 parses the expanded value as an int if the key exists.
+// If key does not exist or the value cannot be parsed the function panics.
+func (p *Properties) MustGetUint64(key string) uint64 {
+       v, err := p.getUint64(key)
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return v
+}
+
+func (p *Properties) getUint64(key string) (value uint64, err error) {
+       if v, ok := p.Get(key); ok {
+               value, err = strconv.ParseUint(v, 10, 64)
+               if err != nil {
+                       return 0, err
+               }
+               return value, nil
+       }
+       return 0, invalidKeyError(key)
+}
+
+// ----------------------------------------------------------------------------
+
+// GetString returns the expanded value for the given key if exists or
+// the default value otherwise.
+func (p *Properties) GetString(key, def string) string {
+       if v, ok := p.Get(key); ok {
+               return v
+       }
+       return def
+}
+
+// MustGetString returns the expanded value for the given key if exists or
+// panics otherwise.
+func (p *Properties) MustGetString(key string) string {
+       if v, ok := p.Get(key); ok {
+               return v
+       }
+       ErrorHandler(invalidKeyError(key))
+       panic("ErrorHandler should exit")
+}
+
+// ----------------------------------------------------------------------------
+
+// Filter returns a new properties object which contains all properties
+// for which the key matches the pattern.
+func (p *Properties) Filter(pattern string) (*Properties, error) {
+       re, err := regexp.Compile(pattern)
+       if err != nil {
+               return nil, err
+       }
+
+       return p.FilterRegexp(re), nil
+}
+
+// FilterRegexp returns a new properties object which contains all properties
+// for which the key matches the regular expression.
+func (p *Properties) FilterRegexp(re *regexp.Regexp) *Properties {
+       pp := NewProperties()
+       for _, k := range p.k {
+               if re.MatchString(k) {
+                       // TODO(fs): we are ignoring the error which flags a 
circular reference.
+                       // TODO(fs): since we are just copying a subset of keys 
this cannot happen (fingers crossed)
+                       pp.Set(k, p.m[k])
+               }
+       }
+       return pp
+}
+
+// FilterPrefix returns a new properties object with a subset of all keys
+// with the given prefix.
+func (p *Properties) FilterPrefix(prefix string) *Properties {
+       pp := NewProperties()
+       for _, k := range p.k {
+               if strings.HasPrefix(k, prefix) {
+                       // TODO(fs): we are ignoring the error which flags a 
circular reference.
+                       // TODO(fs): since we are just copying a subset of keys 
this cannot happen (fingers crossed)
+                       pp.Set(k, p.m[k])
+               }
+       }
+       return pp
+}
+
+// FilterStripPrefix returns a new properties object with a subset of all keys
+// with the given prefix and the prefix removed from the keys.
+func (p *Properties) FilterStripPrefix(prefix string) *Properties {
+       pp := NewProperties()
+       n := len(prefix)
+       for _, k := range p.k {
+               if len(k) > len(prefix) && strings.HasPrefix(k, prefix) {
+                       // TODO(fs): we are ignoring the error which flags a 
circular reference.
+                       // TODO(fs): since we are modifying keys I am not 
entirely sure whether we can create a circular reference
+                       // TODO(fs): this function should probably return an 
error but the signature is fixed
+                       pp.Set(k[n:], p.m[k])
+               }
+       }
+       return pp
+}
+
+// Len returns the number of keys.
+func (p *Properties) Len() int {
+       return len(p.m)
+}
+
+// Keys returns all keys in the same order as in the input.
+func (p *Properties) Keys() []string {
+       keys := make([]string, len(p.k))
+       copy(keys, p.k)
+       return keys
+}
+
+// Set sets the property key to the corresponding value.
+// If a value for key existed before then ok is true and prev
+// contains the previous value. If the value contains a
+// circular reference or a malformed expression then
+// an error is returned.
+// An empty key is silently ignored.
+func (p *Properties) Set(key, value string) (prev string, ok bool, err error) {
+       if key == "" {
+               return "", false, nil
+       }
+
+       // if expansion is disabled we allow circular references
+       if p.DisableExpansion {
+               prev, ok = p.Get(key)
+               p.m[key] = value
+               if !ok {
+                       p.k = append(p.k, key)
+               }
+               return prev, ok, nil
+       }
+
+       // to check for a circular reference we temporarily need
+       // to set the new value. If there is an error then revert
+       // to the previous state. Only if all tests are successful
+       // then we add the key to the p.k list.
+       prev, ok = p.Get(key)
+       p.m[key] = value
+
+       // now check for a circular reference
+       _, err = p.expand(key, value)
+       if err != nil {
+
+               // revert to the previous state
+               if ok {
+                       p.m[key] = prev
+               } else {
+                       delete(p.m, key)
+               }
+
+               return "", false, err
+       }
+
+       if !ok {
+               p.k = append(p.k, key)
+       }
+
+       return prev, ok, nil
+}
+
+// SetValue sets property key to the default string value
+// as defined by fmt.Sprintf("%v").
+func (p *Properties) SetValue(key string, value interface{}) error {
+       _, _, err := p.Set(key, fmt.Sprintf("%v", value))
+       return err
+}
+
+// MustSet sets the property key to the corresponding value.
+// If a value for key existed before then ok is true and prev
+// contains the previous value. An empty key is silently ignored.
+func (p *Properties) MustSet(key, value string) (prev string, ok bool) {
+       prev, ok, err := p.Set(key, value)
+       if err != nil {
+               ErrorHandler(err)
+       }
+       return prev, ok
+}
+
+// String returns a string of all expanded 'key = value' pairs.
+func (p *Properties) String() string {
+       var s string
+       for _, key := range p.k {
+               value, _ := p.Get(key)
+               s = fmt.Sprintf("%s%s = %s\n", s, key, value)
+       }
+       return s
+}
+
+// Write writes all unexpanded 'key = value' pairs to the given writer.
+// Write returns the number of bytes written and any write error encountered.
+func (p *Properties) Write(w io.Writer, enc Encoding) (n int, err error) {
+       return p.WriteComment(w, "", enc)
+}
+
+// WriteComment writes all unexpanced 'key = value' pairs to the given writer.
+// If prefix is not empty then comments are written with a blank line and the
+// given prefix. The prefix should be either "# " or "! " to be compatible with
+// the properties file format. Otherwise, the properties parser will not be
+// able to read the file back in. It returns the number of bytes written and
+// any write error encountered.
+func (p *Properties) WriteComment(w io.Writer, prefix string, enc Encoding) (n 
int, err error) {
+       var x int
+
+       for _, key := range p.k {
+               value := p.m[key]
+
+               if prefix != "" {
+                       if comments, ok := p.c[key]; ok {
+                               // don't print comments if they are all empty
+                               allEmpty := true
+                               for _, c := range comments {
+                                       if c != "" {
+                                               allEmpty = false
+                                               break
+                                       }
+                               }
+
+                               if !allEmpty {
+                                       // add a blank line between entries but 
not at the top
+                                       if len(comments) > 0 && n > 0 {
+                                               x, err = fmt.Fprintln(w)
+                                               if err != nil {
+                                                       return
+                                               }
+                                               n += x
+                                       }
+
+                                       for _, c := range comments {
+                                               x, err = fmt.Fprintf(w, 
"%s%s\n", prefix, encode(c, "", enc))
+                                               if err != nil {
+                                                       return
+                                               }
+                                               n += x
+                                       }
+                               }
+                       }
+               }
+
+               x, err = fmt.Fprintf(w, "%s = %s\n", encode(key, " :", enc), 
encode(value, "", enc))
+               if err != nil {
+                       return
+               }
+               n += x
+       }
+       return
+}
+
+// Map returns a copy of the properties as a map.
+func (p *Properties) Map() map[string]string {
+       m := make(map[string]string)
+       for k, v := range p.m {
+               m[k] = v
+       }
+       return m
+}
+
+// FilterFunc returns a copy of the properties which includes the values which 
passed all filters.
+func (p *Properties) FilterFunc(filters ...func(k, v string) bool) *Properties 
{
+       pp := NewProperties()
+outer:
+       for k, v := range p.m {
+               for _, f := range filters {
+                       if !f(k, v) {
+                               continue outer
+                       }
+                       pp.Set(k, v)
+               }
+       }
+       return pp
+}
+
+// ----------------------------------------------------------------------------
+
+// Delete removes the key and its comments.
+func (p *Properties) Delete(key string) {
+       delete(p.m, key)
+       delete(p.c, key)
+       newKeys := []string{}
+       for _, k := range p.k {
+               if k != key {
+                       newKeys = append(newKeys, k)
+               }
+       }
+       p.k = newKeys
+}
+
+// Merge merges properties, comments and keys from other *Properties into p
+func (p *Properties) Merge(other *Properties) {
+       for k, v := range other.m {
+               p.m[k] = v
+       }
+       for k, v := range other.c {
+               p.c[k] = v
+       }
+
+outer:
+       for _, otherKey := range other.k {
+               for _, key := range p.k {
+                       if otherKey == key {
+                               continue outer
+                       }
+               }
+               p.k = append(p.k, otherKey)
+       }
+}
+
+// ----------------------------------------------------------------------------
+
+// check expands all values and returns an error if a circular reference or
+// a malformed expression was found.
+func (p *Properties) check() error {
+       for key, value := range p.m {
+               if _, err := p.expand(key, value); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (p *Properties) expand(key, input string) (string, error) {
+       // no pre/postfix -> nothing to expand
+       if p.Prefix == "" && p.Postfix == "" {
+               return input, nil
+       }
+
+       return expand(input, []string{key}, p.Prefix, p.Postfix, p.m)
+}
+
+// expand recursively expands expressions of '(prefix)key(postfix)' to their 
corresponding values.
+// The function keeps track of the keys that were already expanded and stops 
if it
+// detects a circular reference or a malformed expression of the form 
'(prefix)key'.
+func expand(s string, keys []string, prefix, postfix string, values 
map[string]string) (string, error) {
+       if len(keys) > maxExpansionDepth {
+               return "", fmt.Errorf("expansion too deep")
+       }
+
+       for {
+               start := strings.Index(s, prefix)
+               if start == -1 {
+                       return s, nil
+               }
+
+               keyStart := start + len(prefix)
+               keyLen := strings.Index(s[keyStart:], postfix)
+               if keyLen == -1 {
+                       return "", fmt.Errorf("malformed expression")
+               }
+
+               end := keyStart + keyLen + len(postfix) - 1
+               key := s[keyStart : keyStart+keyLen]
+
+               // fmt.Printf("s:%q pp:%q start:%d end:%d keyStart:%d keyLen:%d 
key:%q\n", s, prefix + "..." + postfix, start, end, keyStart, keyLen, key)
+
+               for _, k := range keys {
+                       if key == k {
+                               return "", fmt.Errorf("circular reference")
+                       }
+               }
+
+               val, ok := values[key]
+               if !ok {
+                       val = os.Getenv(key)
+               }
+               new_val, err := expand(val, append(keys, key), prefix, postfix, 
values)
+               if err != nil {
+                       return "", err
+               }
+               s = s[:start] + new_val + s[end+1:]
+       }
+       return s, nil
+}
+
+// encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters.
+func encode(s string, special string, enc Encoding) string {
+       switch enc {
+       case UTF8:
+               return encodeUtf8(s, special)
+       case ISO_8859_1:
+               return encodeIso(s, special)
+       default:
+               panic(fmt.Sprintf("unsupported encoding %v", enc))
+       }
+}
+
+func encodeUtf8(s string, special string) string {
+       v := ""
+       for pos := 0; pos < len(s); {
+               r, w := utf8.DecodeRuneInString(s[pos:])
+               pos += w
+               v += escape(r, special)
+       }
+       return v
+}
+
+func encodeIso(s string, special string) string {
+       var r rune
+       var w int
+       var v string
+       for pos := 0; pos < len(s); {
+               switch r, w = utf8.DecodeRuneInString(s[pos:]); {
+               case r < 1<<8: // single byte rune -> escape special chars only
+                       v += escape(r, special)
+               case r < 1<<16: // two byte rune -> unicode literal
+                       v += fmt.Sprintf("\\u%04x", r)
+               default: // more than two bytes per rune -> can't encode
+                       v += "?"
+               }
+               pos += w
+       }
+       return v
+}
+
+func escape(r rune, special string) string {
+       switch r {
+       case '\f':
+               return "\\f"
+       case '\n':
+               return "\\n"
+       case '\r':
+               return "\\r"
+       case '\t':
+               return "\\t"
+       default:
+               if strings.ContainsRune(special, r) {
+                       return "\\" + string(r)
+               }
+               return string(r)
+       }
+}
+
+func invalidKeyError(key string) error {
+       return fmt.Errorf("unknown property: %s", key)
+}
diff --git a/vendor/github.com/magiconair/properties/rangecheck.go 
b/vendor/github.com/magiconair/properties/rangecheck.go
new file mode 100644
index 0000000..b013a2e
--- /dev/null
+++ b/vendor/github.com/magiconair/properties/rangecheck.go
@@ -0,0 +1,31 @@
+// Copyright 2018 Frank Schroeder. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package properties
+
+import (
+       "fmt"
+       "math"
+)
+
+// make this a var to overwrite it in a test
+var is32Bit = ^uint(0) == math.MaxUint32
+
+// intRangeCheck checks if the value fits into the int type and
+// panics if it does not.
+func intRangeCheck(key string, v int64) int {
+       if is32Bit && (v < math.MinInt32 || v > math.MaxInt32) {
+               panic(fmt.Sprintf("Value %d for key %s out of range", v, key))
+       }
+       return int(v)
+}
+
+// uintRangeCheck checks if the value fits into the uint type and
+// panics if it does not.
+func uintRangeCheck(key string, v uint64) uint {
+       if is32Bit && v > math.MaxUint32 {
+               panic(fmt.Sprintf("Value %d for key %s out of range", v, key))
+       }
+       return uint(v)
+}

Reply via email to