From 8aaeb815efe1a00ca65ea183949173a9dd8e2b83 Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Sat, 21 Mar 2026 16:16:27 +0100 Subject: [PATCH] feat(trait): gateway Closes #5072 --- .github/workflows/gateway.yml | 99 +++++++++ .../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 +++ .../camel/v1/trait/zz_generated.deepcopy.go | 16 ++ pkg/apis/camel/v1/zz_generated.deepcopy.go | 5 + .../applyconfiguration/camel/v1/traits.go | 10 + pkg/client/client.go | 3 +- ...camel.apache.org_integrationplatforms.yaml | 36 +++ .../camel.apache.org_integrationprofiles.yaml | 36 +++ .../bases/camel.apache.org_integrations.yaml | 36 +++ .../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(-) create mode 100644 .github/workflows/gateway.yml create mode 100644 e2e/gateway/files/PlatformHttpServer.java create mode 100644 e2e/gateway/gateway_test.go create mode 100644 e2e/gateway/setup/gateway.yaml create mode 100755 e2e/gateway/setup/setup.sh create mode 100644 pkg/apis/addtoscheme_gateway.go create mode 100644 pkg/apis/camel/v1/trait/gateway.go create mode 100644 pkg/trait/gateway.go create mode 100644 pkg/trait/gateway_test.go diff --git a/.github/workflows/gateway.yml b/.github/workflows/gateway.yml new file mode 100644 index 0000000000..052efde3a6 --- /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 0b2c1f4c49..9c8c9ea980 100644 --- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc +++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc @@ -6043,6 +6043,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]* | @@ -7325,6 +7332,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] @@ -9685,6 +9725,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 0000000000..31d8d19f3e --- /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 0000000000..cea4e616e2 --- /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 0000000000..be3172bade --- /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 0000000000..ce647c12e5 --- /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 77f86c8541..00203f8cf0 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 e3435c29ec..7f05aab576 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 e3d28d264c..9ae4832632 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: @@ -9264,6 +9300,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: @@ -11642,6 +11696,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: @@ -20882,6 +20954,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: @@ -23221,6 +23311,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: @@ -33824,6 +33932,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: @@ -36090,6 +36216,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 0000000000..1cbae8e659 --- /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 8bc6a73ba1..c04a1188e0 100644 --- a/pkg/apis/camel/v1/common_types.go +++ b/pkg/apis/camel/v1/common_types.go @@ -208,6 +208,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 0000000000..131048542b --- /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 8cd29b192e..563b464bd5 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 edfe45e01e..875c8413e8 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 d86eece44b..7f5ca128ae 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 27ad8a0382..db15ff9d37 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 e0945c82a0..9ae9a9666d 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 5b4e024143..ae81c51f31 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml @@ -944,6 +944,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: @@ -3322,6 +3340,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 f87fb884ba..7b8e85c990 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 6b307e25d0..880fb71791 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 8a537581b6..8ed6ee7ef4 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 27a264bee6..2410870142 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 0000000000..2c5b306649 --- /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 0000000000..50da8dfc7c --- /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 c233ca7153..90b6a03c0b 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 2f577dae59..2a0ad50061 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) #