You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@camel.apache.org by GitBox <gi...@apache.org> on 2018/12/10 15:01:41 UTC

[GitHub] lburgazzoli closed pull request #273: Traits lifecycle

lburgazzoli closed pull request #273: Traits lifecycle
URL: https://github.com/apache/camel-k/pull/273
 
 
   

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

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

diff --git a/pkg/apis/camel/v1alpha1/types.go b/pkg/apis/camel/v1alpha1/types.go
index 237dc950..3876e829 100644
--- a/pkg/apis/camel/v1alpha1/types.go
+++ b/pkg/apis/camel/v1alpha1/types.go
@@ -18,6 +18,10 @@ limitations under the License.
 package v1alpha1
 
 import (
+	"strings"
+
+	"github.com/apache/camel-k/pkg/util"
+
 	"github.com/mitchellh/mapstructure"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
@@ -69,6 +73,26 @@ func (is *IntegrationSpec) AddSources(sources ...SourceSpec) {
 	is.Sources = append(is.Sources, sources...)
 }
 
+// AddConfiguration --
+func (is *IntegrationSpec) AddConfiguration(confType string, confValue string) {
+	is.Configuration = append(is.Configuration, ConfigurationSpec{
+		Type:  confType,
+		Value: confValue,
+	})
+}
+
+// AddDependency --
+func (is *IntegrationSpec) AddDependency(dependency string) {
+	switch {
+	case strings.HasPrefix(dependency, "mvn:"):
+		util.StringSliceUniqueAdd(&is.Dependencies, dependency)
+	case strings.HasPrefix(dependency, "file:"):
+		util.StringSliceUniqueAdd(&is.Dependencies, dependency)
+	case strings.HasPrefix(dependency, "camel-"):
+		util.StringSliceUniqueAdd(&is.Dependencies, "camel:"+strings.TrimPrefix(dependency, "camel-"))
+	}
+}
+
 // SourceSpec --
 type SourceSpec struct {
 	Name        string   `json:"name,omitempty"`
diff --git a/pkg/client/cmd/run.go b/pkg/client/cmd/run.go
index dd137051..6b3bb8b5 100644
--- a/pkg/client/cmd/run.go
+++ b/pkg/client/cmd/run.go
@@ -20,7 +20,6 @@ package cmd
 import (
 	"bytes"
 	"encoding/base64"
-	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net/http"
@@ -34,11 +33,6 @@ import (
 
 	"github.com/apache/camel-k/pkg/gzip"
 
-	"github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
-	"gopkg.in/yaml.v2"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime"
-
 	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util"
 
@@ -100,23 +94,23 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) *cobra.Command {
 
 type runCmdOptions struct {
 	*RootCmdOptions
+	Compression        bool
+	Wait               bool
+	Logs               bool
+	Sync               bool
+	Dev                bool
 	IntegrationContext string
 	Runtime            string
 	IntegrationName    string
+	Profile            string
+	OutputFormat       string
 	Dependencies       []string
 	Properties         []string
 	ConfigMaps         []string
 	Secrets            []string
 	Repositories       []string
-	Wait               bool
-	Logs               bool
-	Sync               bool
-	Dev                bool
-	Profile            string
 	Traits             []string
 	LoggingLevels      []string
-	OutputFormat       string
-	Compression        bool
 }
 
 func (o *runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error {
@@ -258,7 +252,6 @@ func (o *runCmdOptions) createIntegration(args []string) (*v1alpha1.Integration,
 }
 
 func (o *runCmdOptions) updateIntegrationCode(sources []string) (*v1alpha1.Integration, error) {
-
 	namespace := o.Namespace
 
 	name := ""
@@ -314,44 +307,24 @@ func (o *runCmdOptions) updateIntegrationCode(sources []string) (*v1alpha1.Integ
 		})
 	}
 
-	for _, item := range o.Dependencies {
-		switch {
-		case strings.HasPrefix(item, "mvn:"):
-			integration.Spec.Dependencies = append(integration.Spec.Dependencies, item)
-		case strings.HasPrefix(item, "file:"):
-			integration.Spec.Dependencies = append(integration.Spec.Dependencies, item)
-		case strings.HasPrefix(item, "camel-"):
-			integration.Spec.Dependencies = append(integration.Spec.Dependencies, "camel:"+strings.TrimPrefix(item, "camel-"))
-		}
-	}
-
 	if o.Runtime != "" {
-		util.StringSliceUniqueAdd(&integration.Spec.Dependencies, "runtime:"+o.Runtime)
+		integration.Spec.AddDependency("runtime:" + o.Runtime)
 	}
 
+	for _, item := range o.Dependencies {
+		integration.Spec.AddDependency(item)
+	}
 	for _, item := range o.Properties {
-		integration.Spec.Configuration = append(integration.Spec.Configuration, v1alpha1.ConfigurationSpec{
-			Type:  "property",
-			Value: item,
-		})
+		integration.Spec.AddConfiguration("property", item)
 	}
 	for _, item := range o.LoggingLevels {
-		integration.Spec.Configuration = append(integration.Spec.Configuration, v1alpha1.ConfigurationSpec{
-			Type:  "property",
-			Value: "logging.level." + item,
-		})
+		integration.Spec.AddConfiguration("property", "logging.level."+item)
 	}
 	for _, item := range o.ConfigMaps {
-		integration.Spec.Configuration = append(integration.Spec.Configuration, v1alpha1.ConfigurationSpec{
-			Type:  "configmap",
-			Value: item,
-		})
+		integration.Spec.AddConfiguration("configmap", item)
 	}
 	for _, item := range o.Secrets {
-		integration.Spec.Configuration = append(integration.Spec.Configuration, v1alpha1.ConfigurationSpec{
-			Type:  "secret",
-			Value: item,
-		})
+		integration.Spec.AddConfiguration("secret", item)
 	}
 
 	for _, traitConf := range o.Traits {
@@ -364,19 +337,16 @@ func (o *runCmdOptions) updateIntegrationCode(sources []string) (*v1alpha1.Integ
 	case "":
 		// continue..
 	case "yaml":
-		jsondata, err := toJSON(&integration)
+		data, err := kubernetes.ToYAML(&integration)
 		if err != nil {
 			return nil, err
 		}
-		yamldata, err := jsonToYaml(jsondata)
-		if err != nil {
-			return nil, err
-		}
-		fmt.Print(string(yamldata))
+
+		fmt.Print(string(data))
 		return nil, nil
 
 	case "json":
-		data, err := toJSON(&integration)
+		data, err := kubernetes.ToJSON(&integration)
 		if err != nil {
 			return nil, err
 		}
@@ -412,31 +382,6 @@ func (o *runCmdOptions) updateIntegrationCode(sources []string) (*v1alpha1.Integ
 	return &integration, nil
 }
 
-func toJSON(value runtime.Object) ([]byte, error) {
-	u, err := k8sutil.UnstructuredFromRuntimeObject(value)
-	if err != nil {
-		return nil, fmt.Errorf("error creating unstructured data: %v", err)
-	}
-	data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, u)
-	if err != nil {
-		return nil, fmt.Errorf("error marshalling to json: %v", err)
-	}
-	return data, nil
-}
-
-func jsonToYaml(src []byte) ([]byte, error) {
-	jsondata := map[string]interface{}{}
-	err := json.Unmarshal(src, &jsondata)
-	if err != nil {
-		return nil, fmt.Errorf("error unmarshalling json: %v", err)
-	}
-	yamldata, err := yaml.Marshal(&jsondata)
-	if err != nil {
-		return nil, fmt.Errorf("error marshalling to yaml: %v", err)
-	}
-	return yamldata, nil
-}
-
 func (*runCmdOptions) loadCode(fileName string) (string, error) {
 	if !strings.HasPrefix(fileName, "http://") && !strings.HasPrefix(fileName, "https://") {
 		content, err := ioutil.ReadFile(fileName)
diff --git a/pkg/gzip/compress.go b/pkg/gzip/compress.go
index c2720039..584f9453 100644
--- a/pkg/gzip/compress.go
+++ b/pkg/gzip/compress.go
@@ -45,6 +45,9 @@ func Compress(buffer io.Writer, data []byte) error {
 func Uncompress(buffer io.Writer, data []byte) error {
 	b := bytes.NewBuffer(data)
 	gz, err := g.NewReader(b)
+	if err != nil {
+		return err
+	}
 
 	defer gz.Close()
 
@@ -53,7 +56,10 @@ func Uncompress(buffer io.Writer, data []byte) error {
 		return err
 	}
 
-	buffer.Write(data)
+	_, err = buffer.Write(data)
+	if err != nil {
+		return err
+	}
 
 	return nil
 }
diff --git a/pkg/metadata/http.go b/pkg/metadata/http.go
index 6ca70650..2b8d50aa 100644
--- a/pkg/metadata/http.go
+++ b/pkg/metadata/http.go
@@ -18,29 +18,30 @@ limitations under the License.
 package metadata
 
 import (
-	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"regexp"
 	"strings"
+
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 )
 
 var httpURIs = map[string]bool{
 	"ahc":                  true,
 	"ahc-ws":               true,
 	"atmosphere-websocket": true,
-	"cxf":         true,
-	"cxfrs":       true,
-	"grpc":        true,
-	"jetty":       true,
-	"netty-http":  true,
-	"netty4-http": true,
-	"rest":        true,
-	"restlet":     true,
-	"servlet":     true,
-	"spark-rest":  true,
-	"spring-ws":   true,
-	"undertow":    true,
-	"websocket":   true,
-	"knative":     true,
+	"cxf":                  true,
+	"cxfrs":                true,
+	"grpc":                 true,
+	"jetty":                true,
+	"netty-http":           true,
+	"netty4-http":          true,
+	"rest":                 true,
+	"restlet":              true,
+	"servlet":              true,
+	"spark-rest":           true,
+	"spring-ws":            true,
+	"undertow":             true,
+	"websocket":            true,
+	"knative":              true,
 }
 
 var passiveURIs = map[string]bool{
@@ -65,8 +66,8 @@ var passiveURIs = map[string]bool{
 	"vm":         true,
 }
 
-var restIndicator = regexp.MustCompile(".*rest\\s*\\([^)]*\\).*")
-var xmlRestIndicator = regexp.MustCompile(".*<\\s*rest\\s+[^>]*>.*")
+var restIndicator = regexp.MustCompile(`.*rest\s*\([^)]*\).*`)
+var xmlRestIndicator = regexp.MustCompile(`.*<\s*rest\s+[^>]*>.*`)
 
 // requiresHTTPService returns true if the integration needs to expose itself through HTTP
 func requiresHTTPService(source v1alpha1.SourceSpec, fromURIs []string) bool {
@@ -77,7 +78,7 @@ func requiresHTTPService(source v1alpha1.SourceSpec, fromURIs []string) bool {
 }
 
 // hasOnlyPassiveEndpoints returns true if the integration has no endpoint that needs to remain always active
-func hasOnlyPassiveEndpoints(source v1alpha1.SourceSpec, fromURIs []string) bool {
+func hasOnlyPassiveEndpoints(_ v1alpha1.SourceSpec, fromURIs []string) bool {
 	passivePlusHTTP := make(map[string]bool)
 	for k, v := range passiveURIs {
 		passivePlusHTTP[k] = v
diff --git a/pkg/metadata/metadata.go b/pkg/metadata/metadata.go
index 9eec7bdd..30263e93 100644
--- a/pkg/metadata/metadata.go
+++ b/pkg/metadata/metadata.go
@@ -18,8 +18,9 @@ limitations under the License.
 package metadata
 
 import (
-	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"sort"
+
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 )
 
 // ExtractAll returns metadata information from all listed source codes
diff --git a/pkg/metadata/metadata_http_test.go b/pkg/metadata/metadata_http_test.go
index d75f57b9..59cc1cd3 100644
--- a/pkg/metadata/metadata_http_test.go
+++ b/pkg/metadata/metadata_http_test.go
@@ -82,7 +82,6 @@ func TestHttpOnlyJavaSourceRest2(t *testing.T) {
 	assert.True(t, meta.PassiveEndpoints)
 }
 
-
 func TestNoHttpGroovySource(t *testing.T) {
 	code := v1alpha1.SourceSpec{
 		Name:     "Request.groovy",
@@ -143,8 +142,6 @@ func TestHttpOnlyXMLSource(t *testing.T) {
 	assert.True(t, meta.PassiveEndpoints)
 }
 
-
-
 func TestMultilangHTTPOnlySource(t *testing.T) {
 	codes := []v1alpha1.SourceSpec{
 		{
@@ -192,4 +189,4 @@ func TestMultilangHTTPSource(t *testing.T) {
 	meta := ExtractAll(codes)
 	assert.True(t, meta.RequiresHTTPService)
 	assert.False(t, meta.PassiveEndpoints)
-}
\ No newline at end of file
+}
diff --git a/pkg/metadata/types.go b/pkg/metadata/types.go
index 04ebe1c2..2874eafc 100644
--- a/pkg/metadata/types.go
+++ b/pkg/metadata/types.go
@@ -31,6 +31,7 @@ type IntegrationMetadata struct {
 	Language v1alpha1.Language
 	// RequiresHTTPService indicates if the integration needs to be invoked through HTTP
 	RequiresHTTPService bool
-	// PassiveEndpoints indicates that the integration contains only passive endpoints that are activated from external calls, including HTTP (useful to determine if the integration can scale to 0)
+	// PassiveEndpoints indicates that the integration contains only passive endpoints that are activated from
+	// external calls, including HTTP (useful to determine if the integration can scale to 0)
 	PassiveEndpoints bool
 }
diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go
index 2132a1b3..e4955102 100644
--- a/pkg/trait/builder.go
+++ b/pkg/trait/builder.go
@@ -32,25 +32,30 @@ type builderTrait struct {
 
 func newBuilderTrait() *builderTrait {
 	return &builderTrait{
-		BaseTrait: newBaseTrait("builder"),
+		BaseTrait: BaseTrait{
+			id: ID("builder"),
+		},
 	}
 }
 
-func (*builderTrait) appliesTo(e *Environment) bool {
-	if e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseBuilding {
-		return true
+func (t *builderTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
 	}
 
-	if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseBuildingImage &&
-		e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseReady {
-		return true
+	if e.IntegrationContextInPhase(v1alpha1.IntegrationContextPhaseBuilding) {
+		return true, nil
 	}
 
-	return false
+	if e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingImage) {
+		return true, nil
+	}
+
+	return false, nil
 }
 
-func (*builderTrait) apply(e *Environment) error {
-	if e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseBuilding {
+func (t *builderTrait) Apply(e *Environment) error {
+	if e.IntegrationContextInPhase(v1alpha1.IntegrationContextPhaseBuilding) {
 		if platform.SupportsS2iPublishStrategy(e.Platform) {
 			e.Steps = s2i.DefaultSteps
 		} else if platform.SupportsKanikoPublishStrategy(e.Platform) {
@@ -59,9 +64,7 @@ func (*builderTrait) apply(e *Environment) error {
 		}
 	}
 
-	if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseBuildingImage &&
-		e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseReady {
-
+	if e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingImage) {
 		if platform.SupportsS2iPublishStrategy(e.Platform) {
 			e.Steps = []builder.Step{
 				builder.NewStep("packager", builder.ApplicationPackagePhase, builder.StandardPackager),
diff --git a/pkg/trait/builder_test.go b/pkg/trait/builder_test.go
index cf6c064a..c4bb0ee0 100644
--- a/pkg/trait/builder_test.go
+++ b/pkg/trait/builder_test.go
@@ -44,7 +44,7 @@ func TestBuilderTraitNotAppliedBecauseOfNilContext(t *testing.T) {
 
 			assert.Nil(t, err)
 			assert.NotEmpty(t, e.ExecutedTraits)
-			assert.NotContains(t, e.ExecutedTraits, ID("builder"))
+			assert.Nil(t, e.GetTrait(ID("builder")))
 			assert.Empty(t, e.Steps)
 		})
 	}
@@ -65,7 +65,7 @@ func TestBuilderTraitNotAppliedBecauseOfNilPhase(t *testing.T) {
 
 			assert.Nil(t, err)
 			assert.NotEmpty(t, e.ExecutedTraits)
-			assert.NotContains(t, e.ExecutedTraits, ID("builder"))
+			assert.Nil(t, e.GetTrait(ID("builder")))
 			assert.Empty(t, e.Steps)
 		})
 	}
@@ -77,7 +77,7 @@ func TestS2IBuilderTrait(t *testing.T) {
 
 	assert.Nil(t, err)
 	assert.NotEmpty(t, env.ExecutedTraits)
-	assert.Contains(t, env.ExecutedTraits, ID("builder"))
+	assert.NotNil(t, env.GetTrait(ID("builder")))
 	assert.NotEmpty(t, env.Steps)
 	assert.Len(t, env.Steps, 5)
 	assert.Condition(t, func() bool {
@@ -97,7 +97,7 @@ func TestKanikoBuilderTrait(t *testing.T) {
 
 	assert.Nil(t, err)
 	assert.NotEmpty(t, env.ExecutedTraits)
-	assert.Contains(t, env.ExecutedTraits, ID("builder"))
+	assert.NotNil(t, env.GetTrait(ID("builder")))
 	assert.NotEmpty(t, env.Steps)
 	assert.Len(t, env.Steps, 5)
 	assert.Condition(t, func() bool {
@@ -137,7 +137,7 @@ func createBuilderTestEnv(cluster v1alpha1.IntegrationPlatformCluster, strategy
 			},
 		},
 		EnvVars:        make(map[string]string),
-		ExecutedTraits: make([]ID, 0),
+		ExecutedTraits: make([]Trait, 0),
 		Resources:      kubernetes.NewCollection(),
 	}
 }
diff --git a/pkg/trait/catalog.go b/pkg/trait/catalog.go
index 796670a4..ef0b9b39 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/catalog.go
@@ -39,7 +39,7 @@ type Catalog struct {
 	tOwner        Trait
 	tBuilder      Trait
 	tSpringBoot   Trait
-	tIstio	Trait
+	tIstio        Trait
 }
 
 // NewCatalog creates a new trait Catalog
@@ -55,7 +55,7 @@ func NewCatalog() *Catalog {
 		tOwner:        newOwnerTrait(),
 		tBuilder:      newBuilderTrait(),
 		tSpringBoot:   newSpringBootTrait(),
-		tIstio: newIstioTrait(),
+		tIstio:        newIstioTrait(),
 	}
 }
 
@@ -122,23 +122,20 @@ func (c *Catalog) apply(environment *Environment) error {
 	traits := c.traitsFor(environment)
 
 	for _, trait := range traits {
-		if !trait.appliesTo(environment) {
-			continue
+		enabled, err := trait.Configure(environment)
+		if err != nil {
+			return err
 		}
 
-		if trait.IsAuto() {
-			if err := trait.autoconfigure(environment); err != nil {
-				return err
-			}
-		}
+		if enabled {
+			logrus.Infof("Apply trait: %s", trait.ID())
 
-		if trait.IsEnabled() {
-			logrus.Infof("apply trait: %s", trait.ID())
-			if err := trait.apply(environment); err != nil {
+			err = trait.Apply(environment)
+			if err != nil {
 				return err
 			}
 
-			environment.ExecutedTraits = append(environment.ExecutedTraits, trait.ID())
+			environment.ExecutedTraits = append(environment.ExecutedTraits, trait)
 		}
 	}
 
diff --git a/pkg/trait/debug.go b/pkg/trait/debug.go
index e5e40d92..b78bcc6e 100644
--- a/pkg/trait/debug.go
+++ b/pkg/trait/debug.go
@@ -27,23 +27,21 @@ type debugTrait struct {
 
 func newDebugTrait() *debugTrait {
 	return &debugTrait{
-		BaseTrait: newBaseTrait("debug"),
+		BaseTrait: BaseTrait{
+			id: ID("debug"),
+		},
 	}
 }
 
-func (r *debugTrait) appliesTo(e *Environment) bool {
-	return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
-}
-
-func (r *debugTrait) autoconfigure(e *Environment) error {
-	if r.Enabled == nil {
-		enabled := false
-		r.Enabled = &enabled
+func (t *debugTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && *t.Enabled {
+		return e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying), nil
 	}
-	return nil
+
+	return false, nil
 }
 
-func (r *debugTrait) apply(e *Environment) error {
+func (t *debugTrait) Apply(e *Environment) error {
 	// this is all that's needed as long as the base image is `fabric8/s2i-java` look into builder/builder.go
 	e.EnvVars["JAVA_DEBUG"] = True
 
diff --git a/pkg/trait/debug_test.go b/pkg/trait/debug_test.go
index 39890095..6c24d82d 100644
--- a/pkg/trait/debug_test.go
+++ b/pkg/trait/debug_test.go
@@ -24,26 +24,57 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-var (
-	env = &Environment{
+func TestDebugTraitApplicability(t *testing.T) {
+	env := Environment{
 		Integration: &v1alpha1.Integration{
 			Status: v1alpha1.IntegrationStatus{
 				Phase: v1alpha1.IntegrationPhaseDeploying,
 			},
+			Spec: v1alpha1.IntegrationSpec{
+				Traits: map[string]v1alpha1.IntegrationTraitSpec{
+					"debug": {
+						Configuration: map[string]string{
+							"enabled": "true",
+						},
+					},
+				},
+			},
 		},
 		EnvVars: make(map[string]string)}
 
-	trait = newDebugTrait()
-)
+	trait := newDebugTrait()
 
-func TestApplicability(t *testing.T) {
-	assert.True(t, trait.appliesTo(env))
+	enabled, err := trait.Configure(&env)
+	assert.Nil(t, err)
+	assert.False(t, enabled)
 
 	env.Integration.Status.Phase = v1alpha1.IntegrationPhaseRunning
-	assert.False(t, trait.appliesTo(env))
+
+	enabled, err = trait.Configure(&env)
+	assert.Nil(t, err)
+	assert.False(t, enabled)
 }
 
-func TestApply(t *testing.T) {
-	assert.Nil(t, trait.apply(env))
+func TestApplyDebugTrait(t *testing.T) {
+	env := Environment{
+		Integration: &v1alpha1.Integration{
+			Status: v1alpha1.IntegrationStatus{
+				Phase: v1alpha1.IntegrationPhaseDeploying,
+			},
+			Spec: v1alpha1.IntegrationSpec{
+				Traits: map[string]v1alpha1.IntegrationTraitSpec{
+					"debug": {
+						Configuration: map[string]string{
+							"enabled": "true",
+						},
+					},
+				},
+			},
+		},
+		EnvVars: make(map[string]string)}
+
+	trait := newDebugTrait()
+
+	assert.Nil(t, trait.Apply(&env))
 	assert.Equal(t, True, env.EnvVars["JAVA_DEBUG"])
 }
diff --git a/pkg/trait/dependencies.go b/pkg/trait/dependencies.go
index b57d0971..ccbc41b5 100644
--- a/pkg/trait/dependencies.go
+++ b/pkg/trait/dependencies.go
@@ -31,15 +31,21 @@ type dependenciesTrait struct {
 
 func newDependenciesTrait() *dependenciesTrait {
 	return &dependenciesTrait{
-		BaseTrait: newBaseTrait("dependencies"),
+		BaseTrait: BaseTrait{
+			id: ID("dependencies"),
+		},
 	}
 }
 
-func (*dependenciesTrait) appliesTo(e *Environment) bool {
-	return e.Integration != nil && e.Integration.Status.Phase == ""
+func (t *dependenciesTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
+	}
+
+	return e.IntegrationInPhase(""), nil
 }
 
-func (*dependenciesTrait) apply(e *Environment) error {
+func (t *dependenciesTrait) Apply(e *Environment) error {
 	for _, s := range e.Integration.Spec.Sources {
 		meta := metadata.Extract(s)
 
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index 801f8498..7e8d01fe 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -37,43 +37,49 @@ type deploymentTrait struct {
 
 func newDeploymentTrait() *deploymentTrait {
 	return &deploymentTrait{
-		BaseTrait: newBaseTrait("deployment"),
+		BaseTrait: BaseTrait{
+			id: ID("deployment"),
+		},
 	}
 }
 
-func (d *deploymentTrait) appliesTo(e *Environment) bool {
-	if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying {
+func (t *deploymentTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
+	}
+
+	if e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying) {
 		//
 		// Don't deploy on knative
 		//
-		return e.DetermineProfile() != v1alpha1.TraitProfileKnative
+		return e.DetermineProfile() != v1alpha1.TraitProfileKnative, nil
 	}
 
-	if d.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
-		return true
+	if t.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
+		return true, nil
 	}
 
-	if !d.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
-		return true
+	if !t.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
+		return true, nil
 	}
 
-	return false
+	return false, nil
 }
 
-func (d *deploymentTrait) apply(e *Environment) error {
-	if d.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
+func (t *deploymentTrait) Apply(e *Environment) error {
+	if t.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
 		// trigger container image build
 		e.Integration.Status.Phase = v1alpha1.IntegrationPhaseBuildingImage
 	}
 
-	if !d.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
+	if !t.ContainerImage && e.InPhase(v1alpha1.IntegrationContextPhaseReady, v1alpha1.IntegrationPhaseBuildingContext) {
 		// trigger integration deploy
 		e.Integration.Status.Phase = v1alpha1.IntegrationPhaseDeploying
 	}
 
 	if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying {
-		e.Resources.AddAll(d.getConfigMapsFor(e))
-		e.Resources.Add(d.getDeploymentFor(e))
+		e.Resources.AddAll(t.getConfigMapsFor(e))
+		e.Resources.Add(t.getDeploymentFor(e))
 	}
 
 	return nil
@@ -85,7 +91,7 @@ func (d *deploymentTrait) apply(e *Environment) error {
 //
 // **********************************
 
-func (d *deploymentTrait) getConfigMapsFor(e *Environment) []runtime.Object {
+func (t *deploymentTrait) getConfigMapsFor(e *Environment) []runtime.Object {
 	maps := make([]runtime.Object, 0, len(e.Integration.Spec.Sources)+1)
 
 	// combine properties of integration with context, integration
@@ -112,7 +118,7 @@ func (d *deploymentTrait) getConfigMapsFor(e *Environment) []runtime.Object {
 		},
 	)
 
-	if !d.ContainerImage {
+	if !t.ContainerImage {
 
 		// do not create 'source' ConfigMap if a docker images for deployment
 		// is required
@@ -152,13 +158,13 @@ func (d *deploymentTrait) getConfigMapsFor(e *Environment) []runtime.Object {
 //
 // **********************************
 
-func (d *deploymentTrait) getSources(e *Environment) []string {
+func (t *deploymentTrait) getSources(e *Environment) []string {
 	sources := make([]string, 0, len(e.Integration.Spec.Sources))
 
 	for i, s := range e.Integration.Spec.Sources {
 		root := fmt.Sprintf("/etc/camel/integrations/%03d", i)
 
-		if d.ContainerImage {
+		if t.ContainerImage {
 
 			// assume sources are copied over the standard deployments folder
 			root = "/deployments/sources"
@@ -185,8 +191,8 @@ func (d *deploymentTrait) getSources(e *Environment) []string {
 	return sources
 }
 
-func (d *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment {
-	sources := d.getSources(e)
+func (t *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment {
+	sources := t.getSources(e)
 
 	// combine Environment of integration with context, integration
 	// Environment has the priority
@@ -288,7 +294,7 @@ func (d *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment {
 	// Volumes :: Sources
 	//
 
-	if !d.ContainerImage {
+	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
diff --git a/pkg/trait/ingress.go b/pkg/trait/ingress.go
index 1688e232..f8bd7ec5 100644
--- a/pkg/trait/ingress.go
+++ b/pkg/trait/ingress.go
@@ -30,43 +30,58 @@ import (
 type ingressTrait struct {
 	BaseTrait `property:",squash"`
 	Host      string `property:"host"`
+	Auto      *bool  `property:"auto"`
 }
 
 func newIngressTrait() *ingressTrait {
 	return &ingressTrait{
-		BaseTrait: newBaseTrait("ingress"),
-		Host:      "",
+		BaseTrait: BaseTrait{
+			id: ID("ingress"),
+		},
+		Host: "",
 	}
 }
 
-func (*ingressTrait) appliesTo(e *Environment) bool {
-	return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
-}
+func (t *ingressTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
+	}
 
-func (i *ingressTrait) autoconfigure(e *Environment) error {
-	if i.Enabled == nil {
-		hasService := i.getTargetService(e) != nil
-		hasHost := i.Host != ""
+	if !e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying) {
+		return false, nil
+	}
+
+	if t.Auto == nil || *t.Auto {
+		hasService := t.getTargetService(e) != nil
+		hasHost := t.Host != ""
 		enabled := hasService && hasHost
-		i.Enabled = &enabled
+
+		if !enabled {
+			return false, nil
+		}
 	}
-	return nil
+
+	if t.Host == "" {
+		return false, errors.New("cannot Apply ingress trait: no host defined")
+	}
+
+	return true, nil
 }
 
-func (i *ingressTrait) apply(e *Environment) error {
-	if i.Host == "" {
-		return errors.New("cannot apply ingress trait: no host defined")
+func (t *ingressTrait) Apply(e *Environment) error {
+	if t.Host == "" {
+		return errors.New("cannot Apply ingress trait: no host defined")
 	}
-	service := i.getTargetService(e)
+	service := t.getTargetService(e)
 	if service == nil {
-		return errors.New("cannot apply ingress trait: no target service")
+		return errors.New("cannot Apply ingress trait: no target service")
 	}
 
-	e.Resources.Add(i.getIngressFor(service))
+	e.Resources.Add(t.getIngressFor(service))
 	return nil
 }
 
-func (*ingressTrait) getTargetService(e *Environment) (service *corev1.Service) {
+func (t *ingressTrait) getTargetService(e *Environment) (service *corev1.Service) {
 	e.Resources.VisitService(func(s *corev1.Service) {
 		if s.ObjectMeta.Labels != nil {
 			if intName, ok := s.ObjectMeta.Labels["camel.apache.org/integration"]; ok && intName == e.Integration.Name {
@@ -77,7 +92,7 @@ func (*ingressTrait) getTargetService(e *Environment) (service *corev1.Service)
 	return
 }
 
-func (i *ingressTrait) getIngressFor(service *corev1.Service) *v1beta1.Ingress {
+func (t *ingressTrait) getIngressFor(service *corev1.Service) *v1beta1.Ingress {
 	ingress := v1beta1.Ingress{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "Ingress",
@@ -94,7 +109,7 @@ func (i *ingressTrait) getIngressFor(service *corev1.Service) *v1beta1.Ingress {
 			},
 			Rules: []v1beta1.IngressRule{
 				{
-					Host: i.Host,
+					Host: t.Host,
 				},
 			},
 		},
diff --git a/pkg/trait/istio.go b/pkg/trait/istio.go
index 765cbe87..20732204 100644
--- a/pkg/trait/istio.go
+++ b/pkg/trait/istio.go
@@ -34,16 +34,22 @@ const (
 
 func newIstioTrait() *istioTrait {
 	return &istioTrait{
-		BaseTrait: newBaseTrait("istio"),
-		Allow:     "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16",
+		BaseTrait: BaseTrait{
+			id: ID("istio"),
+		},
+		Allow: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16",
 	}
 }
 
-func (t *istioTrait) appliesTo(e *Environment) bool {
-	return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
+func (t *istioTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
+	}
+
+	return e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying), nil
 }
 
-func (t *istioTrait) apply(e *Environment) error {
+func (t *istioTrait) Apply(e *Environment) error {
 	if t.Allow != "" {
 		e.Resources.VisitDeployment(func(d *appsv1.Deployment) {
 			d.Spec.Template.Annotations = t.injectIstioAnnotation(d.Spec.Template.Annotations)
diff --git a/pkg/trait/knative.go b/pkg/trait/knative.go
index eecec1b6..a40abb35 100644
--- a/pkg/trait/knative.go
+++ b/pkg/trait/knative.go
@@ -48,50 +48,59 @@ type knativeTrait struct {
 	Sinks     string `property:"sinks"`
 	MinScale  *int   `property:"minScale"`
 	MaxScale  *int   `property:"maxScale"`
+	Auto      *bool  `property:"auto"`
 }
 
 func newKnativeTrait() *knativeTrait {
 	return &knativeTrait{
-		BaseTrait: newBaseTrait("knative"),
+		BaseTrait: BaseTrait{
+			id: ID("knative"),
+		},
 	}
 }
 
-func (t *knativeTrait) appliesTo(e *Environment) bool {
-	return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
-}
-
-func (t *knativeTrait) autoconfigure(e *Environment) error {
-	if t.Sources == "" {
-		channels := t.getSourceChannels(e)
-		t.Sources = strings.Join(channels, ",")
+func (t *knativeTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
 	}
-	if t.Sinks == "" {
-		channels := t.getSinkChannels(e)
-		t.Sinks = strings.Join(channels, ",")
+
+	if !e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying) {
+		return false, nil
 	}
-	// Check the right value for minScale, as not all services are allowed to scale down to 0
-	if t.MinScale == nil {
-		meta := metadata.ExtractAll(e.Integration.Spec.Sources)
-		if !meta.RequiresHTTPService || !meta.PassiveEndpoints {
-			single := 1
-			t.MinScale = &single
+
+	if t.Auto == nil || *t.Auto {
+		if t.Sources == "" {
+			channels := t.getSourceChannels(e)
+			t.Sources = strings.Join(channels, ",")
+		}
+		if t.Sinks == "" {
+			channels := t.getSinkChannels(e)
+			t.Sinks = strings.Join(channels, ",")
+		}
+		// Check the right value for minScale, as not all services are allowed to scale down to 0
+		if t.MinScale == nil {
+			meta := metadata.ExtractAll(e.Integration.Spec.Sources)
+			if !meta.RequiresHTTPService || !meta.PassiveEndpoints {
+				single := 1
+				t.MinScale = &single
+			}
 		}
 	}
-	return nil
+
+	return true, nil
 }
 
-func (t *knativeTrait) apply(e *Environment) error {
+func (t *knativeTrait) Apply(e *Environment) error {
 	if err := t.prepareEnvVars(e); err != nil {
 		return err
 	}
 	for _, sub := range t.getSubscriptionsFor(e) {
 		e.Resources.Add(sub)
 	}
-	svc, err := t.getServiceFor(e)
-	if err != nil {
-		return err
-	}
+
+	svc := t.getServiceFor(e)
 	e.Resources.Add(svc)
+
 	return nil
 }
 
@@ -105,7 +114,7 @@ func (t *knativeTrait) prepareEnvVars(e *Environment) error {
 	return nil
 }
 
-func (t *knativeTrait) getServiceFor(e *Environment) (*serving.Service, error) {
+func (t *knativeTrait) getServiceFor(e *Environment) *serving.Service {
 	// combine properties of integration with context, integration
 	// properties have the priority
 	properties := CombineConfigurationAsMap("property", e.Context, e.Integration)
@@ -198,7 +207,7 @@ func (t *knativeTrait) getServiceFor(e *Environment) (*serving.Service, error) {
 		},
 	}
 
-	return &svc, nil
+	return &svc
 }
 
 func (t *knativeTrait) getSubscriptionsFor(e *Environment) []*eventing.Subscription {
diff --git a/pkg/trait/knative_test.go b/pkg/trait/knative_test.go
index 325e0f98..f701c88f 100644
--- a/pkg/trait/knative_test.go
+++ b/pkg/trait/knative_test.go
@@ -64,7 +64,7 @@ func TestKnativeTraitWithCompressedSources(t *testing.T) {
 			},
 		},
 		EnvVars:        make(map[string]string),
-		ExecutedTraits: make([]ID, 0),
+		ExecutedTraits: make([]Trait, 0),
 		Resources:      kubernetes.NewCollection(),
 	}
 
@@ -72,7 +72,7 @@ func TestKnativeTraitWithCompressedSources(t *testing.T) {
 
 	assert.Nil(t, err)
 	assert.NotEmpty(t, env.ExecutedTraits)
-	assert.Contains(t, env.ExecutedTraits, ID("knative"))
+	assert.NotNil(t, env.GetTrait(ID("knative")))
 	assert.NotNil(t, env.EnvVars["KAMEL_KNATIVE_CONFIGURATION"])
 
 	services := 0
diff --git a/pkg/trait/owner.go b/pkg/trait/owner.go
index 51e491ab..26a7906c 100644
--- a/pkg/trait/owner.go
+++ b/pkg/trait/owner.go
@@ -29,15 +29,21 @@ type ownerTrait struct {
 
 func newOwnerTrait() *ownerTrait {
 	return &ownerTrait{
-		BaseTrait: newBaseTrait("owner"),
+		BaseTrait: BaseTrait{
+			id: ID("owner"),
+		},
 	}
 }
 
-func (t *ownerTrait) appliesTo(e *Environment) bool {
-	return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
+func (t *ownerTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
+	}
+
+	return e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying), nil
 }
 
-func (*ownerTrait) apply(e *Environment) error {
+func (*ownerTrait) Apply(e *Environment) error {
 	controller := true
 	blockOwnerDeletion := true
 	e.Resources.VisitMetaObject(func(res metav1.Object) {
diff --git a/pkg/trait/route.go b/pkg/trait/route.go
index e65b0cb9..1e6746c9 100644
--- a/pkg/trait/route.go
+++ b/pkg/trait/route.go
@@ -30,42 +30,52 @@ import (
 
 type routeTrait struct {
 	BaseTrait `property:",squash"`
+	Auto      *bool  `property:"auto"`
 	Host      string `property:"host"`
+	service   *corev1.Service
 }
 
 func newRouteTrait() *routeTrait {
 	return &routeTrait{
-		BaseTrait: newBaseTrait("route"),
+		BaseTrait: BaseTrait{
+			id: ID("route"),
+		},
 	}
 }
 
-func (r *routeTrait) appliesTo(e *Environment) bool {
-	return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
-}
+func (t *routeTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
+	}
 
-func (r *routeTrait) autoconfigure(e *Environment) error {
-	if r.Enabled == nil {
-		hasService := r.getTargetService(e) != nil
-		r.Enabled = &hasService
+	if !e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying) {
+		return false, nil
 	}
-	return nil
+
+	if t.Auto == nil || *t.Auto {
+		t.service = t.getTargetService(e)
+		if t.service == nil {
+			return false, nil
+		}
+	}
+
+	if t.service == nil {
+		return false, errors.New("cannot apply route trait: no target service")
+	}
+
+	return true, nil
 }
 
-func (r *routeTrait) apply(e *Environment) error {
+func (t *routeTrait) Apply(e *Environment) error {
 	if e.Integration == nil || e.Integration.Status.Phase != v1alpha1.IntegrationPhaseDeploying {
 		return nil
 	}
 
-	service := r.getTargetService(e)
-	if service == nil {
-		return errors.New("cannot apply route trait: no target service")
-	}
-
-	e.Resources.Add(r.getRouteFor(service))
+	e.Resources.Add(t.getRouteFor(t.service))
 	return nil
 }
 
-func (*routeTrait) getTargetService(e *Environment) (service *corev1.Service) {
+func (t *routeTrait) getTargetService(e *Environment) (service *corev1.Service) {
 	e.Resources.VisitService(func(s *corev1.Service) {
 		if s.ObjectMeta.Labels != nil {
 			if intName, ok := s.ObjectMeta.Labels["camel.apache.org/integration"]; ok && intName == e.Integration.Name {
@@ -76,7 +86,7 @@ func (*routeTrait) getTargetService(e *Environment) (service *corev1.Service) {
 	return
 }
 
-func (r *routeTrait) getRouteFor(service *corev1.Service) *routev1.Route {
+func (t *routeTrait) getRouteFor(service *corev1.Service) *routev1.Route {
 	route := routev1.Route{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "Route",
@@ -94,7 +104,7 @@ func (r *routeTrait) getRouteFor(service *corev1.Service) *routev1.Route {
 				Kind: "Service",
 				Name: service.Name,
 			},
-			Host: r.Host,
+			Host: t.Host,
 		},
 	}
 	return &route
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index 0efaf45c..007c0fe2 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -28,36 +28,45 @@ import (
 type serviceTrait struct {
 	BaseTrait `property:",squash"`
 
-	Port int `property:"port"`
+	Auto *bool `property:"auto"`
+	Port int   `property:"port"`
 }
 
 func newServiceTrait() *serviceTrait {
 	return &serviceTrait{
-		BaseTrait: newBaseTrait("service"),
-		Port:      8080,
+		BaseTrait: BaseTrait{
+			id: ID("service"),
+		},
+		Port: 8080,
 	}
 }
 
-func (s *serviceTrait) appliesTo(e *Environment) bool {
-	return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
-}
+func (t *serviceTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
+	}
 
-func (s *serviceTrait) autoconfigure(e *Environment) error {
-	if s.Enabled == nil {
+	if !e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying) {
+		return false, nil
+	}
+
+	if t.Auto == nil || *t.Auto {
 		meta := metadata.ExtractAll(e.Integration.Spec.Sources)
-		required := meta.RequiresHTTPService
-		s.Enabled = &required
+		if !meta.RequiresHTTPService {
+			return false, nil
+		}
 	}
-	return nil
+
+	return true, nil
 }
 
-func (s *serviceTrait) apply(e *Environment) (err error) {
-	svc := s.getServiceFor(e)
+func (t *serviceTrait) Apply(e *Environment) (err error) {
+	svc := t.getServiceFor(e)
 	e.Resources.Add(svc)
 	return nil
 }
 
-func (s *serviceTrait) getServiceFor(e *Environment) *corev1.Service {
+func (t *serviceTrait) getServiceFor(e *Environment) *corev1.Service {
 	svc := corev1.Service{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "Service",
@@ -76,7 +85,7 @@ func (s *serviceTrait) getServiceFor(e *Environment) *corev1.Service {
 					Name:       "http",
 					Port:       80,
 					Protocol:   corev1.ProtocolTCP,
-					TargetPort: intstr.FromInt(s.Port),
+					TargetPort: intstr.FromInt(t.Port),
 				},
 			},
 			Selector: map[string]string{
diff --git a/pkg/trait/springboot.go b/pkg/trait/springboot.go
index 1314bfbb..522a526a 100644
--- a/pkg/trait/springboot.go
+++ b/pkg/trait/springboot.go
@@ -36,38 +36,31 @@ type springBootTrait struct {
 
 func newSpringBootTrait() *springBootTrait {
 	return &springBootTrait{
-		BaseTrait: newBaseTrait("springboot"),
+		BaseTrait: BaseTrait{
+			id: ID("springboot"),
+		},
 	}
 }
 
-// IsAuto determines if we should apply automatic configuration
-func (trait *springBootTrait) IsAuto() bool {
-	return false
-}
-
-// IsEnabled is used to determine if the trait needs to be executed
-func (trait *springBootTrait) IsEnabled() bool {
-	if trait.Enabled == nil {
-		return false
+func (t *springBootTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled == nil || !*t.Enabled {
+		return false, nil
 	}
-	return *trait.Enabled
-}
 
-func (trait *springBootTrait) appliesTo(e *Environment) bool {
-	if e.Context != nil && e.Context.Status.Phase == v1alpha1.IntegrationContextPhaseBuilding {
-		return true
+	if e.IntegrationContextInPhase(v1alpha1.IntegrationContextPhaseBuilding) {
+		return true, nil
 	}
-	if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying {
-		return true
+	if e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying) {
+		return true, nil
 	}
-	if e.Integration != nil && e.Integration.Status.Phase == "" {
-		return true
+	if e.IntegrationInPhase("") {
+		return true, nil
 	}
 
-	return false
+	return false, nil
 }
 
-func (trait *springBootTrait) apply(e *Environment) error {
+func (t *springBootTrait) Apply(e *Environment) error {
 
 	//
 	// Integration
diff --git a/pkg/trait/trait.go b/pkg/trait/trait.go
index 592796d8..6684319f 100644
--- a/pkg/trait/trait.go
+++ b/pkg/trait/trait.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pkg/errors"
 )
 
+// True --
 const True = "true"
 
 // Apply --
@@ -72,7 +73,7 @@ func newEnvironment(integration *v1alpha1.Integration, ctx *v1alpha1.Integration
 		Platform:       pl,
 		Context:        ctx,
 		Integration:    integration,
-		ExecutedTraits: make([]ID, 0),
+		ExecutedTraits: make([]Trait, 0),
 		Resources:      kubernetes.NewCollection(),
 		EnvVars:        make(map[string]string),
 	}, nil
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index a770e762..b48bd374 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -37,11 +37,12 @@ const (
 func TestOpenShiftTraits(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "camel:core")
 	res := processTestEnv(t, env)
+
 	assert.NotEmpty(t, env.ExecutedTraits)
-	assert.Contains(t, env.ExecutedTraits, ID("deployment"))
-	assert.NotContains(t, env.ExecutedTraits, ID("service"))
-	assert.NotContains(t, env.ExecutedTraits, ID("route"))
-	assert.Contains(t, env.ExecutedTraits, ID("owner"))
+	assert.NotNil(t, env.GetTrait(ID("deployment")))
+	assert.Nil(t, env.GetTrait(ID("service")))
+	assert.Nil(t, env.GetTrait(ID("route")))
+	assert.NotNil(t, env.GetTrait(ID("owner")))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == TestProperties
 	}))
@@ -53,10 +54,10 @@ func TestOpenShiftTraits(t *testing.T) {
 func TestOpenShiftTraitsWithWeb(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "from('undertow:http').to('log:info')")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedTraits, ID("deployment"))
-	assert.Contains(t, env.ExecutedTraits, ID("service"))
-	assert.Contains(t, env.ExecutedTraits, ID("route"))
-	assert.Contains(t, env.ExecutedTraits, ID("owner"))
+	assert.NotNil(t, env.GetTrait(ID("deployment")))
+	assert.NotNil(t, env.GetTrait(ID("service")))
+	assert.NotNil(t, env.GetTrait(ID("route")))
+	assert.NotNil(t, env.GetTrait(ID("owner")))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == TestProperties
 	}))
@@ -80,8 +81,8 @@ func TestOpenShiftTraitsWithWebAndConfig(t *testing.T) {
 		},
 	}
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedTraits, ID("service"))
-	assert.Contains(t, env.ExecutedTraits, ID("route"))
+	assert.NotNil(t, env.GetTrait(ID("service")))
+	assert.NotNil(t, env.GetTrait(ID("route")))
 	assert.NotNil(t, res.GetService(func(svc *corev1.Service) bool {
 		return svc.Name == TestDeployment && svc.Spec.Ports[0].TargetPort.IntVal == int32(7071)
 	}))
@@ -97,8 +98,8 @@ func TestOpenShiftTraitsWithWebAndDisabledTrait(t *testing.T) {
 		},
 	}
 	res := processTestEnv(t, env)
-	assert.NotContains(t, env.ExecutedTraits, ID("service"))
-	assert.NotContains(t, env.ExecutedTraits, ID("route")) // No route without service
+	assert.Nil(t, env.GetTrait(ID("service")))
+	assert.Nil(t, env.GetTrait(ID("route"))) // No route without service
 	assert.Nil(t, res.GetService(func(svc *corev1.Service) bool {
 		return true
 	}))
@@ -107,10 +108,10 @@ func TestOpenShiftTraitsWithWebAndDisabledTrait(t *testing.T) {
 func TestKubernetesTraits(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, "from('timer:tick').to('log:info')")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedTraits, ID("deployment"))
-	assert.NotContains(t, env.ExecutedTraits, ID("service"))
-	assert.NotContains(t, env.ExecutedTraits, ID("route"))
-	assert.Contains(t, env.ExecutedTraits, ID("owner"))
+	assert.NotNil(t, env.GetTrait(ID("deployment")))
+	assert.Nil(t, env.GetTrait(ID("service")))
+	assert.Nil(t, env.GetTrait(ID("route")))
+	assert.NotNil(t, env.GetTrait(ID("owner")))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == TestProperties
 	}))
@@ -122,10 +123,10 @@ func TestKubernetesTraits(t *testing.T) {
 func TestKubernetesTraitsWithWeb(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, "from('servlet:http').to('log:info')")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedTraits, ID("deployment"))
-	assert.Contains(t, env.ExecutedTraits, ID("service"))
-	assert.NotContains(t, env.ExecutedTraits, ID("route"))
-	assert.Contains(t, env.ExecutedTraits, ID("owner"))
+	assert.NotNil(t, env.GetTrait(ID("deployment")))
+	assert.NotNil(t, env.GetTrait(ID("service")))
+	assert.Nil(t, env.GetTrait(ID("route")))
+	assert.NotNil(t, env.GetTrait(ID("owner")))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == TestProperties
 	}))
@@ -154,7 +155,8 @@ func TestTraitDecode(t *testing.T) {
 
 	assert.Nil(t, err)
 	assert.Equal(t, 7071, svc.Port)
-	assert.Equal(t, false, svc.IsEnabled())
+	assert.NotNil(t, svc.Enabled)
+	assert.Equal(t, false, *svc.Enabled)
 }
 
 func processTestEnv(t *testing.T, env *Environment) *kubernetes.Collection {
@@ -174,9 +176,9 @@ func createTestEnv(cluster v1alpha1.IntegrationPlatformCluster, script string) *
 			Spec: v1alpha1.IntegrationSpec{
 				Sources: []v1alpha1.SourceSpec{
 					{
+						Name:     "file.groovy",
 						Language: v1alpha1.LanguageGroovy,
-						Name: "file.groovy",
-						Content: script,
+						Content:  script,
 					},
 				},
 			},
@@ -191,7 +193,7 @@ func createTestEnv(cluster v1alpha1.IntegrationPlatformCluster, script string) *
 			},
 		},
 		EnvVars:        make(map[string]string),
-		ExecutedTraits: make([]ID, 0),
+		ExecutedTraits: make([]Trait, 0),
 		Resources:      kubernetes.NewCollection(),
 	}
 }
diff --git a/pkg/trait/types.go b/pkg/trait/types.go
index d61f352d..a81f2f40 100644
--- a/pkg/trait/types.go
+++ b/pkg/trait/types.go
@@ -35,16 +35,12 @@ type ID string
 // Trait is the interface of all traits
 type Trait interface {
 	Identifiable
-	// IsEnabled tells if the trait is enabled
-	IsEnabled() bool
-	// IsAuto determine if the trait should be configured automatically
-	IsAuto() bool
-	// appliesTo tells if the trait supports the given environment
-	appliesTo(environment *Environment) bool
-	// autoconfigure is called before any customization to ensure the trait is fully configured
-	autoconfigure(environment *Environment) error
-	// apply executes a customization of the Environment
-	apply(environment *Environment) error
+
+	// Configure the trait
+	Configure(environment *Environment) (bool, error)
+
+	// Apply executes a customization of the Environment
+	Apply(environment *Environment) error
 }
 
 /* Base trait */
@@ -53,13 +49,6 @@ type Trait interface {
 type BaseTrait struct {
 	id      ID
 	Enabled *bool `property:"enabled"`
-	Auto    *bool `property:"auto"`
-}
-
-func newBaseTrait(id string) BaseTrait {
-	return BaseTrait{
-		id: ID(id),
-	}
 }
 
 // ID returns the identifier of the trait
@@ -67,26 +56,6 @@ func (trait *BaseTrait) ID() ID {
 	return trait.id
 }
 
-// IsAuto determines if we should apply automatic configuration
-func (trait *BaseTrait) IsAuto() bool {
-	if trait.Auto == nil {
-		return true
-	}
-	return *trait.Auto
-}
-
-// IsEnabled is used to determine if the trait needs to be executed
-func (trait *BaseTrait) IsEnabled() bool {
-	if trait.Enabled == nil {
-		return true
-	}
-	return *trait.Enabled
-}
-
-func (trait *BaseTrait) autoconfigure(environment *Environment) error {
-	return nil
-}
-
 /* Environment */
 
 // A Environment provides the context where the trait is executed
@@ -97,10 +66,21 @@ type Environment struct {
 	Resources      *kubernetes.Collection
 	Steps          []builder.Step
 	BuildDir       string
-	ExecutedTraits []ID
+	ExecutedTraits []Trait
 	EnvVars        map[string]string
 }
 
+// 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
diff --git a/pkg/util/kubernetes/replace.go b/pkg/util/kubernetes/replace.go
index ec14b162..6d8ea490 100644
--- a/pkg/util/kubernetes/replace.go
+++ b/pkg/util/kubernetes/replace.go
@@ -18,6 +18,7 @@ limitations under the License.
 package kubernetes
 
 import (
+	eventing "github.com/knative/eventing/pkg/apis/eventing/v1alpha1"
 	routev1 "github.com/openshift/api/route/v1"
 	"github.com/operator-framework/operator-sdk/pkg/sdk"
 	"github.com/pkg/errors"
@@ -25,7 +26,6 @@ import (
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
-	eventing "github.com/knative/eventing/pkg/apis/eventing/v1alpha1"
 )
 
 // ReplaceResources allows to completely replace a list of resources on Kubernetes, taking care of immutable fields and resource versions
diff --git a/pkg/util/kubernetes/util.go b/pkg/util/kubernetes/util.go
new file mode 100644
index 00000000..ae109080
--- /dev/null
+++ b/pkg/util/kubernetes/util.go
@@ -0,0 +1,66 @@
+/*
+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 (
+	"encoding/json"
+	"fmt"
+
+	"github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
+	yaml "gopkg.in/yaml.v2"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+// ToJSON --
+func ToJSON(value runtime.Object) ([]byte, error) {
+	u, err := k8sutil.UnstructuredFromRuntimeObject(value)
+	if err != nil {
+		return nil, fmt.Errorf("error creating unstructured data: %v", err)
+	}
+	data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, u)
+	if err != nil {
+		return nil, fmt.Errorf("error marshalling to json: %v", err)
+	}
+	return data, nil
+}
+
+// ToYAML --
+func ToYAML(value runtime.Object) ([]byte, error) {
+	data, err := ToJSON(value)
+	if err != nil {
+		return nil, err
+	}
+
+	return JSONToYAML(data)
+}
+
+// JSONToYAML --
+func JSONToYAML(src []byte) ([]byte, error) {
+	jsondata := map[string]interface{}{}
+	err := json.Unmarshal(src, &jsondata)
+	if err != nil {
+		return nil, fmt.Errorf("error unmarshalling json: %v", err)
+	}
+	yamldata, err := yaml.Marshal(&jsondata)
+	if err != nil {
+		return nil, fmt.Errorf("error marshalling to yaml: %v", err)
+	}
+
+	return yamldata, nil
+}


 

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


With regards,
Apache Git Services