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) {