This is an automated email from the ASF dual-hosted git repository.
pcongiusti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git
The following commit(s) were added to refs/heads/main by this push:
new 3a491c44b feat(trait): gateway
3a491c44b is described below
commit 3a491c44b317c3834ba71850e59872e4d9fb7e53
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Sat Mar 21 16:16:27 2026 +0100
feat(trait): gateway
Closes #5072
---
.github/workflows/gateway.yml | 99 ++++++++++
docs/modules/ROOT/partials/apis/camel-k-crds.adoc | 41 +++++
e2e/gateway/files/PlatformHttpServer.java | 26 +++
e2e/gateway/gateway_test.go | 74 ++++++++
e2e/gateway/setup/gateway.yaml | 23 +++
e2e/gateway/setup/setup.sh | 28 +++
e2e/support/test_support.go | 50 ++++-
go.mod | 1 +
helm/camel-k/crds/camel-k-crds.yaml | 144 +++++++++++++++
pkg/apis/addtoscheme_gateway.go | 27 +++
pkg/apis/camel/v1/common_types.go | 2 +
pkg/apis/camel/v1/trait/gateway.go | 36 ++++
pkg/apis/camel/v1/trait/zz_generated.deepcopy.go | 16 ++
pkg/apis/camel/v1/zz_generated.deepcopy.go | 5 +
.../camel/applyconfiguration/camel/v1/traits.go | 10 +
pkg/client/client.go | 3 +-
.../camel.apache.org_integrationplatforms.yaml | 36 ++++
.../camel.apache.org_integrationprofiles.yaml | 36 ++++
.../crd/bases/camel.apache.org_integrations.yaml | 36 ++++
.../config/crd/bases/camel.apache.org_pipes.yaml | 36 ++++
.../rbac/descoped/operator-cluster-role.yaml | 12 ++
.../config/rbac/namespaced/operator-role.yaml | 12 ++
pkg/trait/gateway.go | 205 +++++++++++++++++++++
pkg/trait/gateway_test.go | 162 ++++++++++++++++
pkg/trait/trait_register.go | 1 +
script/Makefile | 6 +
26 files changed, 1121 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/gateway.yml b/.github/workflows/gateway.yml
new file mode 100644
index 000000000..052efde3a
--- /dev/null
+++ b/.github/workflows/gateway.yml
@@ -0,0 +1,99 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+name: gateway
+
+env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+on:
+ pull_request:
+ branches:
+ - main
+ - "release-*"
+ paths-ignore:
+ - 'docs/**'
+ - 'java/**'
+ - 'proposals/**'
+ - '**.adoc'
+ - '**.md'
+ - 'KEYS'
+ - 'LICENSE'
+ - 'NOTICE'
+ push:
+ branches:
+ - main
+ - "release-*"
+ paths-ignore:
+ - 'docs/**'
+ - 'java/**'
+ - 'proposals/**'
+ - '**.adoc'
+ - '**.md'
+ - 'KEYS'
+ - 'LICENSE'
+ - 'NOTICE'
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number ||
github.sha }}
+ cancel-in-progress: true
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ submodules: recursive
+
+ - name: Infra setting
+ uses: ./.github/actions/infra-setting
+
+ - name: Install Envoy
+ shell: bash
+ run: |
+ ./e2e/gateway/setup/setup.sh
+
+ - name: Install operator
+ shell: bash
+ run: |
+ kubectl create ns camel-k
+ make install-k8s-global
+ kubectl wait --for=jsonpath='{.status.phase}'=Ready itp camel-k -n
camel-k --timeout=60s
+
+ - name: Run test
+ shell: bash
+ run: |
+ set -euo pipefail
+ # Cleanup function to stop tunnel
+ cleanup() {
+ echo "** Stopping Minikube tunnel"
+ if [[ -n "${TUNNEL_PID-}" ]]; then
+ kill "$TUNNEL_PID" || true
+ fi
+ }
+ trap cleanup EXIT
+
+ echo "** Starting Minikube tunnel (requires sudo)"
+ minikube tunnel &
+ TUNNEL_PID=$!
+
+ DO_TEST_PREBUILD=false GOTESTFMT="-json 2>&1 | gotestfmt" make
test-gateway
diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
index ed08e799a..43a395ddf 100644
--- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
+++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
@@ -6046,6 +6046,13 @@ The configuration of Error Handler trait.
Deprecated: no longer in use.
+|`gateway` +
+*xref:#_camel_apache_org_v1_trait_GatewayTrait[GatewayTrait]*
+|
+
+
+The configuration of Istio trait
+
|`gc` +
*xref:#_camel_apache_org_v1_trait_GCTrait[GCTrait]*
|
@@ -7328,6 +7335,39 @@ Discovery client cache to be used, either `disabled`,
`disk` or `memory` (defaul
Deprecated: no longer in use.
+|===
+
+[#_camel_apache_org_v1_trait_GatewayTrait]
+=== GatewayTrait
+
+*Appears on:*
+
+* <<#_camel_apache_org_v1_Traits, Traits>>
+
+The Gateway trait can be used to expose the service associated with the
integration
+to the outside world with a Kubernetes Gateway API.
+
+
+[cols="2,2a",options="header"]
+|===
+|Field
+|Description
+
+|`Trait` +
+*xref:#_camel_apache_org_v1_trait_Trait[Trait]*
+|(Members of `Trait` are embedded into this type.)
+
+
+
+
+|`className` +
+string
+|
+
+
+The class name to use for the gateway configuration.
+
+
|===
[#_camel_apache_org_v1_trait_GitOpsTrait]
@@ -9688,6 +9728,7 @@ The list of taints to tolerate, in the form
`Key[=Value]:Effect[:Seconds]`
* <<#_camel_apache_org_v1_trait_AffinityTrait, AffinityTrait>>
* <<#_camel_apache_org_v1_trait_CronTrait, CronTrait>>
* <<#_camel_apache_org_v1_trait_GCTrait, GCTrait>>
+* <<#_camel_apache_org_v1_trait_GatewayTrait, GatewayTrait>>
* <<#_camel_apache_org_v1_trait_GitOpsTrait, GitOpsTrait>>
* <<#_camel_apache_org_v1_trait_HealthTrait, HealthTrait>>
* <<#_camel_apache_org_v1_trait_IngressTrait, IngressTrait>>
diff --git a/e2e/gateway/files/PlatformHttpServer.java
b/e2e/gateway/files/PlatformHttpServer.java
new file mode 100644
index 000000000..31d8d19f3
--- /dev/null
+++ b/e2e/gateway/files/PlatformHttpServer.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+import org.apache.camel.builder.RouteBuilder;
+
+public class PlatformHttpServer extends RouteBuilder {
+ @Override
+ public void configure() throws Exception {
+ from("platform-http:/hello?httpMethodRestrict=GET")
+ .setBody(simple("Hello ${header.name}"));
+ }
+}
diff --git a/e2e/gateway/gateway_test.go b/e2e/gateway/gateway_test.go
new file mode 100644
index 000000000..cea4e616e
--- /dev/null
+++ b/e2e/gateway/gateway_test.go
@@ -0,0 +1,74 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go ->
Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+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 gateway
+
+import (
+ "context"
+ "os/exec"
+ "testing"
+
+ . "github.com/apache/camel-k/v2/e2e/support"
+ v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+ . "github.com/onsi/gomega"
+ corev1 "k8s.io/api/core/v1"
+)
+
+func TestGatewayTrait(t *testing.T) {
+ t.Parallel()
+ WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) {
+ g.Expect(KamelRun(t, ctx, ns, "files/PlatformHttpServer.java",
+ "-t", "gateway.enabled=true",
+ "-t", "gateway.class-name=envoy",
+ ).Execute()).To(Succeed())
+ g.Eventually(IntegrationConditionStatus(t, ctx, ns,
"platform-http-server",
+ v1.IntegrationConditionReady),
TestTimeoutMedium).Should(Equal(corev1.ConditionTrue))
+ g.Eventually(Gateway(t, ctx, ns, "platform-http-server"),
TestTimeoutShort).Should(Not(BeNil()))
+ // Wait for the address to be assigned
+ var gwAddress string
+
+ // IMPORTANT NOTE: this test would likely fail if the Envoy
gateway is not able
+ // to assign an address correctly. In our case we need to make
sure to run
+ // `minikube tunnel` before running this test. It requires sudo.
+
+ g.Eventually(func() string {
+ gw := Gateway(t, ctx, ns, "platform-http-server")()
+ if gw == nil || len(gw.Status.Addresses) == 0 {
+ return ""
+ }
+ gwAddress = string(gw.Status.Addresses[0].Value)
+
+ return gwAddress
+ }, TestTimeoutShort).ShouldNot(BeEmpty(), "expected gateway to
have an assigned address")
+
+ g.Eventually(func() (string, error) {
+ cmd := exec.Command("curl",
+ "-s",
+ "-H", "name: test!",
+ "http://"+gwAddress+":8080/hello",
+ )
+ out, err := cmd.CombinedOutput()
+
+ return string(out), err
+ }, TestTimeoutMedium).Should(ContainSubstring("Hello test!"))
+ })
+}
diff --git a/e2e/gateway/setup/gateway.yaml b/e2e/gateway/setup/gateway.yaml
new file mode 100644
index 000000000..be3172bad
--- /dev/null
+++ b/e2e/gateway/setup/gateway.yaml
@@ -0,0 +1,23 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+ name: envoy
+spec:
+ controllerName: gateway.envoyproxy.io/gatewayclass-controller
diff --git a/e2e/gateway/setup/setup.sh b/e2e/gateway/setup/setup.sh
new file mode 100755
index 000000000..ce647c12e
--- /dev/null
+++ b/e2e/gateway/setup/setup.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+TIMEOUT="150s"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+kubectl apply -f
https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
--server-side
+kubectl apply -f
https://github.com/envoyproxy/gateway/releases/latest/download/install.yaml
--server-side
+kubectl wait --for=condition=available deployment/envoy-gateway -n
envoy-gateway-system --timeout=$TIMEOUT
+
+# Install gateway classname (not available by default in envoy installation)
+kubectl apply -f $SCRIPT_DIR/gateway.yaml
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 77f86c854..00203f8cf 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -64,6 +64,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/utils/ptr"
+ gwv1 "sigs.k8s.io/gateway-api/apis/v1"
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -153,7 +154,6 @@ func TestContext() context.Context {
}
func TestClient(t *testing.T) client.Client {
-
if testClient != nil {
return testClient
}
@@ -1055,7 +1055,7 @@ func IntegrationSpecProfile(t *testing.T, ctx
context.Context, ns string, name s
func IntegrationStatusCapabilities(t *testing.T, ctx context.Context, ns
string, name string) func() []string {
return func() []string {
it := Integration(t, ctx, ns, name)()
- if it == nil || &it.Status == nil {
+ if it == nil {
return nil
}
return it.Status.Capabilities
@@ -1910,7 +1910,7 @@ func BuildPhase(t *testing.T, ctx context.Context, ns,
name string) func() v1.Bu
func BuildConditions(t *testing.T, ctx context.Context, ns, name string)
func() []v1.BuildCondition {
return func() []v1.BuildCondition {
build := Build(t, ctx, ns, name)()
- if build != nil && &build.Status != nil &&
build.Status.Conditions != nil {
+ if build != nil && build.Status.Conditions != nil {
return build.Status.Conditions
}
return nil
@@ -1920,7 +1920,7 @@ func BuildConditions(t *testing.T, ctx context.Context,
ns, name string) func()
func BuildCondition(t *testing.T, ctx context.Context, ns string, name string,
conditionType v1.BuildConditionType) func() *v1.BuildCondition {
return func() *v1.BuildCondition {
build := Build(t, ctx, ns, name)()
- if build != nil && &build.Status != nil &&
build.Status.Conditions != nil {
+ if build != nil && build.Status.Conditions != nil {
return build.Status.GetCondition(conditionType)
}
return &v1.BuildCondition{}
@@ -3129,3 +3129,45 @@ func ScaledObject(t *testing.T, ctx context.Context, ns,
name string) func() *ke
return &scaledObject
}
}
+
+// MinikubeTunnel is used to temporarily tunnel Minikube and return a function
to stop after it's used.
+func MinikubeTunnel(t *testing.T, ctx context.Context) func() {
+ log.Info("** Started Minikube tunnel")
+ cmd := exec.CommandContext(ctx,
+ "minikube", "tunnel",
+ )
+
+ if err := cmd.Start(); err != nil {
+ t.Fatalf("failed to start minikube tunnel: %v", err)
+ }
+
+ // Give tunnel a moment to establish
+ time.Sleep(2 * time.Second)
+
+ // Return a cleanup function
+ return func() {
+ log.Info("** Stopping Minikube tunnel")
+ if err := cmd.Process.Kill(); err != nil {
+ t.Logf("failed to kill minikube tunnel: %v", err)
+ }
+ }
+}
+
+// Gateway returns the gateway with the given name.
+func Gateway(t *testing.T, ctx context.Context, ns string, name string) func()
*gwv1.Gateway {
+ return func() *gwv1.Gateway {
+ gw := gwv1.Gateway{}
+ key := ctrl.ObjectKey{
+ Namespace: ns,
+ Name: name,
+ }
+ err := TestClient(t).Get(ctx, key, &gw)
+ if err != nil && k8serrors.IsNotFound(err) {
+ return nil
+ } else if err != nil {
+ failTest(t, err)
+ }
+
+ return &gw
+ }
+}
diff --git a/go.mod b/go.mod
index e3435c29e..7f05aab57 100644
--- a/go.mod
+++ b/go.mod
@@ -51,6 +51,7 @@ require (
knative.dev/pkg v0.0.0-20260120122510-4a022ed9999a
knative.dev/serving v0.48.1
sigs.k8s.io/controller-runtime v0.23.3
+ sigs.k8s.io/gateway-api v1.1.0
sigs.k8s.io/structured-merge-diff/v6 v6.3.2
)
diff --git a/helm/camel-k/crds/camel-k-crds.yaml
b/helm/camel-k/crds/camel-k-crds.yaml
index 1d1c28c75..5cc4e45b8 100644
--- a/helm/camel-k/crds/camel-k-crds.yaml
+++ b/helm/camel-k/crds/camel-k-crds.yaml
@@ -4362,6 +4362,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -6863,6 +6881,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -9256,6 +9292,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -11635,6 +11689,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -20875,6 +20947,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -23214,6 +23304,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -33817,6 +33925,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a
trait.
+ All traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -36083,6 +36209,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
diff --git a/pkg/apis/addtoscheme_gateway.go b/pkg/apis/addtoscheme_gateway.go
new file mode 100644
index 000000000..1cbae8e65
--- /dev/null
+++ b/pkg/apis/addtoscheme_gateway.go
@@ -0,0 +1,27 @@
+/*
+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 apis
+
+import (
+ gwv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+func init() {
+ // Register the types with the Scheme so the components can map objects
to GroupVersionKinds and back
+ AddToSchemes = append(AddToSchemes, gwv1.Install)
+}
diff --git a/pkg/apis/camel/v1/common_types.go
b/pkg/apis/camel/v1/common_types.go
index da4ccd4cb..9c23e52af 100644
--- a/pkg/apis/camel/v1/common_types.go
+++ b/pkg/apis/camel/v1/common_types.go
@@ -210,6 +210,8 @@ type Traits struct {
//
// Deprecated: no longer in use.
ErrorHandler *trait.ErrorHandlerTrait `json:"error-handler,omitempty"
property:"error-handler"`
+ // The configuration of Istio trait
+ Gateway *trait.GatewayTrait `json:"gateway,omitempty"
property:"gateway"`
// The configuration of GC trait
GC *trait.GCTrait `json:"gc,omitempty" property:"gc"`
// The configuration of GitOps trait
diff --git a/pkg/apis/camel/v1/trait/gateway.go
b/pkg/apis/camel/v1/trait/gateway.go
new file mode 100644
index 000000000..131048542
--- /dev/null
+++ b/pkg/apis/camel/v1/trait/gateway.go
@@ -0,0 +1,36 @@
+/*
+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
+
+// The Gateway trait can be used to expose the service associated with the
Integration
+// to the outside world with a Kubernetes Gateway API. The trait is in charge
to automatically discover associate the
+// Integration Service generated with a Gateway and an HTTPRoute resource
(HTTP/HTTPS protocol only supported).
+//
+// NOTE: if any other protocol is required, please create a request in order
to develop it.
+//
+// +camel-k:trait=gateway.
+//
+//nolint:godoclint
+type GatewayTrait struct {
+ Trait `json:",inline" property:",squash"`
+
+ // The class name to use for the gateway configuration.
+ ClassName string `json:"className,omitempty" property:"class-name"`
+ // The listeners in the format "port;protocol" (default, "8080;HTTP").
+ Listeners []string `json:"listeners,omitempty" property:"listeners"`
+}
diff --git a/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
b/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
index 8cd29b192..563b464bd 100644
--- a/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
@@ -440,6 +440,22 @@ func (in *GCTrait) DeepCopy() *GCTrait {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *GatewayTrait) DeepCopyInto(out *GatewayTrait) {
+ *out = *in
+ in.Trait.DeepCopyInto(&out.Trait)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new GatewayTrait.
+func (in *GatewayTrait) DeepCopy() *GatewayTrait {
+ if in == nil {
+ return nil
+ }
+ out := new(GatewayTrait)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
func (in *GitOpsTrait) DeepCopyInto(out *GitOpsTrait) {
*out = *in
diff --git a/pkg/apis/camel/v1/zz_generated.deepcopy.go
b/pkg/apis/camel/v1/zz_generated.deepcopy.go
index edfe45e01..875c8413e 100644
--- a/pkg/apis/camel/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1/zz_generated.deepcopy.go
@@ -3231,6 +3231,11 @@ func (in *Traits) DeepCopyInto(out *Traits) {
*out = new(trait.ErrorHandlerTrait)
(*in).DeepCopyInto(*out)
}
+ if in.Gateway != nil {
+ in, out := &in.Gateway, &out.Gateway
+ *out = new(trait.GatewayTrait)
+ (*in).DeepCopyInto(*out)
+ }
if in.GC != nil {
in, out := &in.GC, &out.GC
*out = new(trait.GCTrait)
diff --git a/pkg/client/camel/applyconfiguration/camel/v1/traits.go
b/pkg/client/camel/applyconfiguration/camel/v1/traits.go
index d86eece44..7f5ca128a 100644
--- a/pkg/client/camel/applyconfiguration/camel/v1/traits.go
+++ b/pkg/client/camel/applyconfiguration/camel/v1/traits.go
@@ -50,6 +50,8 @@ type TraitsApplyConfiguration struct {
//
// Deprecated: no longer in use.
ErrorHandler *trait.ErrorHandlerTrait `json:"error-handler,omitempty"`
+ // The configuration of Istio trait
+ Gateway *trait.GatewayTrait `json:"gateway,omitempty"`
// The configuration of GC trait
GC *trait.GCTrait `json:"gc,omitempty"`
// The configuration of GitOps trait
@@ -218,6 +220,14 @@ func (b *TraitsApplyConfiguration) WithErrorHandler(value
trait.ErrorHandlerTrai
return b
}
+// WithGateway sets the Gateway field in the declarative configuration to the
given value
+// and returns the receiver, so that objects can be built by chaining "With"
function invocations.
+// If called multiple times, the Gateway field is set to the value of the last
call.
+func (b *TraitsApplyConfiguration) WithGateway(value trait.GatewayTrait)
*TraitsApplyConfiguration {
+ b.Gateway = &value
+ return b
+}
+
// WithGC sets the GC field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With"
function invocations.
// If called multiple times, the GC field is set to the value of the last call.
diff --git a/pkg/client/client.go b/pkg/client/client.go
index 27ad8a038..db15ff9d3 100644
--- a/pkg/client/client.go
+++ b/pkg/client/client.go
@@ -110,7 +110,7 @@ func (c *defaultClient) GetCurrentNamespace(kubeConfig
string) (string, error) {
func NewOutOfClusterClient(kubeconfig string) (Client, error) {
initialize(kubeconfig)
// using fast discovery from outside the cluster
- return NewClient(true)
+ return NewClient(false)
}
// NewClient creates a new k8s client that can be used from outside or in the
cluster.
@@ -134,7 +134,6 @@ func NewClientWithConfig(fastDiscovery bool, cfg
*rest.Config) (Client, error) {
var err error
clientScheme := scheme.Scheme
if !clientScheme.IsVersionRegistered(v1.SchemeGroupVersion) {
- // Setup Scheme for all resources
err = apis.AddToScheme(clientScheme)
if err != nil {
return nil, err
diff --git
a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
index e0945c82a..9ae9a9666 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
@@ -1078,6 +1078,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -3579,6 +3597,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
diff --git
a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
index 425b16dad..15b66ec5c 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
@@ -936,6 +936,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -3315,6 +3333,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
index f87fb884b..7b8e85c99 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
@@ -7786,6 +7786,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -10125,6 +10143,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
diff --git a/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml
b/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml
index 6b307e25d..880fb7179 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml
@@ -7841,6 +7841,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a
trait.
+ All traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
@@ -10107,6 +10125,24 @@ spec:
in application properties
type: string
type: object
+ gateway:
+ description: The configuration of Istio trait
+ properties:
+ className:
+ description: The class name to use for the gateway
configuration.
+ type: string
+ configuration:
+ description: |-
+ Legacy trait configuration parameters.
+
+ Deprecated: for backward compatibility.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ enabled:
+ description: Can be used to enable or disable a trait.
All
+ traits share this common property.
+ type: boolean
+ type: object
gc:
description: The configuration of GC trait
properties:
diff --git a/pkg/resources/config/rbac/descoped/operator-cluster-role.yaml
b/pkg/resources/config/rbac/descoped/operator-cluster-role.yaml
index 8a537581b..8ed6ee7ef 100644
--- a/pkg/resources/config/rbac/descoped/operator-cluster-role.yaml
+++ b/pkg/resources/config/rbac/descoped/operator-cluster-role.yaml
@@ -182,6 +182,18 @@ rules:
- get
- list
- patch
+# Required by gateway trait
+- apiGroups:
+ - gateway.networking.k8s.io
+ resources:
+ - gateways
+ - httproutes
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
# Roles and RoleBindings
- apiGroups:
- rbac.authorization.k8s.io
diff --git a/pkg/resources/config/rbac/namespaced/operator-role.yaml
b/pkg/resources/config/rbac/namespaced/operator-role.yaml
index 27a264bee..241087014 100644
--- a/pkg/resources/config/rbac/namespaced/operator-role.yaml
+++ b/pkg/resources/config/rbac/namespaced/operator-role.yaml
@@ -163,6 +163,18 @@ rules:
- get
- list
- patch
+# Required by gateway trait
+- apiGroups:
+ - gateway.networking.k8s.io
+ resources:
+ - gateways
+ - httproutes
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
# Required by mount trait
- apiGroups:
- ""
diff --git a/pkg/trait/gateway.go b/pkg/trait/gateway.go
new file mode 100644
index 000000000..2c5b30664
--- /dev/null
+++ b/pkg/trait/gateway.go
@@ -0,0 +1,205 @@
+/*
+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 (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+
+ v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+ traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/utils/ptr"
+ gwv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+const (
+ gatewayTraitID = "gateway"
+ gatewayTraitOrder = 2420
+
+ gatewayDefaultListener = "8080;HTTP"
+)
+
+type gatewayTrait struct {
+ BaseTrait
+ traitv1.GatewayTrait `property:",squash"`
+}
+
+func newGatewayTrait() Trait {
+ return &gatewayTrait{
+ BaseTrait: NewBaseTrait(gatewayTraitID, gatewayTraitOrder),
+ }
+}
+
+func (t *gatewayTrait) Configure(e *Environment) (bool, *TraitCondition,
error) {
+ if e.Integration == nil || !ptr.Deref(t.Enabled, false) ||
!e.IntegrationInRunningPhases() {
+ return false, nil, nil
+ }
+
+ if e.Resources.GetUserServiceForIntegration(e.Integration) == nil {
+ return false, NewIntegrationCondition(
+ "Gateway",
+ v1.IntegrationConditionServiceAvailable,
+ corev1.ConditionFalse,
+ v1.IntegrationConditionServiceNotAvailableReason,
+ "No service available. Skipping the trait execution",
+ ), nil
+ }
+
+ return true, nil, nil
+}
+
+func (t *gatewayTrait) Apply(e *Environment) error {
+ service := e.Resources.GetUserServiceForIntegration(e.Integration)
+ gwName := e.Integration.GetName()
+
+ gw, err := buildGateway(gwName, e.Integration.GetNamespace(),
t.ClassName, t.getListeners())
+ if err != nil {
+ return err
+ }
+ e.Resources.Add(gw)
+ servicePorts := extractPorts(service.Spec.Ports)
+ route := buildHTTPRoute(gwName, gw.GetName(), service.GetName(),
gw.GetNamespace(), servicePorts)
+ e.Resources.Add(route)
+
+ e.Integration.Status.SetCondition(
+ v1.IntegrationConditionExposureAvailable,
+ corev1.ConditionTrue,
+ "GatewayAvailable",
+ "Service is exposed via a Gateway and HTTPRoute named "+gwName,
+ )
+
+ return nil
+}
+
+func (t *gatewayTrait) getListeners() []string {
+ if t.Listeners != nil {
+ return t.Listeners
+ }
+
+ return []string{gatewayDefaultListener}
+}
+
+// buildGateway provides the gateway with the associated listeners.
+func buildGateway(name, namespace, className string, listeners []string)
(*gwv1.Gateway, error) {
+ gwListeners := make([]gwv1.Listener, 0, len(listeners))
+
+ for _, l := range listeners {
+ parts := strings.Split(l, ";")
+ if len(parts) != 2 {
+ return nil, errors.New("could not parse gateway
listener " + l)
+ }
+
+ port32, err := strconv.ParseInt(parts[0], 10, 32)
+ if err != nil {
+ return nil, errors.New("could not parse gateway port "
+ parts[0])
+ }
+ port := int32(port32)
+ protocol := strings.ToUpper(parts[1])
+ if !isSupported(protocol) {
+ return nil, errors.New("protocol gateway " + protocol +
" is not yet supported: open change request issue to project tracking")
+ }
+
+ listenerName := fmt.Sprintf("%s-%d", name, port)
+ gwListeners = append(gwListeners, gwv1.Listener{
+ Name: gwv1.SectionName(listenerName),
+ Port: gwv1.PortNumber(port),
+ Protocol: gwv1.ProtocolType(protocol),
+ AllowedRoutes: &gwv1.AllowedRoutes{
+ Namespaces: &gwv1.RouteNamespaces{
+ From: ptr.To(gwv1.NamespacesFromSame),
+ },
+ },
+ })
+ }
+
+ return &gwv1.Gateway{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: gwv1.SchemeGroupVersion.String(),
+ Kind: "Gateway",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ },
+ Spec: gwv1.GatewaySpec{
+ GatewayClassName: gwv1.ObjectName(className),
+ Listeners: gwListeners,
+ },
+ }, nil
+}
+
+func isSupported(protocol string) bool {
+ return protocol == "HTTP" || protocol == "HTTPS"
+}
+
+// buildHTTPRoute provides the most basic gateway builder method.
+func buildHTTPRoute(routeName, gatewayName, serviceName, namespace string,
servicePorts []int32) *gwv1.HTTPRoute {
+ rules := make([]gwv1.HTTPRouteRule, 0, len(servicePorts))
+
+ for _, p := range servicePorts {
+ port := gwv1.PortNumber(p)
+ rule := gwv1.HTTPRouteRule{
+ BackendRefs: []gwv1.HTTPBackendRef{
+ {
+ BackendRef: gwv1.BackendRef{
+ BackendObjectReference:
gwv1.BackendObjectReference{
+ Name:
gwv1.ObjectName(serviceName),
+ Port: ptr.To(port),
+ },
+ },
+ },
+ },
+ }
+
+ rules = append(rules, rule)
+ }
+
+ return &gwv1.HTTPRoute{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: gwv1.SchemeGroupVersion.String(),
+ Kind: "HTTPRoute",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: routeName,
+ Namespace: namespace,
+ },
+ Spec: gwv1.HTTPRouteSpec{
+ CommonRouteSpec: gwv1.CommonRouteSpec{
+ ParentRefs: []gwv1.ParentReference{
+ {
+ Name:
gwv1.ObjectName(gatewayName),
+ },
+ },
+ },
+ Rules: rules,
+ },
+ }
+}
+
+func extractPorts(ports []corev1.ServicePort) []int32 {
+ result := make([]int32, 0, len(ports))
+ for _, p := range ports {
+ result = append(result, p.Port)
+ }
+
+ return result
+}
diff --git a/pkg/trait/gateway_test.go b/pkg/trait/gateway_test.go
new file mode 100644
index 000000000..50da8dfc7
--- /dev/null
+++ b/pkg/trait/gateway_test.go
@@ -0,0 +1,162 @@
+/*
+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 (
+ "testing"
+
+ v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+ "github.com/apache/camel-k/v2/pkg/util/kubernetes"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/utils/ptr"
+ gwv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+func TestConfigureGatewayTraitDoesSucceed(t *testing.T) {
+ gwTrait, environment := createNominalGatewayTest()
+ gwTrait.ClassName = "my-gw-class"
+ configured, condition, err := gwTrait.Configure(environment)
+
+ require.NoError(t, err)
+ assert.True(t, configured)
+ assert.Nil(t, condition)
+ err = gwTrait.Apply(environment)
+ require.NoError(t, err)
+
+ // Assert gateway resource
+ var gateway gwv1.Gateway
+ environment.Resources.Visit(func(o runtime.Object) {
+ if conv, ok := o.(*gwv1.Gateway); ok {
+ gateway = *conv
+ return
+ }
+ })
+ assert.NotNil(t, gateway, "Could not find any generated Gateway")
+ assert.Equal(t, "integration-name", gateway.Name)
+ assert.Equal(t, gwv1.ObjectName("my-gw-class"),
gateway.Spec.GatewayClassName)
+ assert.Len(t, gateway.Spec.Listeners, 1)
+ assert.Equal(t, gwv1.ProtocolType("HTTP"),
gateway.Spec.Listeners[0].Protocol)
+ assert.Equal(t, gwv1.PortNumber(8080), gateway.Spec.Listeners[0].Port)
+
+ // Assert HTTPRoute resource
+ var httpRoute gwv1.HTTPRoute
+ environment.Resources.Visit(func(o runtime.Object) {
+ if conv, ok := o.(*gwv1.HTTPRoute); ok {
+ httpRoute = *conv
+ return
+ }
+ })
+ assert.NotNil(t, httpRoute, "Could not find any generated HTTPRoute")
+ assert.Len(t, httpRoute.Spec.ParentRefs, 1)
+ assert.Equal(t, gwv1.ObjectName(gateway.Name),
httpRoute.Spec.ParentRefs[0].Name)
+ assert.Len(t, httpRoute.Spec.Rules, 2)
+ assert.Contains(t, httpRoute.Spec.Rules,
+ gwv1.HTTPRouteRule{
+ BackendRefs: []gwv1.HTTPBackendRef{
+ {BackendRef:
gwv1.BackendRef{BackendObjectReference: gwv1.BackendObjectReference{
+ Name: "service-name", Port:
ptr.To(gwv1.PortNumber(1234)),
+ }}},
+ },
+ },
+ )
+ assert.Contains(t, httpRoute.Spec.Rules,
+ gwv1.HTTPRouteRule{
+ BackendRefs: []gwv1.HTTPBackendRef{
+ {BackendRef:
gwv1.BackendRef{BackendObjectReference: gwv1.BackendObjectReference{
+ Name: "service-name", Port:
ptr.To(gwv1.PortNumber(5678)),
+ }}},
+ },
+ },
+ )
+
+ // Verify Integration condition as well
+ assert.NotNil(t,
environment.Integration.Status.GetCondition(v1.IntegrationConditionExposureAvailable))
+ assert.Equal(t, corev1.ConditionTrue,
environment.Integration.Status.GetCondition(v1.IntegrationConditionExposureAvailable).Status)
+ assert.Equal(t, "Service is exposed via a Gateway and HTTPRoute named
integration-name",
+
environment.Integration.Status.GetCondition(v1.IntegrationConditionExposureAvailable).Message)
+}
+
+func TestConfigureGatewayTraitMissingService(t *testing.T) {
+ gwTrait, environment := createNominalGatewayTest()
+ gwTrait.ClassName = "my-gw-class"
+ environment.Resources.Remove(func(o runtime.Object) bool {
+ if _, ok := o.(*corev1.Service); ok {
+ return true
+ }
+
+ return false
+ })
+ configured, condition, err := gwTrait.Configure(environment)
+
+ require.NoError(t, err)
+ assert.False(t, configured)
+ assert.NotNil(t, condition)
+ assert.Contains(t, condition.message, "No service available")
+}
+
+func createNominalGatewayTest() (*gatewayTrait, *Environment) {
+ trait, _ := newGatewayTrait().(*gatewayTrait)
+ trait.Enabled = ptr.To(true)
+
+ environment := &Environment{
+ Catalog: NewCatalog(nil),
+ Integration: &v1.Integration{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "integration-name",
+ },
+ Status: v1.IntegrationStatus{
+ Phase: v1.IntegrationPhaseDeploying,
+ },
+ },
+ Resources: kubernetes.NewCollection(
+ &corev1.Service{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Service",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "service-name",
+ Namespace: "namespace",
+ Labels: map[string]string{
+ v1.IntegrationLabel:
"integration-name",
+
"camel.apache.org/service.type": v1.ServiceTypeUser,
+ },
+ },
+ Spec: corev1.ServiceSpec{
+ Ports: []corev1.ServicePort{
+ {
+ Port: 1234,
+ },
+ {
+ Port: 5678,
+ },
+ },
+ Selector: map[string]string{
+ v1.IntegrationLabel:
"integration-name",
+ },
+ },
+ },
+ ),
+ }
+
+ return trait, environment
+}
diff --git a/pkg/trait/trait_register.go b/pkg/trait/trait_register.go
index c233ca715..90b6a03c0 100644
--- a/pkg/trait/trait_register.go
+++ b/pkg/trait/trait_register.go
@@ -29,6 +29,7 @@ func init() {
AddToTraits(newDeployerTrait)
AddToTraits(newDeploymentTrait)
AddToTraits(newEnvironmentTrait)
+ AddToTraits(newGatewayTrait)
AddToTraits(newGCTrait)
AddToTraits(newGitTrait)
AddToTraits(newGitOpsTrait)
diff --git a/script/Makefile b/script/Makefile
index 2f577dae5..2a0ad5006 100644
--- a/script/Makefile
+++ b/script/Makefile
@@ -335,6 +335,12 @@ test-kafka:
test-telemetry:
go test -timeout 30m -v ./e2e/telemetry -tags=integration $(GOTESTFMT)
+#
+# Gateway tests that require the configuration of a gateway (Envoy)
+#
+test-gateway:
+ go test -timeout 10m -v ./e2e/gateway -tags=integration $(GOTESTFMT)
+
#
# Quarkus native test (requires certain CPU and memory conditions)
#