[GitHub] lburgazzoli closed pull request #261: Better Knative and Istio integration

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[GitHub] lburgazzoli closed pull request #261: Better Knative and Istio integration

GitBox
lburgazzoli closed pull request #261: Better Knative and Istio integration
URL: https://github.com/apache/camel-k/pull/261
 
 
   

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

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

diff --git a/README.adoc b/README.adoc
index c50c564..91790ec 100644
--- a/README.adoc
+++ b/README.adoc
@@ -51,17 +51,17 @@ For Minishift, this means executing `oc login -u system:admin` then `kamel insta
 After the initial setup, you can run a Camel integration on the cluster by executing:
 
 ```
-kamel run runtime/examples/Sample.java
+kamel run examples/Sample.java
 ```
 
-A "Sample.java" file is included in the link:/runtime/examples[/runtime/examples] folder of this repository. You can change the content of the file and execute the command again to see the changes.
+A "Sample.java" file is included in the link:/examples[/examples] folder of this repository. You can change the content of the file and execute the command again to see the changes.
 
 ==== Configure Integration properties
 
 Properties associated to an integration can be configured either using a ConfigMap/Secret or by setting using the "--property" flag, i.e.
 
 ```
-kamel run --property my.message=test runtime/examples/props.js
+kamel run --property my.message=test examples/props.js
 ```
 
 ==== Configure Integration Logging
@@ -92,7 +92,7 @@ camel.component.seda.queueSize = 10
 If you want to iterate quickly on an integration to have fast feedback on the code you're writing, you can use by running it in **"dev" mode**:
 
 ```
-kamel run runtime/examples/Sample.java --dev
+kamel run examples/Sample.java --dev
 ```
 
 The `--dev` flag deploys immediately the integration and shows the integration logs in the console. You can then change the code and see
@@ -103,7 +103,7 @@ The console follows automatically all redeploys of the integration.
 Here's an example of the output:
 
 ```
-[nferraro@localhost camel-k]$ kamel run runtime/examples/Sample.java --dev
+[nferraro@localhost camel-k]$ kamel run examples/Sample.java --dev
 integration "sample" created
 integration "sample" in phase Building
 integration "sample" in phase Deploying
@@ -160,9 +160,9 @@ Camel K supports multiple languages for writing integrations:
 
 More information about supported languages is provided in the link:docs/languages.adoc[languages guide].
 
-Integrations written in different languages are provided in the link:/runtime/examples[examples] directory.
+Integrations written in different languages are provided in the link:/examples[examples] directory.
 
-An example of integration written in JavaScript is the link:/runtime/examples/dns.js[/runtime/examples/dns.js] integration.
+An example of integration written in JavaScript is the link:/examples/dns.js[/examples/dns.js] integration.
 Here's the content:
 
 ```
@@ -178,7 +178,7 @@ from('timer:dns?period=1s')
 To run it, you need just to execute:
 
 ```
-kamel run runtime/examples/dns.js
+kamel run examples/dns.js
 ```
 
 === Traits
diff --git a/docs/developers.adoc b/docs/developers.adoc
index b3a6a6c..9589be3 100644
--- a/docs/developers.adoc
+++ b/docs/developers.adoc
@@ -140,13 +140,13 @@ This command assumes you have an already running Minikube instance.
 Now you can play with Camel K:
 
 ```
-./kamel run runtime/examples/Sample.java
+./kamel run examples/Sample.java
 ```
 
 To add additional dependencies to your routes:
 
 ```
-./kamel run -d camel:dns runtime/examples/dns.js
+./kamel run -d camel:dns examples/dns.js
 ```
 
 [[debugging]]
diff --git a/docs/traits.adoc b/docs/traits.adoc
index 130958e..5e638a2 100644
--- a/docs/traits.adoc
+++ b/docs/traits.adoc
@@ -84,8 +84,23 @@ The following is a list of common traits that can be configured by the end users
 
 !===
 
+| istio
+| Knative (Kubernetes, Openshift)
+| Allows to configure outbound traffic for Istio.
+  +
+  +
+  It's enabled by default when the Knative profile is active.
+
+[cols="m,"]
+!===
+
+! istio.allow
+! Configures a (comma-separated) list of CIDR subnets that should not be intercepted by the Istio proxy (`10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` by default).
+
+!===
+
 | service
-| Kubernetes, OpenShift
+| All (Knative in deployment mode)
 | Exposes the integration with a Service resource so that it can be accessed by other applications (or integrations) in the same namespace.
   +
   +
@@ -129,6 +144,13 @@ The following is a list of common traits that can be configured by the end users
 
 !===
 
+| debug
+| All
+| Run the integration in debug mode (you can port-forward to port 5005 to connect)
+  +
+  +
+  It's disabled by default.
+
 |=======================
 
 
diff --git a/runtime/examples/CaffeineCacheSample.java b/examples/CaffeineCacheSample.java
similarity index 100%
rename from runtime/examples/CaffeineCacheSample.java
rename to examples/CaffeineCacheSample.java
diff --git a/runtime/examples/RestWithRestlet.java b/examples/RestWithRestlet.java
similarity index 92%
rename from runtime/examples/RestWithRestlet.java
rename to examples/RestWithRestlet.java
index 16c77c7..fa48418 100644
--- a/runtime/examples/RestWithRestlet.java
+++ b/examples/RestWithRestlet.java
@@ -1,7 +1,7 @@
 //
 // To run this integrations use:
 //
-//     kamel run --name=rest-with-restlet --dependency=camel-restlet runtime/examples/RestWithRestlet.java
+//     kamel run --name=rest-with-restlet --dependency=camel-restlet examples/RestWithRestlet.java
 //
 public class RestWithRestlet extends org.apache.camel.builder.RouteBuilder {
     @Override
diff --git a/runtime/examples/Sample.java b/examples/Sample.java
similarity index 100%
rename from runtime/examples/Sample.java
rename to examples/Sample.java
diff --git a/runtime/examples/camel-caffeine.groovy b/examples/camel-caffeine.groovy
similarity index 95%
rename from runtime/examples/camel-caffeine.groovy
rename to examples/camel-caffeine.groovy
index 1a70c25..d52deb6 100644
--- a/runtime/examples/camel-caffeine.groovy
+++ b/examples/camel-caffeine.groovy
@@ -1,7 +1,7 @@
 //
 // To run this integrations use:
 //
-//     kamel run --runtime groovy runtime/examples/camel-caffeine.groovy
+//     kamel run --runtime groovy examples/camel-caffeine.groovy
 //
 
 import com.github.benmanes.caffeine.cache.Caffeine
diff --git a/runtime/examples/dns.js b/examples/dns.js
similarity index 68%
rename from runtime/examples/dns.js
rename to examples/dns.js
index f7c90d2..9ae759c 100644
--- a/runtime/examples/dns.js
+++ b/examples/dns.js
@@ -1,11 +1,11 @@
 //
 // To run this integrations use:
 //
-//     kamel run -d camel:dns runtime/examples/dns.js
+//     kamel run -d camel:dns examples/dns.js
 //
 // Or simply (since dependency auto-detection is enabled by default):
 //
-//     kamel run runtime/examples/dns.js
+//     kamel run examples/dns.js
 //
 
 from('timer:dns?period=1s')
diff --git a/runtime/examples/hello.xml b/examples/hello.xml
similarity index 100%
rename from runtime/examples/hello.xml
rename to examples/hello.xml
diff --git a/examples/knative/README.adoc b/examples/knative/README.adoc
new file mode 100644
index 0000000..e392d6b
--- /dev/null
+++ b/examples/knative/README.adoc
@@ -0,0 +1,136 @@
+Knative Example (Apache Camel K)
+================================
+
+This example shows how Camel K can be used to connect Knative building blocks to create awesome applications.
+
+A video version of this https://youtu.be/btf_e2GniXM[demo is available on YouTube].
+
+It's assumed that both Camel K and Knative are properly installed (including Knative Build, Serving and Eventing) into the cluster.
+Refer to the specific documentation to install and configure all components.
+
+We're going to create two channels:
+- messages
+- words
+
+The first channel will contain phrases, while the second one will contains the single words contained in the phrases.
+
+To create the channels (they use the in-memory channel provisioner):
+
+```
+kubectl create -f messages-channel.yaml
+kubectl create -f words-channel.yaml
+```
+
+We can now proceed to install all camel K integrations.
+
+== Install a "Printer"
+
+We'll install a Camel K integration that will print all words from the `words` channel.
+
+Writing a "function" that does this is as simple as writing:
+
+```
+from('knative:channel/words')
+  .convertBodyTo(String.class)
+  .to('log:info')
+```
+
+You can run this integration by running:
+
+```
+kamel run printer.groovy
+```
+
+Under the hood, the Camel K operator does this:
+- Understands that the integration is passive, meaning that it can be activated only using an external HTTP call (the knative consumer endpoint)
+- Materializes the integration as a Knative autoscaling service, integrated in the Istio service mesh
+- Adds a Knative Eventing `Subscription` that points to the autoscaling service
+
+The resulting integration will be scaled to 0 when not used (if you wait ~5 minutes, you'll see it).
+
+== Install a "Splitter"
+
+We're now going to deploy a splitter, using the Camel core Split EIP. The splitter will take all messages from the `messages` channel,
+split them and push the single words into the `words` channel.
+
+The integration code is super simple:
+
+```
+from('knative:channel/messages')
+  .split().tokenize(" ")
+  .log('sending ${body} to words channel')
+  .to('knative:channel/words')
+```
+
+Let's run it with:
+
+```
+kamel run splitter.groovy
+```
+
+This integration will be also materialized as a Knative autoscaling service, because the only entrypoint is passive (waits for a push notification).
+
+== Install a "Feed"
+
+We're going to feed this chain of functions using a timed feed like this:
+
+```
+from('timer:clock?period=3s')
+  .setBody().constant("Hello World from Camel K")
+  .to('knative:channel/messages')
+  .log('sent message to messages channel')
+```
+
+Every 3 seconds, the integration sends a message to the Knative `messages` channel.
+
+Let's run it with:
+
+```
+kamel run feed.groovy
+```
+
+This cannot be materialized into an autoscaling service, but the operator understands it automatically and maps it to a plain Kubernetes Deployment
+(Istio sidecar will be injected).
+
+== Playing around
+
+If you've installed all the services, you'll find that the printer pod will print single words as they arrive from the feed (every 3 seconds, passing by the splitter function).
+
+If you now stop the feed integration (`kamel delete feed`) you will notice that the other services (splitter and printer) will scale down to 0 in few minutes.
+
+And if you reinstall the feed again (`kamel run feed.groovy`), the other integration will scale up again as soon as they receive messages (splitter first, then printer).
+
+== Playing harder
+
+You can also play with different kind of feeds. E.g. the following simple feed can be used to bind messages from Telegram to the system:
+
+```
+from('telegram:bots/<put-here-your-botfather-authorization>')
+  .convertBodyTo(String.class)
+  .to('log:info')
+  .to('knative:channel/messages')
+```
+
+Now just send messages to your bot with the Telegram client app to see all single words appearing in the printer service.
+
+You can also replace the printer with a Slack-based printer like:
+
+```
+from('knative:channel/words')
+  .log('Received: ${body}')
+  .to('slack:#camel-k-tests')
+
+
+context {
+  components {
+    slack {
+      webhookUrl '<put-here-your-slack-incoming-webhook-url>'
+    }
+  }
+}
+```
+
+Now the single words will be printed in the log but also forwarded to the
+slack channel named `#camel-k-tests`.
+
+You have infinite possibilities with Camel!
\ No newline at end of file
diff --git a/examples/knative/feed.groovy b/examples/knative/feed.groovy
new file mode 100644
index 0000000..1e86907
--- /dev/null
+++ b/examples/knative/feed.groovy
@@ -0,0 +1,4 @@
+from('timer:clock?period=3s')
+ .setBody().constant("Hello World from Camel K")
+ .to('knative:channel/messages')
+ .log('sent message to messages channel')
\ No newline at end of file
diff --git a/examples/knative/messages-channel.yaml b/examples/knative/messages-channel.yaml
new file mode 100644
index 0000000..2dcd271
--- /dev/null
+++ b/examples/knative/messages-channel.yaml
@@ -0,0 +1,9 @@
+apiVersion: eventing.knative.dev/v1alpha1
+kind: Channel
+metadata:
+  name: messages
+spec:
+  provisioner:
+    apiVersion: eventing.knative.dev/v1alpha1
+    kind: ClusterChannelProvisioner
+    name: in-memory-channel
\ No newline at end of file
diff --git a/examples/knative/printer.groovy b/examples/knative/printer.groovy
new file mode 100644
index 0000000..58a0068
--- /dev/null
+++ b/examples/knative/printer.groovy
@@ -0,0 +1,4 @@
+
+from('knative:channel/words')
+  .convertBodyTo(String.class)
+  .to('log:info')
diff --git a/examples/knative/splitter.groovy b/examples/knative/splitter.groovy
new file mode 100644
index 0000000..9e848e3
--- /dev/null
+++ b/examples/knative/splitter.groovy
@@ -0,0 +1,5 @@
+
+from('knative:channel/messages')
+  .split().tokenize(" ")
+  .log('sending ${body} to words channel')
+  .to('knative:channel/words')
\ No newline at end of file
diff --git a/examples/knative/words-channel.yaml b/examples/knative/words-channel.yaml
new file mode 100644
index 0000000..ad8640f
--- /dev/null
+++ b/examples/knative/words-channel.yaml
@@ -0,0 +1,9 @@
+apiVersion: eventing.knative.dev/v1alpha1
+kind: Channel
+metadata:
+  name: words
+spec:
+  provisioner:
+    apiVersion: eventing.knative.dev/v1alpha1
+    kind: ClusterChannelProvisioner
+    name: in-memory-channel
\ No newline at end of file
diff --git a/runtime/examples/kotlin-routes.kts b/examples/kotlin-routes.kts
similarity index 64%
rename from runtime/examples/kotlin-routes.kts
rename to examples/kotlin-routes.kts
index 8b15930..1d6ce6c 100644
--- a/runtime/examples/kotlin-routes.kts
+++ b/examples/kotlin-routes.kts
@@ -1,11 +1,11 @@
 //
 // To run this integrations use:
 //
-//     kamel run --runtime kotlin runtime/examples/kotlin-routes.kts
+//     kamel run --runtime kotlin examples/kotlin-routes.kts
 //
 // Or leveraging runtime detection
 //
-//     kamel run runtime/examples/kotlin-routes.kts
+//     kamel run examples/kotlin-routes.kts
 //
 
 val rnd = java.util.Random()
diff --git a/runtime/examples/props.js b/examples/props.js
similarity index 50%
rename from runtime/examples/props.js
rename to examples/props.js
index a89b6ae..f3cde88 100644
--- a/runtime/examples/props.js
+++ b/examples/props.js
@@ -1,7 +1,7 @@
 //
 // To run this integrations use:
 //
-//     kamel run -p my.message=test-props runtime/examples/props.js
+//     kamel run -p my.message=test-props examples/props.js
 //
 
 from('timer:props?period=1s')
diff --git a/runtime/examples/routes-rest.js b/examples/routes-rest.js
similarity index 87%
rename from runtime/examples/routes-rest.js
rename to examples/routes-rest.js
index 97d41ec..34981a2 100644
--- a/runtime/examples/routes-rest.js
+++ b/examples/routes-rest.js
@@ -1,7 +1,7 @@
 //
 // To run this integrations use:
 //
-//     kamel run --name=withrest --dependency=camel-undertow runtime/examples/routes-rest.js
+//     kamel run --name=withrest --dependency=camel-undertow examples/routes-rest.js
 //
 
 // ****************
diff --git a/runtime/examples/routes.groovy b/examples/routes.groovy
similarity index 83%
rename from runtime/examples/routes.groovy
rename to examples/routes.groovy
index aef7ad3..c5d3b69 100644
--- a/runtime/examples/routes.groovy
+++ b/examples/routes.groovy
@@ -3,11 +3,11 @@ import java.util.concurrent.ThreadLocalRandom
 //
 // To run this integrations use:
 //
-//     kamel run --runtime groovy runtime/examples/routes.groovy
+//     kamel run --runtime groovy examples/routes.groovy
 //
 // Or leveraging runtime detection
 //
-//     kamel run runtime/examples/routes.groovy
+//     kamel run examples/routes.groovy
 //
 
 context {
diff --git a/runtime/examples/routes.js b/examples/routes.js
similarity index 100%
rename from runtime/examples/routes.js
rename to examples/routes.js
diff --git a/runtime/examples/simple.groovy b/examples/simple.groovy
similarity index 100%
rename from runtime/examples/simple.groovy
rename to examples/simple.groovy
diff --git a/runtime/examples/simple.js b/examples/simple.js
similarity index 100%
rename from runtime/examples/simple.js
rename to examples/simple.js
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..796670a 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/catalog.go
@@ -39,6 +39,7 @@ type Catalog struct {
  tOwner        Trait
  tBuilder      Trait
  tSpringBoot   Trait
+ tIstio Trait
 }
 
 // NewCatalog creates a new trait Catalog
@@ -54,6 +55,7 @@ func NewCatalog() *Catalog {
  tOwner:        newOwnerTrait(),
  tBuilder:      newBuilderTrait(),
  tSpringBoot:   newSpringBootTrait(),
+ tIstio: newIstioTrait(),
  }
 }
 
@@ -69,6 +71,7 @@ func (c *Catalog) allTraits() []Trait {
  c.tOwner,
  c.tBuilder,
  c.tSpringBoot,
+ c.tIstio,
  }
 }
 
@@ -78,32 +81,33 @@ 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.tIstio,
  c.tOwner,
  }
  }
diff --git a/pkg/trait/debug.go b/pkg/trait/debug.go
index f74896f..e5e40d9 100644
--- a/pkg/trait/debug.go
+++ b/pkg/trait/debug.go
@@ -35,6 +35,14 @@ func (r *debugTrait) appliesTo(e *Environment) bool {
  return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
 }
 
+func (r *debugTrait) autoconfigure(e *Environment) error {
+ if r.Enabled == nil {
+ enabled := false
+ r.Enabled = &enabled
+ }
+ return nil
+}
+
 func (r *debugTrait) apply(e *Environment) error {
  // this is all that's needed as long as the base image is `fabric8/s2i-java` look into builder/builder.go
  e.EnvVars["JAVA_DEBUG"] = True
diff --git a/pkg/trait/istio.go b/pkg/trait/istio.go
new file mode 100644
index 0000000..765cbe8
--- /dev/null
+++ b/pkg/trait/istio.go
@@ -0,0 +1,64 @@
+/*
+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 trait
+
+import (
+ "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+ serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
+ appsv1 "k8s.io/api/apps/v1"
+)
+
+type istioTrait struct {
+ BaseTrait `property:",squash"`
+ Allow     string `property:"allow"`
+}
+
+const (
+ istioIncludeAnnotation = "traffic.sidecar.istio.io/includeOutboundIPRanges"
+)
+
+func newIstioTrait() *istioTrait {
+ return &istioTrait{
+ BaseTrait: newBaseTrait("istio"),
+ Allow:     "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16",
+ }
+}
+
+func (t *istioTrait) appliesTo(e *Environment) bool {
+ return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
+}
+
+func (t *istioTrait) apply(e *Environment) error {
+ if t.Allow != "" {
+ e.Resources.VisitDeployment(func(d *appsv1.Deployment) {
+ d.Spec.Template.Annotations = t.injectIstioAnnotation(d.Spec.Template.Annotations)
+ })
+ e.Resources.VisitKnativeConfigurationSpec(func(cs *serving.ConfigurationSpec) {
+ cs.RevisionTemplate.Annotations = t.injectIstioAnnotation(cs.RevisionTemplate.Annotations)
+ })
+ }
+ return nil
+}
+
+func (t *istioTrait) injectIstioAnnotation(annotations map[string]string) map[string]string {
+ if annotations == nil {
+ annotations = make(map[string]string)
+ }
+ annotations[istioIncludeAnnotation] = t.Allow
+ return annotations
+}
diff --git a/pkg/trait/knative.go b/pkg/trait/knative.go
index 0e211f1..716392a 100644
--- a/pkg/trait/knative.go
+++ b/pkg/trait/knative.go
@@ -20,10 +20,11 @@ package trait
 import (
  "encoding/json"
  "fmt"
- "strings"
 
  "github.com/operator-framework/operator-sdk/pkg/sdk"
  "github.com/pkg/errors"
+ "strconv"
+ "strings"
 
  "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 
@@ -35,10 +36,17 @@ import (
  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
+const (
+ knativeMinScaleAnnotation = "autoscaling.knative.dev/minScale"
+ knativeMaxScaleAnnotation = "autoscaling.knative.dev/maxScale"
+)
+
 type knativeTrait struct {
  BaseTrait `property:",squash"`
  Sources   string `property:"sources"`
  Sinks     string `property:"sinks"`
+ MinScale  *int   `property:"minScale"`
+ MaxScale  *int   `property:"maxScale"`
 }
 
 func newKnativeTrait() *knativeTrait {
@@ -60,10 +68,21 @@ func (t *knativeTrait) autoconfigure(e *Environment) error {
  channels := t.getSinkChannels(e)
  t.Sinks = strings.Join(channels, ",")
  }
+ // Check the right value for minScale, as not all services are allowed to scale down to 0
+ if t.MinScale == nil {
+ meta := metadata.ExtractAll(e.Integration.Spec.Sources)
+ if !meta.RequiresHTTPService || !meta.PassiveEndpoints {
+ single := 1
+ t.MinScale = &single
+ }
+ }
  return nil
 }
 
 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)
  }
@@ -75,6 +94,16 @@ func (t *knativeTrait) apply(e *Environment) error {
  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.EnvVars["CAMEL_KNATIVE_CONFIGURATION"] = conf
+ return nil
+}
+
 func (t *knativeTrait) getServiceFor(e *Environment) (*serving.Service, error) {
  // combine properties of integration with context, integration
  // properties have the priority
@@ -112,17 +141,23 @@ 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,
  }
 
+ annotations := make(map[string]string)
+ if t.MinScale != nil {
+ annotations[knativeMinScaleAnnotation] = strconv.Itoa(*t.MinScale)
+ }
+ if t.MaxScale != nil {
+ annotations[knativeMaxScaleAnnotation] = strconv.Itoa(*t.MaxScale)
+ }
+
  svc := serving.Service{
  TypeMeta: metav1.TypeMeta{
  Kind:       "Service",
@@ -138,6 +173,10 @@ func (t *knativeTrait) getServiceFor(e *Environment) (*serving.Service, error) {
  RunLatest: &serving.RunLatestType{
  Configuration: serving.ConfigurationSpec{
  RevisionTemplate: serving.RevisionTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Labels:      labels,
+ Annotations: annotations,
+ },
  Spec: serving.RevisionSpec{
  Container: corev1.Container{
  Image: e.Integration.Status.Image,
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index a7c927a..0efaf45 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -19,24 +19,12 @@ 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"
  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,7 +44,8 @@ func (s *serviceTrait) appliesTo(e *Environment) bool {
 
 func (s *serviceTrait) autoconfigure(e *Environment) error {
  if s.Enabled == nil {
- required := s.requiresService(e)
+ meta := metadata.ExtractAll(e.Integration.Spec.Sources)
+ required := meta.RequiresHTTPService
  s.Enabled = &required
  }
  return nil
@@ -98,28 +87,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..d6b7b72 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,47 @@ 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 idx := range d.Spec.Template.Spec.Containers {
+ c := &d.Spec.Template.Spec.Containers[idx]
+ visitor(c)
+ }
+ })
+ c.VisitKnativeConfigurationSpec(func(cs *serving.ConfigurationSpec) {
+ c := &cs.RevisionTemplate.Spec.Container
+ visitor(c)
+ })
+}
+
+// VisitKnativeConfigurationSpec executes the visitor function on all knative ConfigurationSpec inside serving Services
+func (c *Collection) VisitKnativeConfigurationSpec(visitor func(container *serving.ConfigurationSpec)) {
+ c.VisitKnativeService(func(s *serving.Service) {
+ if s.Spec.RunLatest != nil {
+ c := &s.Spec.RunLatest.Configuration
+ visitor(c)
+ }
+ if s.Spec.Pinned != nil {
+ c := &s.Spec.Pinned.Configuration
+ visitor(c)
+ }
+ if s.Spec.Release != nil {
+ c := &s.Spec.Release.Configuration
+ 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) {
diff --git a/pkg/util/kubernetes/replace.go b/pkg/util/kubernetes/replace.go
index 45b908d..ec14b16 100644
--- a/pkg/util/kubernetes/replace.go
+++ b/pkg/util/kubernetes/replace.go
@@ -25,6 +25,7 @@ import (
  k8serrors "k8s.io/apimachinery/pkg/api/errors"
  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  "k8s.io/apimachinery/pkg/runtime"
+ eventing "github.com/knative/eventing/pkg/apis/eventing/v1alpha1"
 )
 
 // ReplaceResources allows to completely replace a list of resources on Kubernetes, taking care of immutable fields and resource versions
@@ -50,6 +51,7 @@ func ReplaceResource(res runtime.Object) error {
  mapRequiredMeta(existing, res)
  mapRequiredServiceData(existing, res)
  mapRequiredRouteData(existing, res)
+ mapRequiredKnativeData(existing, res)
  err = sdk.Update(res)
  }
  if err != nil {
@@ -82,6 +84,14 @@ func mapRequiredRouteData(from runtime.Object, to runtime.Object) {
  }
 }
 
+func mapRequiredKnativeData(from runtime.Object, to runtime.Object) {
+ if fromC, ok := from.(*eventing.Subscription); ok {
+ if toC, ok := to.(*eventing.Subscription); ok {
+ toC.Spec.Generation = fromC.Spec.Generation
+ }
+ }
+}
+
 func findResourceDetails(res runtime.Object) string {
  if res == nil {
  return "nil resource"


 

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


With regards,
Apache Git Services