This is an automated email from the ASF dual-hosted git repository. lburgazzoli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit bacb33c132bd09ea0154633894c942a51d27e927 Author: nferraro <ni.ferr...@gmail.com> AuthorDate: Fri Oct 26 00:04:53 2018 +0200 Refactoring traits package --- docs/traits.adoc | 6 +- pkg/client/cmd/completion_bash.go | 2 +- pkg/client/cmd/run.go | 3 +- pkg/{discover => metadata}/dependencies.go | 62 ++---- pkg/{discover => metadata}/doc.go | 5 +- .../language.go => metadata/languages.go} | 7 +- .../languages_test.go => metadata/metadata.go} | 30 ++- .../metadata_dependencies_test.go} | 22 +-- .../metadata_languages_test.go} | 10 +- pkg/metadata/metadata_uri_test.go | 215 +++++++++++++++++++++ pkg/{discover/doc.go => metadata/types.go} | 18 +- pkg/metadata/uris.go | 100 ++++++++++ pkg/stub/action/integration/initialize.go | 11 +- pkg/trait/catalog.go | 139 +++++++++---- pkg/trait/{base.go => deployment.go} | 18 +- pkg/trait/owner.go | 12 +- pkg/trait/route.go | 38 ++-- pkg/trait/service.go | 36 ++-- pkg/trait/trait.go | 13 +- pkg/trait/trait_test.go | 56 +++--- pkg/trait/types.go | 113 +++++------ pkg/trait/util.go | 31 --- 22 files changed, 627 insertions(+), 320 deletions(-) diff --git a/docs/traits.adoc b/docs/traits.adoc index c5ada79..efe2aa1 100644 --- a/docs/traits.adoc +++ b/docs/traits.adoc @@ -23,6 +23,10 @@ The flag `--trait` can be also abbreviated with `-t`. The `enabled` property is available on all traits and can be used to enable/disable them. All traits have their own internal logic to determine if they need to be enabled when the user does not activate them explicitly. +All traits share also a `auto` property that can be used to enable/disable auto-configuration of the trait based on the +environment. The auto-configuration mechanism is able to enable/disable the trait when the `enabled` property is not explicitly +set by the user and also change the trait configuration. The `auto` property is enabled by default. + NOTE: Some traits are applicable only to specific platforms (see "profiles" in the table). A trait may have additional properties that can be configured by the end user. @@ -83,6 +87,6 @@ There are also platform traits that **normally should not be configured** by the [options="header",cols="m,,"] |======================= | Trait | Profiles | Description -| base | Kubernetes, OpenShift | Creates the basic Kubernetes resource needed for running the integration. +| deployment | Kubernetes, OpenShift | Creates the basic Kubernetes resource needed for running the integration. | owner | Kubernetes, OpenShift | Makes sure that every resource created by the traits belongs to the integration custom resource (so they are deleted when the integration is deleted). |======================= diff --git a/pkg/client/cmd/completion_bash.go b/pkg/client/cmd/completion_bash.go index df76a1e..ab390fe 100644 --- a/pkg/client/cmd/completion_bash.go +++ b/pkg/client/cmd/completion_bash.go @@ -69,7 +69,7 @@ __kamel_dependency_type() { } __kamel_traits() { - local type_list="` + strings.Join(trait.ComputeTraitsProperties(), " ") + `" + local type_list="` + strings.Join(trait.NewCatalog().ComputeTraitsProperties(), " ") + `" COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") ) compopt -o nospace } diff --git a/pkg/client/cmd/run.go b/pkg/client/cmd/run.go index 0778b07..3c59c84 100644 --- a/pkg/client/cmd/run.go +++ b/pkg/client/cmd/run.go @@ -127,7 +127,8 @@ func (o *runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error { } func (o *runCmdOptions) run(cmd *cobra.Command, args []string) error { - tp := trait.ComputeTraitsProperties() + catalog := trait.NewCatalog() + tp := catalog.ComputeTraitsProperties() for _, t := range o.Traits { kv := strings.SplitN(t, "=", 2) diff --git a/pkg/discover/dependencies.go b/pkg/metadata/dependencies.go similarity index 50% rename from pkg/discover/dependencies.go rename to pkg/metadata/dependencies.go index 8e402b2..d207228 100644 --- a/pkg/discover/dependencies.go +++ b/pkg/metadata/dependencies.go @@ -15,10 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -package discover +package metadata import ( - "regexp" "sort" "strings" @@ -26,23 +25,14 @@ import ( "github.com/apache/camel-k/pkg/util/camel" ) -var ( - singleQuotedURI *regexp.Regexp - doubleQuotedURI *regexp.Regexp -) - -func init() { - singleQuotedURI = regexp.MustCompile("'([a-z0-9-]+):[^']+'") - doubleQuotedURI = regexp.MustCompile("\"([a-z0-9-]+):[^\"]+\"") -} - -// Dependencies returns a list of dependencies required by the given source code -func Dependencies(source v1alpha1.SourceSpec) []string { +// discoverDependencies returns a list of dependencies required by the given source code +func discoverDependencies(source v1alpha1.SourceSpec, fromURIs []string, toURIs []string) []string { candidateMap := make(map[string]bool) - regexps := getRegexpsForLanguage(source.Language) - subMatches := findAllStringSubmatch(source.Content, regexps...) - for _, uriPrefix := range subMatches { - candidateComp := decodeComponent(uriPrefix) + uris := make([]string, 0, len(fromURIs)+len(toURIs)) + uris = append(uris, fromURIs...) + uris = append(uris, toURIs...) + for _, uri := range uris { + candidateComp := decodeComponent(uri) if candidateComp != "" { candidateMap[candidateComp] = true } @@ -56,38 +46,12 @@ func Dependencies(source v1alpha1.SourceSpec) []string { return candidateComponents } -func getRegexpsForLanguage(language v1alpha1.Language) []*regexp.Regexp { - switch language { - case v1alpha1.LanguageJavaSource: - return []*regexp.Regexp{doubleQuotedURI} - case v1alpha1.LanguageXML: - return []*regexp.Regexp{doubleQuotedURI} - case v1alpha1.LanguageGroovy: - return []*regexp.Regexp{singleQuotedURI, doubleQuotedURI} - case v1alpha1.LanguageJavaScript: - return []*regexp.Regexp{singleQuotedURI, doubleQuotedURI} - case v1alpha1.LanguageKotlin: - return []*regexp.Regexp{doubleQuotedURI} - } - return []*regexp.Regexp{} -} - -func findAllStringSubmatch(data string, regexps ...*regexp.Regexp) []string { - candidates := make([]string, 0) - for _, reg := range regexps { - hits := reg.FindAllStringSubmatch(data, -1) - for _, hit := range hits { - if hit != nil && len(hit) > 1 { - for _, match := range hit[1:] { - candidates = append(candidates, match) - } - } - } +func decodeComponent(uri string) string { + uriSplit := strings.SplitN(uri, ":", 2) + if len(uriSplit) < 2 { + return "" } - return candidates -} - -func decodeComponent(uriStart string) string { + uriStart := uriSplit[0] if component := camel.Runtime.GetArtifactByScheme(uriStart); component != nil { artifactID := component.ArtifactID if strings.HasPrefix(artifactID, "camel-") { diff --git a/pkg/discover/doc.go b/pkg/metadata/doc.go similarity index 86% copy from pkg/discover/doc.go copy to pkg/metadata/doc.go index 51cc065..e1b5958 100644 --- a/pkg/discover/doc.go +++ b/pkg/metadata/doc.go @@ -15,6 +15,5 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package discover contains functions for extracting -// information from user code before compilation -package discover +// Package metadata contains tools to discover metadata from Camel routes +package metadata diff --git a/pkg/discover/language.go b/pkg/metadata/languages.go similarity index 84% rename from pkg/discover/language.go rename to pkg/metadata/languages.go index cafb6c3..2d0d2da 100644 --- a/pkg/discover/language.go +++ b/pkg/metadata/languages.go @@ -15,8 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package discover contains functions for analyzing user code -package discover +package metadata import ( "strings" @@ -24,8 +23,8 @@ import ( "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" ) -// Language discovers the code language from file extension if not set -func Language(source v1alpha1.SourceSpec) v1alpha1.Language { +// discoverLanguage discovers the code language from file extension if not set +func discoverLanguage(source v1alpha1.SourceSpec) v1alpha1.Language { if source.Language != "" { return source.Language } diff --git a/pkg/discover/languages_test.go b/pkg/metadata/metadata.go similarity index 63% copy from pkg/discover/languages_test.go copy to pkg/metadata/metadata.go index bbe0c45..46a7aef 100644 --- a/pkg/discover/languages_test.go +++ b/pkg/metadata/metadata.go @@ -15,28 +15,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -package discover +package metadata import ( - "testing" - "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" - "github.com/stretchr/testify/assert" ) -func TestLanguageJavaSource(t *testing.T) { - code := v1alpha1.SourceSpec{ - Name: "Request.java", - } - language := Language(code) - assert.Equal(t, v1alpha1.LanguageJavaSource, language) -} - -func TestLanguageAlreadySet(t *testing.T) { - code := v1alpha1.SourceSpec{ - Name: "Request.java", - Language: v1alpha1.LanguageJavaScript, +// Extract returns metadata information from the source code +func Extract(source v1alpha1.SourceSpec) IntegrationMetadata { + language := discoverLanguage(source) + fromURIs := discoverFromURIs(source, language) + toURIs := discoverToURIs(source, language) + dependencies := discoverDependencies(source, fromURIs, toURIs) + return IntegrationMetadata{ + Language: language, + FromURIs: fromURIs, + ToURIs: toURIs, + Dependencies: dependencies, } - language := Language(code) - assert.Equal(t, v1alpha1.LanguageJavaScript, language) } diff --git a/pkg/discover/dependencies_test.go b/pkg/metadata/metadata_dependencies_test.go similarity index 90% rename from pkg/discover/dependencies_test.go rename to pkg/metadata/metadata_dependencies_test.go index a7934ef..8eea487 100644 --- a/pkg/discover/dependencies_test.go +++ b/pkg/metadata/metadata_dependencies_test.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package discover +package metadata import ( "testing" @@ -34,9 +34,9 @@ func TestDependenciesJavaSource(t *testing.T) { from("ine:xistent").to("amqp:queue"); `, } - dependencies := Dependencies(code) + meta := Extract(code) // assert all dependencies are found and sorted (removing duplicates) - assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, dependencies) + assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, meta.Dependencies) } func TestDependenciesJavaClass(t *testing.T) { @@ -49,8 +49,8 @@ func TestDependenciesJavaClass(t *testing.T) { from("ine:xistent").to("amqp:queue"); `, } - dependencies := Dependencies(code) - assert.Empty(t, dependencies) + meta := Extract(code) + assert.Empty(t, meta.Dependencies) } func TestDependenciesJavaScript(t *testing.T) { @@ -64,9 +64,9 @@ func TestDependenciesJavaScript(t *testing.T) { '"' `, } - dependencies := Dependencies(code) + meta := Extract(code) // assert all dependencies are found and sorted (removing duplicates) - assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, dependencies) + assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, meta.Dependencies) } func TestDependenciesGroovy(t *testing.T) { @@ -80,9 +80,9 @@ func TestDependenciesGroovy(t *testing.T) { '"' `, } - dependencies := Dependencies(code) + meta := Extract(code) // assert all dependencies are found and sorted (removing duplicates) - assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, dependencies) + assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, meta.Dependencies) } func TestDependencies(t *testing.T) { @@ -95,7 +95,7 @@ func TestDependencies(t *testing.T) { from("twitter-timeline:test").to("mock:end"); `, } - dependencies := Dependencies(code) + meta := Extract(code) // assert all dependencies are found and sorted (removing duplicates) - assert.Equal(t, []string{"camel:core", "camel:http4", "camel:twitter"}, dependencies) + assert.Equal(t, []string{"camel:core", "camel:http4", "camel:twitter"}, meta.Dependencies) } diff --git a/pkg/discover/languages_test.go b/pkg/metadata/metadata_languages_test.go similarity index 85% rename from pkg/discover/languages_test.go rename to pkg/metadata/metadata_languages_test.go index bbe0c45..5382d38 100644 --- a/pkg/discover/languages_test.go +++ b/pkg/metadata/metadata_languages_test.go @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package discover +package metadata import ( "testing" @@ -28,8 +28,8 @@ func TestLanguageJavaSource(t *testing.T) { code := v1alpha1.SourceSpec{ Name: "Request.java", } - language := Language(code) - assert.Equal(t, v1alpha1.LanguageJavaSource, language) + meta := Extract(code) + assert.Equal(t, v1alpha1.LanguageJavaSource, meta.Language) } func TestLanguageAlreadySet(t *testing.T) { @@ -37,6 +37,6 @@ func TestLanguageAlreadySet(t *testing.T) { Name: "Request.java", Language: v1alpha1.LanguageJavaScript, } - language := Language(code) - assert.Equal(t, v1alpha1.LanguageJavaScript, language) + meta := Extract(code) + assert.Equal(t, v1alpha1.LanguageJavaScript, meta.Language) } diff --git a/pkg/metadata/metadata_uri_test.go b/pkg/metadata/metadata_uri_test.go new file mode 100644 index 0000000..84da92a --- /dev/null +++ b/pkg/metadata/metadata_uri_test.go @@ -0,0 +1,215 @@ +/* +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 metadata + +import ( + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestJava1(t *testing.T) { + source := v1alpha1.SourceSpec{ + Name: "test", + Language: v1alpha1.LanguageJavaSource, + Content: ` + import org.apache.camel.builder.RouteBuilder; + + public class Sample extends RouteBuilder { + @Override + public void configure() throws Exception { + from("timer:tick") + .setBody(constant("-\n r\n o\n c\nHello! Camel K\n s\n !\n")) + .to("log:info?skipBodyLineSeparator=false"); + } + } + `, + } + + metadata := Extract(source) + assert.Contains(t, metadata.FromURIs, "timer:tick") + assert.Len(t, metadata.FromURIs, 1) + assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false") + assert.Len(t, metadata.ToURIs, 1) +} + +func TestJava2(t *testing.T) { + source := v1alpha1.SourceSpec{ + Name: "test", + Language: v1alpha1.LanguageJavaSource, + Content: ` + import org.apache.camel.builder.RouteBuilder; + + public class Sample extends RouteBuilder { + @Override + public void configure() throws Exception { + from("timer:tick") + .setBody(constant("!\n")) + .to ( + + "log:info?skipBodyLineSeparator=false" + + ) + .toD("uri:2") + .toF("uri:%s", "3"); + } + } + `, + } + + metadata := Extract(source) + assert.Contains(t, metadata.FromURIs, "timer:tick") + assert.Len(t, metadata.FromURIs, 1) + assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false") + assert.Contains(t, metadata.ToURIs, "uri:2") + assert.Contains(t, metadata.ToURIs, "uri:%s") // resolution not supported yet + assert.Len(t, metadata.ToURIs, 3) +} + +func TestGroovy1(t *testing.T) { + source := v1alpha1.SourceSpec{ + Name: "test", + Language: v1alpha1.LanguageGroovy, + Content: ` + + from( "timer:tick") + .setBody().constant("aa") + .to ('log:info?skipBodyLineSeparator=false').to( + 'http://url' ) + + from("uri:2") + .setBody().constant("aa") + .to('uri:3') + `, + } + + metadata := Extract(source) + assert.Contains(t, metadata.FromURIs, "timer:tick") + assert.Contains(t, metadata.FromURIs, "uri:2") + assert.Len(t, metadata.FromURIs, 2) + assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false") + assert.Contains(t, metadata.ToURIs, "http://url") + assert.Contains(t, metadata.ToURIs, "uri:3") + assert.Len(t, metadata.ToURIs, 3) +} + +func TestGroovy2(t *testing.T) { + source := v1alpha1.SourceSpec{ + Name: "test", + Language: v1alpha1.LanguageGroovy, + Content: ` + + rest().get("/") + .to ('log:info?skipBodyLineSeparator=false').to( 'http://url' ) + .toD('dyn:1') + .tony('thisisnot:anuri') + .toD( "dyn:2") + .toF( "f:%s", "2") + `, + } + + metadata := Extract(source) + assert.Empty(t, metadata.FromURIs) + assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false") + assert.Contains(t, metadata.ToURIs, "http://url") + assert.Contains(t, metadata.ToURIs, "dyn:1") + assert.Contains(t, metadata.ToURIs, "dyn:2") + assert.Contains(t, metadata.ToURIs, "f:%s") // resolution not supported yet + assert.Len(t, metadata.ToURIs, 5) +} + +func TestXml1(t *testing.T) { + source := v1alpha1.SourceSpec{ + Name: "test", + Language: v1alpha1.LanguageXML, + Content: ` + <routes> + <route id="hello"> + <from uri="timer:hello?period=3s"/> + <setBody> + <constant>Hello World!!!</constant> + </setBody> + <to uri="log:info"/> + <to uri="log:info2"/> + <toD uri="log:info3"/> + </route> + </routes> + `, + } + + metadata := Extract(source) + assert.Contains(t, metadata.FromURIs, "timer:hello?period=3s") + assert.Len(t, metadata.FromURIs, 1) + assert.Contains(t, metadata.ToURIs, "log:info") + assert.Contains(t, metadata.ToURIs, "log:info2") + assert.Contains(t, metadata.ToURIs, "log:info3") + assert.Len(t, metadata.ToURIs, 3) +} + +func TestKotlin1(t *testing.T) { + source := v1alpha1.SourceSpec{ + Name: "test", + Language: v1alpha1.LanguageKotlin, + Content: ` + + from( "timer:tick") + .setBody().constant("aa") + .to ("log:info?skipBodyLineSeparator=false").to( + "http://url" ) + + from("uri:2") + .setBody().constant("aa") + .to("uri:3") + .toD("uri:4") + .toF("uri:%s", 5) + `, + } + + metadata := Extract(source) + assert.Contains(t, metadata.FromURIs, "timer:tick") + assert.Contains(t, metadata.FromURIs, "uri:2") + assert.Len(t, metadata.FromURIs, 2) + assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false") + assert.Contains(t, metadata.ToURIs, "http://url") + assert.Contains(t, metadata.ToURIs, "uri:3") + assert.Contains(t, metadata.ToURIs, "uri:4") + assert.Contains(t, metadata.ToURIs, "uri:%s") // resolution not supported yet + assert.Len(t, metadata.ToURIs, 5) +} + +func TestJavascript1(t *testing.T) { + source := v1alpha1.SourceSpec{ + Name: "test", + Language: v1alpha1.LanguageJavaScript, + Content: ` + + rest().get("/") + .to ('log:info?skipBodyLineSeparator=false').to( 'http://url' ) + .toD("uri:2") + .toF("uri:%s", "3") + `, + } + + metadata := Extract(source) + assert.Empty(t, metadata.FromURIs) + assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false") + assert.Contains(t, metadata.ToURIs, "http://url") + assert.Contains(t, metadata.ToURIs, "uri:2") + assert.Contains(t, metadata.ToURIs, "uri:%s") // resolution not supported yet + assert.Len(t, metadata.ToURIs, 4) +} diff --git a/pkg/discover/doc.go b/pkg/metadata/types.go similarity index 61% rename from pkg/discover/doc.go rename to pkg/metadata/types.go index 51cc065..74ac309 100644 --- a/pkg/discover/doc.go +++ b/pkg/metadata/types.go @@ -15,6 +15,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package discover contains functions for extracting -// information from user code before compilation -package discover +package metadata + +import "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + +// IntegrationMetadata contains aggregate metadata about all Camel routes in a integrations +type IntegrationMetadata struct { + // All starting URIs of defined routes + FromURIs []string + // All end URIs of defined routes + ToURIs []string + // All inferred dependencies required to run the integration + Dependencies []string + // The language in which the integration is written + Language v1alpha1.Language +} diff --git a/pkg/metadata/uris.go b/pkg/metadata/uris.go new file mode 100644 index 0000000..e9c17ab --- /dev/null +++ b/pkg/metadata/uris.go @@ -0,0 +1,100 @@ +/* +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 metadata + +import ( + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "regexp" +) + +var ( + singleQuotedFrom = regexp.MustCompile("from\\s*\\(\\s*'([a-z0-9-]+:[^']+)'\\s*\\)") + doubleQuotedFrom = regexp.MustCompile("from\\s*\\(\\s*\"([a-z0-9-]+:[^\"]+)\"\\s*\\)") + singleQuotedTo = regexp.MustCompile("\\.to\\s*\\(\\s*'([a-z0-9-]+:[^']+)'\\s*\\)") + singleQuotedToD = regexp.MustCompile("\\.toD\\s*\\(\\s*'([a-z0-9-]+:[^']+)'\\s*\\)") + singleQuotedToF = regexp.MustCompile("\\.toF\\s*\\(\\s*'([a-z0-9-]+:[^']+)'[^)]*\\)") + doubleQuotedTo = regexp.MustCompile("\\.to\\s*\\(\\s*\"([a-z0-9-]+:[^\"]+)\"\\s*\\)") + doubleQuotedToD = regexp.MustCompile("\\.toD\\s*\\(\\s*\"([a-z0-9-]+:[^\"]+)\"\\s*\\)") + doubleQuotedToF = regexp.MustCompile("\\.toF\\s*\\(\\s*\"([a-z0-9-]+:[^\"]+)\"[^)]*\\)") + xmlTagFrom = regexp.MustCompile("<\\s*from\\s+[^>]*uri\\s*=\\s*\"([a-z0-9-]+:[^\"]+)\"[^>]*>") + xmlTagTo = regexp.MustCompile("<\\s*to\\s+[^>]*uri\\s*=\\s*\"([a-z0-9-]+:[^\"]+)\"[^>]*>") + xmlTagToD = regexp.MustCompile("<\\s*toD\\s+[^>]*uri\\s*=\\s*\"([a-z0-9-]+:[^\"]+)\"[^>]*>") +) + +// discoverFromURIs returns all uris used in a from clause +func discoverFromURIs(source v1alpha1.SourceSpec, language v1alpha1.Language) []string { + fromRegexps := getFromRegexpsForLanguage(language) + return findAllDistinctStringSubmatch(source.Content, fromRegexps...) +} + +// discoverToURIs returns all uris used in a to clause +func discoverToURIs(source v1alpha1.SourceSpec, language v1alpha1.Language) []string { + toRegexps := getToRegexpsForLanguage(language) + return findAllDistinctStringSubmatch(source.Content, toRegexps...) +} + +func getFromRegexpsForLanguage(language v1alpha1.Language) []*regexp.Regexp { + switch language { + case v1alpha1.LanguageJavaSource: + return []*regexp.Regexp{doubleQuotedFrom} + case v1alpha1.LanguageXML: + return []*regexp.Regexp{xmlTagFrom} + case v1alpha1.LanguageGroovy: + return []*regexp.Regexp{singleQuotedFrom, doubleQuotedFrom} + case v1alpha1.LanguageJavaScript: + return []*regexp.Regexp{singleQuotedFrom, doubleQuotedFrom} + case v1alpha1.LanguageKotlin: + return []*regexp.Regexp{doubleQuotedFrom} + } + return []*regexp.Regexp{} +} + +func getToRegexpsForLanguage(language v1alpha1.Language) []*regexp.Regexp { + switch language { + case v1alpha1.LanguageJavaSource: + return []*regexp.Regexp{doubleQuotedTo, doubleQuotedToD, doubleQuotedToF} + case v1alpha1.LanguageXML: + return []*regexp.Regexp{xmlTagTo, xmlTagToD} + case v1alpha1.LanguageGroovy: + return []*regexp.Regexp{singleQuotedTo, doubleQuotedTo, singleQuotedToD, doubleQuotedToD, singleQuotedToF, doubleQuotedToF} + case v1alpha1.LanguageJavaScript: + return []*regexp.Regexp{singleQuotedTo, doubleQuotedTo, singleQuotedToD, doubleQuotedToD, singleQuotedToF, doubleQuotedToF} + case v1alpha1.LanguageKotlin: + return []*regexp.Regexp{doubleQuotedTo, doubleQuotedToD, doubleQuotedToF} + } + return []*regexp.Regexp{} +} + +func findAllDistinctStringSubmatch(data string, regexps ...*regexp.Regexp) []string { + candidates := make([]string, 0) + alreadyFound := make(map[string]bool) + for _, reg := range regexps { + hits := reg.FindAllStringSubmatch(data, -1) + for _, hit := range hits { + if hit != nil && len(hit) > 1 { + for _, match := range hit[1:] { + if _, ok := alreadyFound[match]; !ok { + alreadyFound[match] = true + candidates = append(candidates, match) + } + } + } + } + } + return candidates +} \ No newline at end of file diff --git a/pkg/stub/action/integration/initialize.go b/pkg/stub/action/integration/initialize.go index f0b5e7f..3abf51f 100644 --- a/pkg/stub/action/integration/initialize.go +++ b/pkg/stub/action/integration/initialize.go @@ -18,6 +18,7 @@ limitations under the License. package integration import ( + "github.com/apache/camel-k/pkg/metadata" "github.com/apache/camel-k/pkg/platform" "github.com/sirupsen/logrus" "sort" @@ -25,7 +26,6 @@ import ( "github.com/apache/camel-k/pkg/util" "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" - "github.com/apache/camel-k/pkg/discover" "github.com/apache/camel-k/pkg/util/digest" "github.com/operator-framework/operator-sdk/pkg/sdk" ) @@ -62,9 +62,11 @@ func (action *initializeAction) Handle(integration *v1alpha1.Integration) error var defaultReplicas int32 = 1 target.Spec.Replicas = &defaultReplicas } + // extract metadata + meta := metadata.Extract(target.Spec.Source) + // set the correct language - language := discover.Language(target.Spec.Source) - target.Spec.Source.Language = language + target.Spec.Source.Language = meta.Language if !util.StringSliceExists(target.Spec.Dependencies, "camel:core") { target.Spec.Dependencies = append(target.Spec.Dependencies, "camel:core") @@ -76,8 +78,7 @@ func (action *initializeAction) Handle(integration *v1alpha1.Integration) error target.Spec.DependenciesAutoDiscovery = &autoDiscoveryDependencies } if *target.Spec.DependenciesAutoDiscovery { - discovered := discover.Dependencies(target.Spec.Source) - target.Spec.Dependencies = action.mergeDependencies(target.Spec.Dependencies, discovered) + target.Spec.Dependencies = action.mergeDependencies(target.Spec.Dependencies, meta.Dependencies) } // sort the dependencies to get always the same list if they don't change sort.Strings(target.Spec.Dependencies) diff --git a/pkg/trait/catalog.go b/pkg/trait/catalog.go index 4d94477..049b1a6 100644 --- a/pkg/trait/catalog.go +++ b/pkg/trait/catalog.go @@ -20,63 +20,126 @@ package trait import ( "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "github.com/apache/camel-k/pkg/util/kubernetes" + "github.com/fatih/structs" + "reflect" + "strings" ) -var ( - tBase = newBaseTrait() - tService = newServiceTrait() - tRoute = newRouteTrait() - tOwner = newOwnerTrait() -) +// Catalog collects all information about traits in one place +type Catalog struct { + tDeployment ITrait + tService ITrait + tRoute ITrait + tOwner ITrait +} + +// NewCatalog creates a new trait Catalog +func NewCatalog() *Catalog { + return &Catalog{ + tDeployment: newDeploymentTrait(), + tService: newServiceTrait(), + tRoute: newRouteTrait(), + tOwner: newOwnerTrait(), + } +} + +func (c *Catalog) allTraits() []ITrait { + return []ITrait{ + c.tDeployment, + c.tService, + c.tRoute, + c.tOwner, + } +} -// customizersFor returns a Catalog for the given integration details -func customizersFor(environment *environment) customizer { +func (c *Catalog) traitsFor(environment *environment) []ITrait { switch environment.Platform.Spec.Cluster { case v1alpha1.IntegrationPlatformClusterOpenShift: - return compose( - &tBase, - &tService, - &tRoute, - &tOwner, - ) + return []ITrait{ + c.tDeployment, + c.tService, + c.tRoute, + c.tOwner, + } case v1alpha1.IntegrationPlatformClusterKubernetes: - return compose( - &tBase, - &tService, - &tOwner, - ) + return []ITrait{ + c.tDeployment, + c.tService, + c.tOwner, + } // case Knative: ... } return nil } -func compose(traits ...customizer) customizer { - return &chainedCustomizer{ - customizers: traits, +func (c *Catalog) customize(environment *environment, resources *kubernetes.Collection) error { + c.configure(environment) + traits := c.traitsFor(environment) + for _, trait := range traits { + if trait.IsAuto() { + if err := trait.autoconfigure(environment, resources); err != nil { + return err + } + } + if trait.IsEnabled() { + if err := trait.customize(environment, resources); err != nil { + return err + } + environment.ExecutedTraits = append(environment.ExecutedTraits, trait.ID()) + } } + return nil } -// ------------------------------------------- +// GetTrait returns the trait with the given ID +func (c *Catalog) GetTrait(id string) ITrait { + for _, t := range c.allTraits() { + if t.ID() == ID(id) { + return t + } + } + return nil +} -type chainedCustomizer struct { - customizers []customizer +func (c *Catalog) configure(env *environment) { + if env.Integration == nil || env.Integration.Spec.Traits == nil { + return + } + for id, traitSpec := range env.Integration.Spec.Traits { + catTrait := c.GetTrait(id) + if catTrait != nil { + traitSpec.Decode(catTrait) + } + } } -func (c *chainedCustomizer) ID() ID { - return ID("") +// ComputeTraitsProperties returns all key/value configuration properties that can be used to configure traits +func (c *Catalog) ComputeTraitsProperties() []string { + results := make([]string, 0) + for _, trait := range c.allTraits() { + c.processFields(structs.Fields(trait), func(name string) { + results = append(results, string(trait.ID())+"."+name) + }) + } + + return results } -func (c *chainedCustomizer) customize(environment *environment, resources *kubernetes.Collection) (bool, error) { - atLeastOne := false - for _, custom := range c.customizers { - if environment.isEnabled(custom.ID()) || environment.isAutoDetectionMode(custom.ID()) { - if done, err := custom.customize(environment, resources); err != nil { - return false, err - } else if done && custom.ID() != "" { - environment.ExecutedCustomizers = append(environment.ExecutedCustomizers, custom.ID()) - atLeastOne = atLeastOne || done - } +func (c *Catalog) processFields(fields []*structs.Field, processor func(string)) { + for _, f := range fields { + if f.IsEmbedded() && f.IsExported() && f.Kind() == reflect.Struct { + c.processFields(f.Fields(), processor) + } + + if f.IsEmbedded() { + continue + } + + property := f.Tag("property") + + if property != "" { + items := strings.Split(property, ",") + processor(items[0]) } } - return atLeastOne, nil } diff --git a/pkg/trait/base.go b/pkg/trait/deployment.go similarity index 93% rename from pkg/trait/base.go rename to pkg/trait/deployment.go index f146ce0..4225b99 100644 --- a/pkg/trait/base.go +++ b/pkg/trait/deployment.go @@ -28,20 +28,20 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type baseTrait struct { - Trait +type deploymentTrait struct { + BaseTrait `property:",squash"` } -func newBaseTrait() baseTrait { - return baseTrait{ - Trait: NewTraitWithID("base"), +func newDeploymentTrait() *deploymentTrait { + return &deploymentTrait{ + BaseTrait: newBaseTrait("deployment"), } } -func (d *baseTrait) customize(environment *environment, resources *kubernetes.Collection) (bool, error) { +func (d *deploymentTrait) customize(environment *environment, resources *kubernetes.Collection) error { resources.Add(d.getConfigMapFor(environment)) resources.Add(d.getDeploymentFor(environment)) - return true, nil + return nil } // ********************************** @@ -50,7 +50,7 @@ func (d *baseTrait) customize(environment *environment, resources *kubernetes.Co // // ********************************** -func (*baseTrait) getConfigMapFor(e *environment) *corev1.ConfigMap { +func (*deploymentTrait) getConfigMapFor(e *environment) *corev1.ConfigMap { // combine properties of integration with context, integration // properties have the priority properties := CombineConfigurationAsMap("property", e.Context, e.Integration) @@ -86,7 +86,7 @@ func (*baseTrait) getConfigMapFor(e *environment) *corev1.ConfigMap { // // ********************************** -func (*baseTrait) getDeploymentFor(e *environment) *appsv1.Deployment { +func (*deploymentTrait) getDeploymentFor(e *environment) *appsv1.Deployment { sourceName := strings.TrimPrefix(e.Integration.Spec.Source.Name, "/") // combine environment of integration with context, integration diff --git a/pkg/trait/owner.go b/pkg/trait/owner.go index a47f3c9..701b219 100644 --- a/pkg/trait/owner.go +++ b/pkg/trait/owner.go @@ -24,16 +24,16 @@ import ( // ownerTrait ensures that all created resources belong to the integration being created type ownerTrait struct { - Trait + BaseTrait `property:",squash"` } -func newOwnerTrait() ownerTrait { - return ownerTrait{ - Trait: NewTraitWithID("owner"), +func newOwnerTrait() *ownerTrait { + return &ownerTrait{ + BaseTrait: newBaseTrait("owner"), } } -func (*ownerTrait) customize(e *environment, resources *kubernetes.Collection) (bool, error) { +func (*ownerTrait) customize(e *environment, resources *kubernetes.Collection) error { controller := true blockOwnerDeletion := true resources.VisitMetaObject(func(res metav1.Object) { @@ -49,5 +49,5 @@ func (*ownerTrait) customize(e *environment, resources *kubernetes.Collection) ( } res.SetOwnerReferences(references) }) - return true, nil + return nil } diff --git a/pkg/trait/route.go b/pkg/trait/route.go index 725b1a9..1553bae 100644 --- a/pkg/trait/route.go +++ b/pkg/trait/route.go @@ -26,31 +26,41 @@ import ( ) type routeTrait struct { - Trait + BaseTrait `property:",squash"` } -func newRouteTrait() routeTrait { - return routeTrait{ - Trait: NewTraitWithID("route"), +func newRouteTrait() *routeTrait { + return &routeTrait{ + BaseTrait: newBaseTrait("route"), } } -func (e *routeTrait) customize(environment *environment, resources *kubernetes.Collection) (bool, error) { - var service *corev1.Service +func (e *routeTrait) autoconfigure(environment *environment, resources *kubernetes.Collection) error { + if e.Enabled == nil { + hasService := e.getTargetService(environment, resources) != nil + e.Enabled = &hasService + } + return nil +} + +func (e *routeTrait) customize(environment *environment, resources *kubernetes.Collection) error { + service := e.getTargetService(environment, resources) + if service != nil { + resources.Add(e.getRouteFor(environment, service)) + } + + return nil +} + +func (*routeTrait) getTargetService(e *environment, resources *kubernetes.Collection) (service *corev1.Service) { resources.VisitService(func(s *corev1.Service) { if s.ObjectMeta.Labels != nil { - if intName, ok := s.ObjectMeta.Labels["camel.apache.org/integration"]; ok && intName == environment.Integration.Name { + if intName, ok := s.ObjectMeta.Labels["camel.apache.org/integration"]; ok && intName == e.Integration.Name { service = s } } }) - - if service != nil { - resources.Add(e.getRouteFor(environment, service)) - return true, nil - } - - return false, nil + return } func (*routeTrait) getRouteFor(e *environment, service *corev1.Service) *routev1.Route { diff --git a/pkg/trait/service.go b/pkg/trait/service.go index 2afe06c..619226b 100644 --- a/pkg/trait/service.go +++ b/pkg/trait/service.go @@ -35,36 +35,36 @@ var webComponents = map[string]bool{ } type serviceTrait struct { - Trait + BaseTrait `property:",squash"` Port int `property:"port"` } -func newServiceTrait() serviceTrait { - return serviceTrait{ - Trait: NewTraitWithID("service"), - Port: 8080, +func newServiceTrait() *serviceTrait { + return &serviceTrait{ + BaseTrait: newBaseTrait("service"), + Port: 8080, } } -func (s *serviceTrait) customize(environment *environment, resources *kubernetes.Collection) (bool, error) { - if environment.isAutoDetectionMode(s.ID()) && !s.requiresService(environment) { - return false, nil +func (s *serviceTrait) autoconfigure(environment *environment, resources *kubernetes.Collection) error { + if s.Enabled == nil { + required := s.requiresService(environment) + s.Enabled = &required } - svc, err := s.getServiceFor(environment) - if err != nil { - return false, err + return nil +} + +func (s *serviceTrait) customize(environment *environment, resources *kubernetes.Collection) (err error) { + var svc *corev1.Service + if svc, err = s.getServiceFor(environment); err != nil { + return err } resources.Add(svc) - return true, nil + return nil } func (s *serviceTrait) getServiceFor(e *environment) (*corev1.Service, error) { - t := newServiceTrait() - if _, err := e.getTrait(s.ID(), &t); err != nil { - return nil, err - } - svc := corev1.Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", @@ -83,7 +83,7 @@ func (s *serviceTrait) getServiceFor(e *environment) (*corev1.Service, error) { Name: "http", Port: 80, Protocol: corev1.ProtocolTCP, - TargetPort: intstr.FromInt(t.Port), + TargetPort: intstr.FromInt(s.Port), }, }, Selector: map[string]string{ diff --git a/pkg/trait/trait.go b/pkg/trait/trait.go index 8b317ab..bdb5b0e 100644 --- a/pkg/trait/trait.go +++ b/pkg/trait/trait.go @@ -32,9 +32,9 @@ func ComputeDeployment(integration *v1alpha1.Integration) ([]runtime.Object, err return nil, err } resources := kubernetes.NewCollection() - customizers := customizersFor(environment) + catalog := NewCatalog() // invoke the trait framework to determine the needed resources - if _, err = customizers.customize(environment, resources); err != nil { + if err := catalog.customize(environment, resources); err != nil { return nil, errors.Wrap(err, "error during trait customization") } return resources.Items(), nil @@ -52,9 +52,10 @@ func newEnvironment(integration *v1alpha1.Integration) (*environment, error) { } return &environment{ - Platform: pl, - Context: ctx, - Integration: integration, - ExecutedCustomizers: make([]ID, 0), + Platform: pl, + Context: ctx, + Integration: integration, + ExecutedTraits: make([]ID, 0), }, nil } + diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go index f447f84..6631c8c 100644 --- a/pkg/trait/trait_test.go +++ b/pkg/trait/trait_test.go @@ -32,10 +32,11 @@ import ( func TestOpenShiftTraits(t *testing.T) { env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "camel:core") res := processTestEnv(t, env) - assert.Contains(t, env.ExecutedCustomizers, ID("base")) - assert.NotContains(t, env.ExecutedCustomizers, ID("service")) - assert.NotContains(t, env.ExecutedCustomizers, ID("route")) - assert.Contains(t, env.ExecutedCustomizers, ID("owner")) + 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, res.GetConfigMap(func(cm *corev1.ConfigMap) bool { return cm.Name == "test" })) @@ -47,10 +48,10 @@ func TestOpenShiftTraits(t *testing.T) { func TestOpenShiftTraitsWithWeb(t *testing.T) { env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "camel:core", "camel:undertow") res := processTestEnv(t, env) - assert.Contains(t, env.ExecutedCustomizers, ID("base")) - assert.Contains(t, env.ExecutedCustomizers, ID("service")) - assert.Contains(t, env.ExecutedCustomizers, ID("route")) - assert.Contains(t, env.ExecutedCustomizers, ID("owner")) + 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, res.GetConfigMap(func(cm *corev1.ConfigMap) bool { return cm.Name == "test" })) @@ -74,8 +75,8 @@ func TestOpenShiftTraitsWithWebAndConfig(t *testing.T) { }, } res := processTestEnv(t, env) - assert.Contains(t, env.ExecutedCustomizers, ID("service")) - assert.Contains(t, env.ExecutedCustomizers, ID("route")) + assert.Contains(t, env.ExecutedTraits, ID("service")) + assert.Contains(t, env.ExecutedTraits, ID("route")) assert.NotNil(t, res.GetService(func(svc *corev1.Service) bool { return svc.Name == "test" && svc.Spec.Ports[0].TargetPort.IntVal == int32(7071) })) @@ -91,8 +92,8 @@ func TestOpenShiftTraitsWithWebAndDisabledTrait(t *testing.T) { }, } res := processTestEnv(t, env) - assert.NotContains(t, env.ExecutedCustomizers, ID("service")) - assert.NotContains(t, env.ExecutedCustomizers, ID("route")) // No route without service + assert.NotContains(t, env.ExecutedTraits, ID("service")) + assert.NotContains(t, env.ExecutedTraits, ID("route")) // No route without service assert.Nil(t, res.GetService(func(svc *corev1.Service) bool { return true })) @@ -101,10 +102,10 @@ func TestOpenShiftTraitsWithWebAndDisabledTrait(t *testing.T) { func TestKubernetesTraits(t *testing.T) { env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, "camel:core") res := processTestEnv(t, env) - assert.Contains(t, env.ExecutedCustomizers, ID("base")) - assert.NotContains(t, env.ExecutedCustomizers, ID("service")) - assert.NotContains(t, env.ExecutedCustomizers, ID("route")) - assert.Contains(t, env.ExecutedCustomizers, ID("owner")) + 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, res.GetConfigMap(func(cm *corev1.ConfigMap) bool { return cm.Name == "test" })) @@ -116,10 +117,10 @@ func TestKubernetesTraits(t *testing.T) { func TestKubernetesTraitsWithWeb(t *testing.T) { env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, "camel:core", "camel:servlet") res := processTestEnv(t, env) - assert.Contains(t, env.ExecutedCustomizers, ID("base")) - assert.Contains(t, env.ExecutedCustomizers, ID("service")) - assert.NotContains(t, env.ExecutedCustomizers, ID("route")) - assert.Contains(t, env.ExecutedCustomizers, ID("owner")) + 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, res.GetConfigMap(func(cm *corev1.ConfigMap) bool { return cm.Name == "test" })) @@ -134,26 +135,27 @@ func TestKubernetesTraitsWithWeb(t *testing.T) { func TestTraitDecode(t *testing.T) { env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift) env.Integration.Spec.Traits = make(map[string]v1alpha1.IntegrationTraitSpec) - env.Integration.Spec.Traits["service"] = v1alpha1.IntegrationTraitSpec{ + svcTrait := v1alpha1.IntegrationTraitSpec{ Configuration: map[string]string{ "enabled": "false", "port": "7071", + "cippa": "lippa", }, } + env.Integration.Spec.Traits["service"] = svcTrait svc := newServiceTrait() - ok, err := env.getTrait(ID("service"), &svc) + err := svcTrait.Decode(svc) assert.Nil(t, err) - assert.True(t, ok) assert.Equal(t, 7071, svc.Port) - assert.Equal(t, true, svc.Enabled) + assert.Equal(t, false, svc.IsEnabled()) } func processTestEnv(t *testing.T, env *environment) *kubernetes.Collection { resources := kubernetes.NewCollection() - customizers := customizersFor(env) - _, err := customizers.customize(env, resources) + catalog := NewCatalog() + err := catalog.customize(env, resources) assert.Nil(t, err) return resources } @@ -175,6 +177,6 @@ func createTestEnv(cluster v1alpha1.IntegrationPlatformCluster, dependencies ... Cluster: cluster, }, }, - ExecutedCustomizers: make([]ID, 0), + ExecutedTraits: make([]ID, 0), } } diff --git a/pkg/trait/types.go b/pkg/trait/types.go index a825c6f..23aa6a2 100644 --- a/pkg/trait/types.go +++ b/pkg/trait/types.go @@ -18,14 +18,8 @@ limitations under the License. package trait import ( - "fmt" - "reflect" - - "github.com/sirupsen/logrus" - "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "github.com/apache/camel-k/pkg/util/kubernetes" - "github.com/pkg/errors" ) // Identifiable represent an identifiable type @@ -36,90 +30,69 @@ type Identifiable interface { // ID uniquely identifies a trait type ID string -// Trait -- -type Trait struct { +// ITrait TODO rename +type ITrait interface { Identifiable - - id ID - Enabled bool `property:"enabled"` + // enabled tells if the trait is enabled + IsEnabled() bool + // auto determine if the trait should be configured automatically + IsAuto() bool + // autoconfigure is called before any customization to ensure the trait is fully configured + autoconfigure(environment *environment, resources *kubernetes.Collection) error + // customize executes the trait customization on the resources and return true if the resources have been changed + customize(environment *environment, resources *kubernetes.Collection) error } -// ID returns the trait ID -func (trait *Trait) ID() ID { - return trait.id -} +/* Base trait */ -// NewTrait creates a new trait with defaults -func NewTrait() Trait { - return Trait{ - Enabled: true, - } +// BaseTrait is the root trait with noop implementations for hooks +type BaseTrait struct { + id ID + Enabled *bool `property:"enabled"` + Auto *bool `property:"auto"` } -// NewTraitWithID creates a new trait with defaults and given ID -func NewTraitWithID(traitID ID) Trait { - return Trait{ - id: traitID, - Enabled: true, +func newBaseTrait(id string) BaseTrait { + return BaseTrait{ + id: ID(id), } } -// A Customizer performs customization of the deployed objects -type customizer interface { - Identifiable - // Customize executes the trait customization on the resources and return true if the resources have been changed - customize(environment *environment, resources *kubernetes.Collection) (bool, error) +// ID returns the identifier of the trait +func (trait *BaseTrait) ID() ID { + return trait.id } -// A environment provides the context where the trait is executed -type environment struct { - Platform *v1alpha1.IntegrationPlatform - Context *v1alpha1.IntegrationContext - Integration *v1alpha1.Integration - ExecutedCustomizers []ID +// IsAuto determines if we should apply automatic configuration +func (trait *BaseTrait) IsAuto() bool { + if trait.Auto == nil { + return true + } + return *trait.Auto } -func (e *environment) getTrait(traitID ID, target interface{}) (bool, error) { - if spec := e.getTraitSpec(traitID); spec != nil { - err := spec.Decode(&target) - if err != nil { - return false, errors.Wrap(err, fmt.Sprintf("unable to convert trait %s to the target struct %s", traitID, reflect.TypeOf(target).Name())) - } - - return true, nil +// IsEnabled is used to determine if the trait needs to be executed +func (trait *BaseTrait) IsEnabled() bool { + if trait.Enabled == nil { + return true } - - return false, nil + return *trait.Enabled } -func (e *environment) getTraitSpec(traitID ID) *v1alpha1.IntegrationTraitSpec { - if e.Integration.Spec.Traits == nil { - return nil - } - if conf, ok := e.Integration.Spec.Traits[string(traitID)]; ok { - return &conf - } +func (trait *BaseTrait) autoconfigure(environment *environment, resources *kubernetes.Collection) error { return nil } -func (e *environment) isEnabled(traitID ID) bool { - t := NewTrait() - if _, err := e.getTrait(traitID, &t); err != nil { - logrus.Panic(err) - } - - return t.Enabled +func (trait *BaseTrait) customize(environment *environment, resources *kubernetes.Collection) error { + return nil } -func (e *environment) isAutoDetectionMode(traitID ID) bool { - spec := e.getTraitSpec(traitID) - if spec == nil { - return true - } +/* Environment */ - if spec.Configuration == nil { - return true - } - - return spec.Configuration["enabled"] == "" +// A environment provides the context where the trait is executed +type environment struct { + Platform *v1alpha1.IntegrationPlatform + Context *v1alpha1.IntegrationContext + Integration *v1alpha1.Integration + ExecutedTraits []ID } diff --git a/pkg/trait/util.go b/pkg/trait/util.go index 577bdb7..5b280a9 100644 --- a/pkg/trait/util.go +++ b/pkg/trait/util.go @@ -19,11 +19,9 @@ package trait import ( "fmt" - "reflect" "strings" "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" - "github.com/fatih/structs" "github.com/operator-framework/operator-sdk/pkg/sdk" "github.com/pkg/errors" "k8s.io/api/core/v1" @@ -119,32 +117,3 @@ func CombineConfigurationAsSlice(configurationType string, context *v1alpha1.Int return keys } -// ComputeTraitsProperties -- -func ComputeTraitsProperties() []string { - results := make([]string, 0) - - processFields(structs.Fields(tService), func(name string) { - results = append(results, string(tService.ID())+"."+name) - }) - - return results -} - -func processFields(fields []*structs.Field, processor func(string)) { - for _, f := range fields { - if f.IsEmbedded() && f.IsExported() && f.Kind() == reflect.Struct { - processFields(f.Fields(), processor) - } - - if f.IsEmbedded() { - continue - } - - property := f.Tag("property") - - if property != "" { - items := strings.Split(property, ",") - processor(items[0]) - } - } -}