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.ferraro@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])
- }
- }
-}
|