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 46fe56be0cb06238d02401b52514100cd6b9a106
Author: nferraro <ni.ferr...@gmail.com>
AuthorDate: Mon Dec 3 15:21:47 2018 +0100

    Fix #219: use deployment in Knative when cannot scale to 0
---
 pkg/metadata/http.go               | 131 +++++++++++++++++++++++++
 pkg/metadata/metadata.go           |  57 ++++++++++-
 pkg/metadata/metadata_http_test.go | 195 +++++++++++++++++++++++++++++++++++++
 pkg/metadata/types.go              |   4 +
 pkg/trait/catalog.go               |  10 +-
 pkg/trait/knative.go               |  70 ++++++++++---
 pkg/trait/service.go               |  54 +++-------
 pkg/trait/trait_test.go            |  22 +++--
 pkg/util/kubernetes/collection.go  |  33 +++++++
 9 files changed, 508 insertions(+), 68 deletions(-)

diff --git a/pkg/metadata/http.go b/pkg/metadata/http.go
new file mode 100644
index 0000000..6ca7065
--- /dev/null
+++ b/pkg/metadata/http.go
@@ -0,0 +1,131 @@
+/*
+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"
+       "strings"
+)
+
+var httpURIs = map[string]bool{
+       "ahc":                  true,
+       "ahc-ws":               true,
+       "atmosphere-websocket": true,
+       "cxf":         true,
+       "cxfrs":       true,
+       "grpc":        true,
+       "jetty":       true,
+       "netty-http":  true,
+       "netty4-http": true,
+       "rest":        true,
+       "restlet":     true,
+       "servlet":     true,
+       "spark-rest":  true,
+       "spring-ws":   true,
+       "undertow":    true,
+       "websocket":   true,
+       "knative":     true,
+}
+
+var passiveURIs = map[string]bool{
+       "bean":       true,
+       "binding":    true,
+       "browse":     true,
+       "class":      true,
+       "controlbus": true,
+       "dataformat": true,
+       "dataset":    true,
+       "direct":     true,
+       "direct-vm":  true,
+       "language":   true,
+       "log":        true,
+       "mock":       true,
+       "properties": true,
+       "ref":        true,
+       "seda":       true,
+       "stub":       true,
+       "test":       true,
+       "validator":  true,
+       "vm":         true,
+}
+
+var restIndicator = regexp.MustCompile(".*rest\\s*\\([^)]*\\).*")
+var xmlRestIndicator = regexp.MustCompile(".*<\\s*rest\\s+[^>]*>.*")
+
+// requiresHTTPService returns true if the integration needs to expose itself 
through HTTP
+func requiresHTTPService(source v1alpha1.SourceSpec, fromURIs []string) bool {
+       if hasRestIndicator(source) {
+               return true
+       }
+       return containsHTTPURIs(fromURIs)
+}
+
+// hasOnlyPassiveEndpoints returns true if the integration has no endpoint 
that needs to remain always active
+func hasOnlyPassiveEndpoints(source v1alpha1.SourceSpec, fromURIs []string) 
bool {
+       passivePlusHTTP := make(map[string]bool)
+       for k, v := range passiveURIs {
+               passivePlusHTTP[k] = v
+       }
+       for k, v := range httpURIs {
+               passivePlusHTTP[k] = v
+       }
+       return containsOnlyURIsIn(fromURIs, passivePlusHTTP)
+}
+
+func containsHTTPURIs(fromURI []string) bool {
+       for _, uri := range fromURI {
+               prefix := getURIPrefix(uri)
+               if enabled, ok := httpURIs[prefix]; ok && enabled {
+                       return true
+               }
+       }
+       return false
+}
+
+func containsOnlyURIsIn(fromURI []string, allowed map[string]bool) bool {
+       for _, uri := range fromURI {
+               prefix := getURIPrefix(uri)
+               if enabled, ok := allowed[prefix]; !ok || !enabled {
+                       return false
+               }
+       }
+       return true
+}
+
+func getURIPrefix(uri string) string {
+       parts := strings.SplitN(uri, ":", 2)
+       if len(parts) > 0 {
+               return parts[0]
+       }
+       return ""
+}
+
+func hasRestIndicator(source v1alpha1.SourceSpec) bool {
+       pat := getRestIndicatorRegexpsForLanguage(source.Language)
+       return pat.MatchString(source.Content)
+}
+
+func getRestIndicatorRegexpsForLanguage(language v1alpha1.Language) 
*regexp.Regexp {
+       switch language {
+       case v1alpha1.LanguageXML:
+               return xmlRestIndicator
+       default:
+               return restIndicator
+       }
+}
diff --git a/pkg/metadata/metadata.go b/pkg/metadata/metadata.go
index 6e43b0b..9eec7bd 100644
--- a/pkg/metadata/metadata.go
+++ b/pkg/metadata/metadata.go
@@ -19,19 +19,68 @@ package metadata
 
 import (
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+       "sort"
 )
 
+// ExtractAll returns metadata information from all listed source codes
+func ExtractAll(sources []v1alpha1.SourceSpec) IntegrationMetadata {
+       // neutral metadata
+       meta := IntegrationMetadata{
+               Language:            "",
+               Dependencies:        []string{},
+               FromURIs:            []string{},
+               ToURIs:              []string{},
+               PassiveEndpoints:    true,
+               RequiresHTTPService: false,
+       }
+       for _, source := range sources {
+               meta = merge(meta, Extract(source))
+       }
+       return meta
+}
+
+func merge(m1 IntegrationMetadata, m2 IntegrationMetadata) IntegrationMetadata 
{
+       language := m2.Language
+       if m1.Language != "" && m1.Language != language {
+               language = ""
+       }
+       deps := make(map[string]bool)
+       for _, d := range m1.Dependencies {
+               deps[d] = true
+       }
+       for _, d := range m2.Dependencies {
+               deps[d] = true
+       }
+       allDependencies := make([]string, 0)
+       for k := range deps {
+               allDependencies = append(allDependencies, k)
+       }
+       sort.Strings(allDependencies)
+       return IntegrationMetadata{
+               Language:            language,
+               FromURIs:            append(m1.FromURIs, m2.FromURIs...),
+               ToURIs:              append(m1.ToURIs, m2.ToURIs...),
+               Dependencies:        allDependencies,
+               RequiresHTTPService: m1.RequiresHTTPService || 
m2.RequiresHTTPService,
+               PassiveEndpoints:    m1.PassiveEndpoints && m2.PassiveEndpoints,
+       }
+}
+
 // 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)
+       requiresHTTPService := requiresHTTPService(source, fromURIs)
+       passiveEndpoints := hasOnlyPassiveEndpoints(source, fromURIs)
        return IntegrationMetadata{
-               Language:     language,
-               FromURIs:     fromURIs,
-               ToURIs:       toURIs,
-               Dependencies: dependencies,
+               Language:            language,
+               FromURIs:            fromURIs,
+               ToURIs:              toURIs,
+               Dependencies:        dependencies,
+               RequiresHTTPService: requiresHTTPService,
+               PassiveEndpoints:    passiveEndpoints,
        }
 }
 
diff --git a/pkg/metadata/metadata_http_test.go 
b/pkg/metadata/metadata_http_test.go
new file mode 100644
index 0000000..d75f57b
--- /dev/null
+++ b/pkg/metadata/metadata_http_test.go
@@ -0,0 +1,195 @@
+/*
+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 (
+       "testing"
+
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+       "github.com/stretchr/testify/assert"
+)
+
+func TestHttpJavaSource(t *testing.T) {
+       code := v1alpha1.SourceSpec{
+               Name:     "Request.java",
+               Language: v1alpha1.LanguageJavaSource,
+               Content: `
+                       from("telegram:bots/cippa").to("log:stash");
+                       from("undertow:uri").to("log:stash");
+                       from("ine:xistent").to("log:stash");
+               `,
+       }
+       meta := Extract(code)
+       assert.True(t, meta.RequiresHTTPService)
+       assert.False(t, meta.PassiveEndpoints)
+}
+
+func TestHttpOnlyJavaSource(t *testing.T) {
+       code := v1alpha1.SourceSpec{
+               Name:     "Request.java",
+               Language: v1alpha1.LanguageJavaSource,
+               Content: `
+                       from("direct:bots/cippa").to("log:stash");
+                       from("undertow:uri").to("log:stash");
+                       from("seda:path").to("log:stash");
+               `,
+       }
+       meta := Extract(code)
+       assert.True(t, meta.RequiresHTTPService)
+       assert.True(t, meta.PassiveEndpoints)
+}
+
+func TestHttpOnlyJavaSourceRest(t *testing.T) {
+       code := v1alpha1.SourceSpec{
+               Name:     "Request.java",
+               Language: v1alpha1.LanguageJavaSource,
+               Content: `
+                       from("direct:bots/cippa").to("log:stash");
+                       rest().get("").to("log:stash");
+               `,
+       }
+       meta := Extract(code)
+       assert.True(t, meta.RequiresHTTPService)
+       assert.True(t, meta.PassiveEndpoints)
+}
+
+func TestHttpOnlyJavaSourceRest2(t *testing.T) {
+       code := v1alpha1.SourceSpec{
+               Name:     "Request.java",
+               Language: v1alpha1.LanguageJavaSource,
+               Content: `
+                       from("vm:bots/cippa").to("log:stash");
+                       rest( ).get("").to("log:stash");
+               `,
+       }
+       meta := Extract(code)
+       assert.True(t, meta.RequiresHTTPService)
+       assert.True(t, meta.PassiveEndpoints)
+}
+
+
+func TestNoHttpGroovySource(t *testing.T) {
+       code := v1alpha1.SourceSpec{
+               Name:     "Request.groovy",
+               Language: v1alpha1.LanguageGroovy,
+               Content: `
+                       from('direct:bots/cippa').to("log:stash");
+                       from('teelgram:uri').to("log:stash");
+                       from('seda:path').to("log:stash");
+               `,
+       }
+       meta := Extract(code)
+       assert.False(t, meta.RequiresHTTPService)
+       assert.False(t, meta.PassiveEndpoints)
+}
+
+func TestHttpOnlyGroovySource(t *testing.T) {
+       code := v1alpha1.SourceSpec{
+               Name:     "Request.groovy",
+               Language: v1alpha1.LanguageGroovy,
+               Content: `
+                       from('direct:bots/cippa').to("log:stash");
+                       from('undertow:uri').to("log:stash");
+                       from('seda:path').to("log:stash");
+               `,
+       }
+       meta := Extract(code)
+       assert.True(t, meta.RequiresHTTPService)
+       assert.True(t, meta.PassiveEndpoints)
+}
+
+func TestHttpXMLSource(t *testing.T) {
+       code := v1alpha1.SourceSpec{
+               Name:     "routes.xml",
+               Language: v1alpha1.LanguageXML,
+               Content: `
+                       <from uri="telegram:ciao" />
+                       <rest path="/">
+                       </rest>
+               `,
+       }
+       meta := Extract(code)
+       assert.True(t, meta.RequiresHTTPService)
+       assert.False(t, meta.PassiveEndpoints)
+}
+
+func TestHttpOnlyXMLSource(t *testing.T) {
+       code := v1alpha1.SourceSpec{
+               Name:     "routes.xml",
+               Language: v1alpha1.LanguageXML,
+               Content: `
+                       <from uri="direct:ciao" />
+                       <rest path="/">
+                       </rest>
+               `,
+       }
+       meta := Extract(code)
+       assert.True(t, meta.RequiresHTTPService)
+       assert.True(t, meta.PassiveEndpoints)
+}
+
+
+
+func TestMultilangHTTPOnlySource(t *testing.T) {
+       codes := []v1alpha1.SourceSpec{
+               {
+                       Name:     "routes.xml",
+                       Language: v1alpha1.LanguageXML,
+                       Content: `
+                               <from uri="direct:ciao" />
+                               <rest path="/">
+                               </rest>
+                       `,
+               },
+               {
+                       Name:     "routes2.groovy",
+                       Language: v1alpha1.LanguageGroovy,
+                       Content: `
+                               from('seda:in').to('seda:out')
+                       `,
+               },
+       }
+       meta := ExtractAll(codes)
+       assert.True(t, meta.RequiresHTTPService)
+       assert.True(t, meta.PassiveEndpoints)
+}
+
+func TestMultilangHTTPSource(t *testing.T) {
+       codes := []v1alpha1.SourceSpec{
+               {
+                       Name:     "routes.xml",
+                       Language: v1alpha1.LanguageXML,
+                       Content: `
+                               <from uri="direct:ciao" />
+                               <rest path="/">
+                               </rest>
+                       `,
+               },
+               {
+                       Name:     "routes2.groovy",
+                       Language: v1alpha1.LanguageGroovy,
+                       Content: `
+                               from('seda:in').to('seda:out')
+                               from('timer:tick').to('log:info')
+                       `,
+               },
+       }
+       meta := ExtractAll(codes)
+       assert.True(t, meta.RequiresHTTPService)
+       assert.False(t, meta.PassiveEndpoints)
+}
\ No newline at end of file
diff --git a/pkg/metadata/types.go b/pkg/metadata/types.go
index de10bb1..04ebe1c 100644
--- a/pkg/metadata/types.go
+++ b/pkg/metadata/types.go
@@ -29,4 +29,8 @@ type IntegrationMetadata struct {
        Dependencies []string
        // The language in which the integration is written
        Language v1alpha1.Language
+       // RequiresHTTPService indicates if the integration needs to be invoked 
through HTTP
+       RequiresHTTPService bool
+       // PassiveEndpoints indicates that the integration contains only 
passive endpoints that are activated from external calls, including HTTP 
(useful to determine if the integration can scale to 0)
+       PassiveEndpoints bool
 }
diff --git a/pkg/trait/catalog.go b/pkg/trait/catalog.go
index d0180be..1766929 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/catalog.go
@@ -78,31 +78,31 @@ func (c *Catalog) traitsFor(environment *Environment) 
[]Trait {
                return []Trait{
                        c.tDebug,
                        c.tDependencies,
-                       c.tService,
-                       c.tRoute,
                        c.tBuilder,
                        c.tSpringBoot,
                        c.tDeployment,
+                       c.tService,
+                       c.tRoute,
                        c.tOwner,
                }
        case v1alpha1.TraitProfileKubernetes:
                return []Trait{
                        c.tDebug,
                        c.tDependencies,
-                       c.tService,
-                       c.tIngress,
                        c.tBuilder,
                        c.tSpringBoot,
                        c.tDeployment,
+                       c.tService,
+                       c.tIngress,
                        c.tOwner,
                }
        case v1alpha1.TraitProfileKnative:
                return []Trait{
                        c.tDebug,
                        c.tDependencies,
-                       c.tKnative,
                        c.tBuilder,
                        c.tSpringBoot,
+                       c.tKnative,
                        c.tDeployment,
                        c.tOwner,
                }
diff --git a/pkg/trait/knative.go b/pkg/trait/knative.go
index 0e211f1..992b889 100644
--- a/pkg/trait/knative.go
+++ b/pkg/trait/knative.go
@@ -24,6 +24,8 @@ import (
 
        "github.com/operator-framework/operator-sdk/pkg/sdk"
        "github.com/pkg/errors"
+       "k8s.io/api/apps/v1"
+       "strings"
 
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 
@@ -35,15 +37,23 @@ import (
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
+const (
+       knativeKindDeployment = "deployment"
+       knativeKindService    = "service"
+)
+
 type knativeTrait struct {
-       BaseTrait `property:",squash"`
-       Sources   string `property:"sources"`
-       Sinks     string `property:"sinks"`
+       BaseTrait          `property:",squash"`
+       Kind               string `property:"kind"`
+       Sources            string `property:"sources"`
+       Sinks              string `property:"sinks"`
+       deploymentDelegate *deploymentTrait
 }
 
 func newKnativeTrait() *knativeTrait {
        return &knativeTrait{
-               BaseTrait: newBaseTrait("knative"),
+               BaseTrait:          newBaseTrait("knative"),
+               deploymentDelegate: newDeploymentTrait(),
        }
 }
 
@@ -60,18 +70,58 @@ func (t *knativeTrait) autoconfigure(e *Environment) error {
                channels := t.getSinkChannels(e)
                t.Sinks = strings.Join(channels, ",")
        }
+       if t.Kind == "" {
+               meta := metadata.ExtractAll(e.Integration.Spec.Sources)
+               if meta.RequiresHTTPService && meta.PassiveEndpoints {
+                       t.Kind = knativeKindService
+               } else {
+                       t.Kind = knativeKindDeployment
+               }
+       }
        return nil
 }
 
 func (t *knativeTrait) apply(e *Environment) error {
+       if err := t.prepareEnvVars(e); err != nil {
+               return err
+       }
        for _, sub := range t.getSubscriptionsFor(e) {
                e.Resources.Add(sub)
        }
-       svc, err := t.getServiceFor(e)
+       switch t.Kind {
+       case knativeKindService:
+               svc, err := t.getServiceFor(e)
+               if err != nil {
+                       return err
+               }
+               e.Resources.Add(svc)
+               return nil
+       case knativeKindDeployment:
+               return t.addDeployment(e)
+       }
+       return nil
+}
+
+func (t *knativeTrait) prepareEnvVars(e *Environment) error {
+       // common env var for Knative integration
+       conf, err := t.getConfigurationSerialized(e)
        if err != nil {
                return err
        }
-       e.Resources.Add(svc)
+       e.EnvVars["CAMEL_KNATIVE_CONFIGURATION"] = conf
+       return nil
+}
+
+func (t *knativeTrait) addDeployment(e *Environment) error {
+       if err := t.deploymentDelegate.apply(e); err != nil {
+               return err
+       }
+       e.Resources.VisitDeployment(func(d *v1.Deployment) {
+               if d.Spec.Template.Annotations == nil {
+                       d.Spec.Template.Annotations = make(map[string]string)
+               }
+               d.Spec.Template.Annotations["sidecar.istio.io/inject"] = "true"
+       })
        return nil
 }
 
@@ -112,12 +162,10 @@ func (t *knativeTrait) getServiceFor(e *Environment) 
(*serving.Service, error) {
        // optimizations
        environment["AB_JOLOKIA_OFF"] = True
 
-       // Knative integration
-       conf, err := t.getConfigurationSerialized(e)
-       if err != nil {
-               return nil, err
+       // add env vars from traits
+       for k, v := range e.EnvVars {
+               environment[k] = v
        }
-       environment["CAMEL_KNATIVE_CONFIGURATION"] = conf
 
        labels := map[string]string{
                "camel.apache.org/integration": e.Integration.Name,
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index a7c927a..df0d66f 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -19,24 +19,13 @@ package trait
 
 import (
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-       "github.com/apache/camel-k/version"
+       "github.com/apache/camel-k/pkg/metadata"
+       "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/util/intstr"
 )
 
-var webComponents = map[string]bool{
-       "camel:servlet":     true,
-       "camel:undertow":    true,
-       "camel:jetty":       true,
-       "camel:jetty9":      true,
-       "camel:netty-http":  true,
-       "camel:netty4-http": true,
-       "mvn:org.apache.camel.k:camel-knative:" + version.Version: true,
-       // TODO find a better way to discover need for exposure
-       // maybe using the resolved classpath of the context instead of the 
requested dependencies
-}
-
 type serviceTrait struct {
        BaseTrait `property:",squash"`
 
@@ -56,8 +45,18 @@ func (s *serviceTrait) appliesTo(e *Environment) bool {
 
 func (s *serviceTrait) autoconfigure(e *Environment) error {
        if s.Enabled == nil {
-               required := s.requiresService(e)
-               s.Enabled = &required
+               hasDeployment := false
+               e.Resources.VisitDeployment(func(s *v1.Deployment) {
+                       hasDeployment = true
+               })
+               if hasDeployment {
+                       meta := metadata.ExtractAll(e.Integration.Spec.Sources)
+                       required := meta.RequiresHTTPService
+                       s.Enabled = &required
+               } else {
+                       enabled := false
+                       s.Enabled = &enabled
+               }
        }
        return nil
 }
@@ -98,28 +97,3 @@ func (s *serviceTrait) getServiceFor(e *Environment) 
*corev1.Service {
 
        return &svc
 }
-
-func (*serviceTrait) requiresService(environment *Environment) bool {
-       cweb := false
-       iweb := false
-
-       if environment.Context != nil {
-               for _, dep := range environment.Context.Spec.Dependencies {
-                       if decision, present := webComponents[dep]; present {
-                               cweb = decision
-                               break
-                       }
-               }
-       }
-
-       if environment.Integration != nil {
-               for _, dep := range environment.Integration.Spec.Dependencies {
-                       if decision, present := webComponents[dep]; present {
-                               iweb = decision
-                               break
-                       }
-               }
-       }
-
-       return cweb || iweb
-}
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index 3e78fad..a770e76 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -51,7 +51,7 @@ func TestOpenShiftTraits(t *testing.T) {
 }
 
 func TestOpenShiftTraitsWithWeb(t *testing.T) {
-       env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, 
"camel:core", "camel:undertow")
+       env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, 
"from('undertow:http').to('log:info')")
        res := processTestEnv(t, env)
        assert.Contains(t, env.ExecutedTraits, ID("deployment"))
        assert.Contains(t, env.ExecutedTraits, ID("service"))
@@ -72,7 +72,7 @@ func TestOpenShiftTraitsWithWeb(t *testing.T) {
 }
 
 func TestOpenShiftTraitsWithWebAndConfig(t *testing.T) {
-       env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, 
"camel:core", "camel:undertow")
+       env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, 
"from('undertow:http').to('log:info')")
        env.Integration.Spec.Traits = 
make(map[string]v1alpha1.IntegrationTraitSpec)
        env.Integration.Spec.Traits["service"] = v1alpha1.IntegrationTraitSpec{
                Configuration: map[string]string{
@@ -88,7 +88,7 @@ func TestOpenShiftTraitsWithWebAndConfig(t *testing.T) {
 }
 
 func TestOpenShiftTraitsWithWebAndDisabledTrait(t *testing.T) {
-       env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, 
"camel:core", "camel:undertow")
+       env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, 
"from('undertow:http').to('log:info')")
        env.Integration.Spec.Traits = 
make(map[string]v1alpha1.IntegrationTraitSpec)
        env.Integration.Spec.Traits["service"] = v1alpha1.IntegrationTraitSpec{
                Configuration: map[string]string{
@@ -105,7 +105,7 @@ func TestOpenShiftTraitsWithWebAndDisabledTrait(t 
*testing.T) {
 }
 
 func TestKubernetesTraits(t *testing.T) {
-       env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, 
"camel:core")
+       env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, 
"from('timer:tick').to('log:info')")
        res := processTestEnv(t, env)
        assert.Contains(t, env.ExecutedTraits, ID("deployment"))
        assert.NotContains(t, env.ExecutedTraits, ID("service"))
@@ -120,7 +120,7 @@ func TestKubernetesTraits(t *testing.T) {
 }
 
 func TestKubernetesTraitsWithWeb(t *testing.T) {
-       env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, 
"camel:core", "camel:servlet")
+       env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, 
"from('servlet:http').to('log:info')")
        res := processTestEnv(t, env)
        assert.Contains(t, env.ExecutedTraits, ID("deployment"))
        assert.Contains(t, env.ExecutedTraits, ID("service"))
@@ -138,7 +138,7 @@ func TestKubernetesTraitsWithWeb(t *testing.T) {
 }
 
 func TestTraitDecode(t *testing.T) {
-       env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift)
+       env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "")
        env.Integration.Spec.Traits = 
make(map[string]v1alpha1.IntegrationTraitSpec)
        svcTrait := v1alpha1.IntegrationTraitSpec{
                Configuration: map[string]string{
@@ -164,7 +164,7 @@ func processTestEnv(t *testing.T, env *Environment) 
*kubernetes.Collection {
        return env.Resources
 }
 
-func createTestEnv(cluster v1alpha1.IntegrationPlatformCluster, dependencies 
...string) *Environment {
+func createTestEnv(cluster v1alpha1.IntegrationPlatformCluster, script string) 
*Environment {
        return &Environment{
                Integration: &v1alpha1.Integration{
                        ObjectMeta: metav1.ObjectMeta{
@@ -172,7 +172,13 @@ func createTestEnv(cluster 
v1alpha1.IntegrationPlatformCluster, dependencies ...
                                Namespace: "ns",
                        },
                        Spec: v1alpha1.IntegrationSpec{
-                               Dependencies: dependencies,
+                               Sources: []v1alpha1.SourceSpec{
+                                       {
+                                               Language: 
v1alpha1.LanguageGroovy,
+                                               Name: "file.groovy",
+                                               Content: script,
+                                       },
+                               },
                        },
                        Status: v1alpha1.IntegrationStatus{
                                Phase: v1alpha1.IntegrationPhaseDeploying,
diff --git a/pkg/util/kubernetes/collection.go 
b/pkg/util/kubernetes/collection.go
index fbc9098..4a770e5 100644
--- a/pkg/util/kubernetes/collection.go
+++ b/pkg/util/kubernetes/collection.go
@@ -18,6 +18,7 @@ limitations under the License.
 package kubernetes
 
 import (
+       serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
        routev1 "github.com/openshift/api/route/v1"
        appsv1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
@@ -146,6 +147,38 @@ func (c *Collection) GetRoute(filter func(*routev1.Route) 
bool) *routev1.Route {
        return retValue
 }
 
+// VisitKnativeService executes the visitor function on all Knative serving 
Service resources
+func (c *Collection) VisitKnativeService(visitor func(*serving.Service)) {
+       c.Visit(func(res runtime.Object) {
+               if conv, ok := res.(*serving.Service); ok {
+                       visitor(conv)
+               }
+       })
+}
+
+// VisitContainer executes the visitor function on all Containers inside 
deployments or other resources
+func (c *Collection) VisitContainer(visitor func(container *corev1.Container)) 
{
+       c.VisitDeployment(func(d *appsv1.Deployment) {
+               for _, c := range d.Spec.Template.Spec.Containers {
+                       visitor(&c)
+               }
+       })
+       c.VisitKnativeService(func(s *serving.Service) {
+               if s.Spec.RunLatest != nil {
+                       c := 
s.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container
+                       visitor(&c)
+               }
+               if s.Spec.Pinned != nil {
+                       c := 
s.Spec.Pinned.Configuration.RevisionTemplate.Spec.Container
+                       visitor(&c)
+               }
+               if s.Spec.Release != nil {
+                       c := 
s.Spec.Release.Configuration.RevisionTemplate.Spec.Container
+                       visitor(&c)
+               }
+       })
+}
+
 // VisitMetaObject executes the visitor function on all meta.Object resources
 func (c *Collection) VisitMetaObject(visitor func(metav1.Object)) {
        c.Visit(func(res runtime.Object) {

Reply via email to