diff --git a/.github/workflows/e2e-tf-deployment.yaml b/.github/workflows/e2e-tf-deployment.yaml index e41e56b2c..974afd029 100644 --- a/.github/workflows/e2e-tf-deployment.yaml +++ b/.github/workflows/e2e-tf-deployment.yaml @@ -15,6 +15,17 @@ on: pull_request_target: types: [ labeled ] workflow_dispatch: + inputs: + use_gateway_api: + description: 'Use Gateway API (Envoy Gateway) instead of NGINX Ingress' + required: false + type: boolean + default: false + terraform_branch: + description: 'Terraform repo branch to use (leave empty for latest LTS tag)' + required: false + type: string + default: '' jobs: test: @@ -40,6 +51,7 @@ jobs: TF_VAR_crowdstrike_kms_key_name: ${{ secrets.TF_VAR_CROWDSTRIKE_KMS_KEY_NAME }} TF_VAR_crowdstrike_aws_account_id: ${{ secrets.TF_VAR_CROWDSTRIKE_AWS_ACCOUNT_ID }} USE_DOMAIN: "true" + USE_GATEWAY_API: ${{ github.event.inputs.use_gateway_api || 'false' }} steps: - name: Checkout Helm charts @@ -82,11 +94,17 @@ jobs: - name: Checkout Deployment Automation run: | DEPLOYMENT_REPO='https://github.com/atlassian-labs/data-center-terraform.git' - LTS=$(git ls-remote --tags --exit-code --refs "$DEPLOYMENT_REPO" \ - | sed -E 's/^[[:xdigit:]]+[[:space:]]+refs\/tags\/(.+)/\1/g' \ - | grep '^[0-9]*.[0-9]*.[0-9]*$' | sort -V | tail -1) - echo "Using LTS version('${LTS}') of Deployment Automation to provision the infrastructure for Atlassian DC Products." - git clone -b $LTS $DEPLOYMENT_REPO tf + CUSTOM_BRANCH="${{ github.event.inputs.terraform_branch }}" + if [ -n "$CUSTOM_BRANCH" ]; then + echo "Using custom branch '${CUSTOM_BRANCH}' of Deployment Automation." + git clone -b $CUSTOM_BRANCH $DEPLOYMENT_REPO tf + else + LTS=$(git ls-remote --tags --exit-code --refs "$DEPLOYMENT_REPO" \ + | sed -E 's/^[[:xdigit:]]+[[:space:]]+refs\/tags\/(.+)/\1/g' \ + | grep '^[0-9]*.[0-9]*.[0-9]*$' | sort -V | tail -1) + echo "Using LTS version('${LTS}') of Deployment Automation to provision the infrastructure for Atlassian DC Products." + git clone -b $LTS $DEPLOYMENT_REPO tf + fi - name: Setup test environment uses: actions/setup-go@v3 @@ -148,6 +166,14 @@ jobs: crowd_install_local_chart = true EOF + # Append Gateway API toggle if enabled + if [ "$USE_GATEWAY_API" = "true" ]; then + echo 'use_gateway_api = true' >> ./e2etest/test-config.tfvars.tmpl + echo "Gateway API mode enabled (Envoy Gateway will replace NGINX Ingress)" + else + echo "NGINX Ingress mode (default)" + fi + # Deploy infrastructure, install helm charts, run e2e tests, and cleanup all # boto3 ignores AWS creds env vars for some reason mkdir -p ~/.aws diff --git a/.github/workflows/kind.yaml b/.github/workflows/kind.yaml index d35268acd..6d13da72c 100644 --- a/.github/workflows/kind.yaml +++ b/.github/workflows/kind.yaml @@ -71,7 +71,7 @@ jobs: - name: Verify ${{inputs.dc_app}} status run: | source src/test/scripts/kind/deploy_app.sh - verify_ingress + verify_gateway_ingress - name: Verify ${{inputs.dc_app}} grafana dashboards run: | @@ -90,6 +90,11 @@ jobs: source src/test/scripts/kind/deploy_app.sh verify_metrics + - name: Verify Gateway API integration + run: | + source src/test/scripts/kind/deploy_app.sh + verify_gateway + - name: Get debug info if: always() run: | diff --git a/.github/workflows/openshift.yaml b/.github/workflows/openshift.yaml index fb4751ef0..ac064c8dd 100644 --- a/.github/workflows/openshift.yaml +++ b/.github/workflows/openshift.yaml @@ -97,6 +97,98 @@ jobs: run: | oc apply -f src/test/config/openshift/shared-home-pvc.yaml + - name: Install Gateway API + Envoy Gateway + run: | + # Gateway API CRDs + oc apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml + oc apply --server-side=true -f https://raw.githubusercontent.com/envoyproxy/gateway/v1.2.5/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml + + oc wait --for condition=established --timeout=60s crd/gateways.gateway.networking.k8s.io + oc wait --for condition=established --timeout=60s crd/httproutes.gateway.networking.k8s.io + oc wait --for condition=established --timeout=60s crd/gatewayclasses.gateway.networking.k8s.io + oc wait --for condition=established --timeout=60s crd/envoyproxies.gateway.envoyproxy.io + + # Envoy Gateway (installs into envoy-gateway-system) + # OpenShift admission can reject fixed runAsUser/seccomp settings unless the + # service accounts are allowed to use a more permissive SCC. + oc create namespace envoy-gateway-system --dry-run=client -o yaml | oc apply -f - + # Pre-grant SCCs to all service accounts in the namespace so Helm hooks can run + # even before the chart-created ServiceAccounts exist. + oc adm policy add-scc-to-group anyuid system:serviceaccounts:envoy-gateway-system || true + oc adm policy add-scc-to-group privileged system:serviceaccounts:envoy-gateway-system || true + + helm install eg oci://docker.io/envoyproxy/gateway-helm \ + --version v1.2.5 \ + --namespace envoy-gateway-system \ + --set deployment.envoyGateway.resources.requests.cpu=50m \ + --set deployment.envoyGateway.resources.requests.memory=100Mi \ + --skip-crds \ + --timeout=600s \ + --wait || { \ + oc get all -n envoy-gateway-system || true; \ + oc get events -n envoy-gateway-system --sort-by='.lastTimestamp' | tail -n 200 || true; \ + exit 1; \ + } + + oc wait --for=condition=available deployment/envoy-gateway \ + --namespace envoy-gateway-system \ + --timeout=300s + + # EnvoyProxy CR configures the proxy Service as ClusterIP (default for OpenShift; an OpenShift Route will handle external access). + oc apply -f src/test/config/openshift/envoy-proxy.yaml + + # GatewayClass references the EnvoyProxy CR via parametersRef + cat << EOF | oc apply -f - + apiVersion: gateway.networking.k8s.io/v1 + kind: GatewayClass + metadata: + name: eg + spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: openshift-proxy-config + namespace: envoy-gateway-system + EOF + oc wait --for=condition=Accepted gatewayclass/eg --timeout=180s || { oc get gatewayclass/eg -o yaml; oc get pods -n envoy-gateway-system -o wide || true; exit 1; } + + # Create test Gateway + oc apply -f src/test/config/openshift/gateway.yaml + oc wait --for=condition=Accepted gateway/atlassian-gateway -n atlassian --timeout=300s || { oc describe gateway/atlassian-gateway -n atlassian; oc get events -n atlassian --sort-by='.lastTimestamp' | tail -n 200 || true; exit 1; } + + # Wait for data-plane + oc wait --for=condition=Available deployment \ + -n envoy-gateway-system \ + -l gateway.envoyproxy.io/owning-gateway-name=atlassian-gateway \ + --timeout=300s || { \ + oc get deployments -n envoy-gateway-system -o wide; \ + oc get pods -n envoy-gateway-system -o wide; \ + oc describe deployment -n envoy-gateway-system -l gateway.envoyproxy.io/owning-gateway-name=atlassian-gateway || true; \ + oc get events -n envoy-gateway-system --sort-by='.lastTimestamp' | tail -n 200 || true; \ + exit 1; \ + } + + # Create an OpenShift Route so atlassian.apps.crc.testing reaches the + # Envoy proxy Service without port-forward or Host headers. + ENVOY_SVC=$(oc get svc -n envoy-gateway-system \ + -l gateway.envoyproxy.io/owning-gateway-name=atlassian-gateway \ + -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) + + if [ -z "${ENVOY_SVC}" ]; then + echo "[ERROR]: Envoy Gateway proxy service not found" + oc get svc -n envoy-gateway-system -o wide || true + exit 1 + fi + + oc -n envoy-gateway-system expose svc/${ENVOY_SVC} \ + --name atlassian-gateway-proxy \ + --hostname atlassian.apps.crc.testing || true + + # Log the Route and Endpoint details for debugging + oc -n envoy-gateway-system get route atlassian-gateway-proxy -o yaml || true + oc -n envoy-gateway-system get endpoints ${ENVOY_SVC} -o yaml || true + - name: Deploy postgres database run: | source src/test/scripts/kind/deploy_app.sh @@ -117,7 +209,7 @@ jobs: run: | export OPENSHIFT_VALUES="1" source src/test/scripts/kind/deploy_app.sh - verify_ingress + verify_gateway_ingress - name: Verify ${{inputs.dc_app}} metrics availability run: | @@ -129,6 +221,11 @@ jobs: source src/test/scripts/kind/deploy_app.sh verify_openshift_analytics + - name: Verify Gateway API integration + run: | + source src/test/scripts/kind/deploy_app.sh + verify_gateway + - name: Get debug info if: always() run: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad062d8a6..4ba51efd0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,22 @@ For any pull requests, code owners should review the changes thoroughly and make For external contributions, any Github action workflow related changes are not acceptable. +## Windows Users + +This repository uses **symlinks** to share common Helm templates across product charts +(see `src/main/charts/common_templates/`). Git on Windows doesn't enable symlinks by default — without them, +Helm template rendering will fail. + +To enable symlinks on Windows: + +1. Enable **Developer Mode** in Windows Settings (Settings → Update & Security → For Developers), or run Git as Administrator +2. Configure Git to create real symlinks: + ``` + git config --global core.symlinks true + ``` +3. Re-clone the repository after changing this setting (existing clones won't retroactively fix symlinks) + + ### How to run E2E tests The Data Center Helm Charts uses the latest release of [Deployment Automation for Atlassian DC on K8s](https://github.com/atlassian-labs/data-center-terraform#deployment-automation-for-atlassian-dc-on-k8s) for end-to-end testing. Internal reviewers can run the tests by adding `e2e` label on a pull request (for external contributions, be mindful of the changes as it will run on internal cloud environment). \ No newline at end of file diff --git a/docs/docs/examples/ingress/CONTROLLERS.md b/docs/docs/examples/ingress/CONTROLLERS.md index 67ef8c8c9..700754c7c 100644 --- a/docs/docs/examples/ingress/CONTROLLERS.md +++ b/docs/docs/examples/ingress/CONTROLLERS.md @@ -1,6 +1,21 @@ -# Provisioning an Ingress controller -In order for the provided Ingress resource to work, your Kubernetes cluster must have an ingress controller running. The Atlassian Helm charts have been tested with the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/){.external}, however [alternatives can also be used](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/#additional-controllers){.external}. +# Provisioning a traffic entry controller -Here is an example of how these controllers can be installed and configured for use with the Atlassian Helm charts: +To expose an Atlassian DC product outside your Kubernetes cluster, you must run **one** of the following: -* [NGINX Ingress Controller](INGRESS_NGINX.md) +- an **Ingress controller** (to process Kubernetes `Ingress` resources), or +- a **Gateway API controller** (to process Kubernetes Gateway API resources, such as `HTTPRoute`). + +The Helm charts can render either: + +- a Kubernetes `Ingress` when `ingress.create: true`, or +- a Kubernetes Gateway API `HTTPRoute` when `gateway.create: true`. + +These options are **mutually exclusive** (you cannot enable both `ingress.create` and `gateway.create`). + +!!!note "Sticky sessions are required" + Atlassian DC products require **session stickiness** ("sticky sessions") for high availability. With NGINX Ingress this is handled via controller annotations. With Gateway API you must configure stickiness using your chosen Gateway implementation. + +## Example guides + +- [NGINX Ingress Controller (Ingress)](INGRESS_NGINX.md) +- [Gateway API controller (Gateway API)](GATEWAY_API.md) diff --git a/docs/docs/examples/ingress/GATEWAY_API.md b/docs/docs/examples/ingress/GATEWAY_API.md new file mode 100644 index 000000000..8cf10a5eb --- /dev/null +++ b/docs/docs/examples/ingress/GATEWAY_API.md @@ -0,0 +1,99 @@ +# Gateway API controller (HTTPRoute) + +The Atlassian DC Helm charts support exposing products via the **Kubernetes Gateway API** by rendering a `HTTPRoute` resource when `gateway.create: true`. + +To use this, your cluster must have: + +- Gateway API CRDs installed +- a Gateway API controller installed (for example, Envoy Gateway, Istio, etc.) +- a `Gateway` resource that allows routes from your product namespace + +!!!note "What the charts create" + The charts create a **`HTTPRoute`** only. You must provision the **`GatewayClass`**, **`Gateway`**, and (optionally) **TLS certificates** in your cluster. + +## 1. Install a Gateway API controller + +Follow your chosen implementation's installation instructions: + +- Gateway API overview: +- Implementations: + +## 2. Create a Gateway + +Create a `Gateway` that will accept `HTTPRoute` attachments from the namespace where you install the Atlassian product. + +The exact `gatewayClassName`, listener configuration, and TLS configuration depend on your chosen implementation. + +## 3. Configure the Helm chart + +Disable `ingress.create` and enable `gateway.create`. Provide a **parentRef** pointing to your `Gateway` and at least one **hostname**. + +```yaml +ingress: + create: false + +gateway: + create: true + hostnames: + - confluence.example.com + https: true + parentRefs: + - name: atlassian-gateway + namespace: gateway-system # optional, defaults to release namespace + sectionName: https # optional, target a specific Gateway listener +``` + +!!!info "TLS termination" + With Gateway API, TLS termination is configured on the `Gateway` listeners (not on the `HTTPRoute`). The `gateway.https` value controls the product's proxy/URL settings (e.g., generating HTTPS links), but it does not provision certificates by itself. + +## Gateway values reference + +The `gateway` stanza is split into two groups: + +**Product configuration** (always active when `gateway.hostnames` is set): + +| Value | Description | Default | +|-------|-------------|---------| +| `gateway.create` | Create an `HTTPRoute` resource | `false` | +| `gateway.hostnames` | Hostnames to route; first entry is used as the canonical hostname for base URL and proxy settings | `[]` | +| `gateway.https` | Whether users access the application over HTTPS | `true` | +| `gateway.externalPort` | Port users connect on; only set for non-standard ports | `443` (https) / `80` (http) | +| `gateway.path` | Base path; falls back to `.service.contextPath` when empty | (empty) | + +**HTTPRoute configuration** (only applies when `gateway.create: true`): + +| Value | Description | Default | +|-------|-------------|---------| +| `gateway.parentRefs` | List of [ParentReference](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference){.external} objects (`name`, `namespace`, `sectionName`, etc.) | `[]` (required) | +| `gateway.pathType` | Path matching type: `PathPrefix`, `Exact`, or `RegularExpression` | `PathPrefix` | +| `gateway.annotations` | Annotations to add to the HTTPRoute | `{}` | +| `gateway.labels` | Labels to add to the HTTPRoute | `{}` | +| `gateway.filters` | [HTTPRouteFilter](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteFilter){.external} list (header modification, redirects, URL rewrites) | `[]` | +| `gateway.timeouts.request` | Total request timeout | `60s` | +| `gateway.timeouts.backendRequest` | Backend request timeout | `60s` | +| `gateway.additionalRules` | Extra [HTTPRouteRule](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule){.external} entries for advanced routing | `[]` | + +!!!note "Using gateway config without creating an HTTPRoute" + Setting `gateway.hostnames` activates gateway mode for the product's proxy and base-URL configuration **even when `gateway.create` is false**. This is useful when you have a pre-existing Gateway or external proxy/load balancer and only need the Helm chart to configure the product itself, without creating any Kubernetes routing resource. + +## 4. Timeouts + +The `gateway.timeouts` block replaces the Ingress-style `proxyReadTimeout` / `proxySendTimeout` settings: + +```yaml +gateway: + timeouts: + request: "60s" # total request timeout + backendRequest: "60s" # backend request timeout +``` + +!!!warning "No Gateway API equivalents" + There is no standard Gateway API equivalent for `proxyConnectTimeout` or `maxBodySize`. If you need those, configure them through controller-specific policies (e.g. Envoy Gateway `BackendTrafficPolicy`). + + +## Configure session affinity (sticky sessions) + +Session affinity is **required** for Atlassian DC products and is **not** part of the standard `HTTPRoute` API. + +See [Session affinity with Gateway API](GATEWAY_API_SESSION_AFFINITY.md) for implementation-specific examples (cookie-based) and links for further reading. + diff --git a/docs/docs/examples/ingress/GATEWAY_API_SESSION_AFFINITY.md b/docs/docs/examples/ingress/GATEWAY_API_SESSION_AFFINITY.md new file mode 100644 index 000000000..0a3af5c05 --- /dev/null +++ b/docs/docs/examples/ingress/GATEWAY_API_SESSION_AFFINITY.md @@ -0,0 +1,70 @@ +# Session Affinity with Gateway API + +Atlassian DC products require **sticky sessions** so that a user is consistently routed to the same pod. With the NGINX Ingress controller this was handled automatically via annotations: + +```yaml +nginx.ingress.kubernetes.io/affinity: "cookie" +nginx.ingress.kubernetes.io/affinity-mode: "persistent" +``` + +With Gateway API, session affinity is **not** part of the standard `HTTPRoute` spec and must be configured separately. + +## Cookie naming + +Use a **dedicated routing cookie** (for example `ATLROUTE_`) rather than the application's own `JSESSIONID`. This avoids conflicts with the application's session cookie and matches the approach used by NGINX Ingress. + +## Options at a glance + +| Approach | Cookie-based | Standard channel | +|----------|:---:|:---:| +| Implementation policy (Option 1) | Yes | N/A (separate CRD) | +| Experimental `sessionPersistence` (Option 2) | Yes | No (experimental) | + +--- + +## Implementation-specific policies (recommended) + +Each Gateway API implementation provides its own policy resource for session affinity. Create the appropriate resource in the **same namespace** as your Helm release. + +### Envoy Gateway + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: -session-affinity + namespace: +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: - + loadBalancer: + type: ConsistentHash + consistentHash: + type: Cookie + cookie: + name: ATLROUTE_ + ttl: 10h +``` + +**Explore further:** + +- Envoy Gateway load balancing & session persistence: +- Envoy Gateway `BackendTrafficPolicy` API: +- Istio consistent-hash via `DestinationRule`: + +--- + +## Experimental `sessionPersistence` on HTTPRoute + +The Gateway API project has an **experimental** `sessionPersistence` field on HTTPRoute rules, tracked in **GEP-1619**. This field is **not** included in the standard-channel CRDs and will cause validation errors if those are installed. + +**Explore further:** + +- GEP-1619: +- `SessionPersistence` field reference: +- Experimental CRDs install (pick a release): + +!!!warning "Implementation support varies" + Even with experimental CRDs installed, not all Gateway implementations support `sessionPersistence`. Check your implementation's conformance/support documentation. diff --git a/docs/docs/userguide/CONFIGURATION.md b/docs/docs/userguide/CONFIGURATION.md index f18c06f3d..cd1628ac7 100644 --- a/docs/docs/userguide/CONFIGURATION.md +++ b/docs/docs/userguide/CONFIGURATION.md @@ -1,15 +1,22 @@ # Configuration ## :material-directions-fork: Ingress -In order to make the Atlassian product available from outside of the Kubernetes cluster, a suitable HTTP/HTTPS ingress controller needs to be installed. The standard Kubernetes Ingress resource is not flexible enough for our needs, so a third-party ingress controller and resource definition must be provided. The exact details of the Ingress will be highly site-specific. These Helm charts were tested using the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/){.external}. We also provide [example instructions](../examples/ingress/CONTROLLERS.md) on how this controller can be installed and configured. +In order to make the Atlassian product available from outside of the Kubernetes cluster, you must provision a suitable HTTP/HTTPS traffic entry controller. -The charts themselves provide a template for Ingress resource rules to be utilised by the provisioned controller. These include all required annotations and optional TLS configuration for the NGINX Ingress Controller. +The Helm charts support exposing products using either: + +- Kubernetes **Ingress** resources (via an Ingress controller), or +- Kubernetes **Gateway API** resources (via a Gateway API controller), by creating a `HTTPRoute`. + +The exact details will be highly site-specific. These Helm charts were tested using the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/){.external}. We also provide [example instructions](../examples/ingress/CONTROLLERS.md) on how controllers can be installed and configured. + +The charts themselves provide templates for either `Ingress` or `HTTPRoute` resources (depending on configuration). These include the required knobs for configuring hostnames, paths, timeouts, and (for NGINX Ingress) annotations. Some key considerations to note when configuring the controller are: !!!Ingress requirements * At a minimum, the ingress needs the ability to support long request timeouts, as well as session affinity (aka "sticky sessions"). - * The Ingress Resource provided as part of the Helm charts is geared toward the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/){.external} and can be configured via the `ingress` stanza in the appropriate `values.yaml`. Some key aspects that can be configured include: + * The `Ingress` template provided as part of the Helm charts is geared toward the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/){.external} and can be configured via the `ingress` stanza in the appropriate `values.yaml`. Some key aspects that can be configured include: * Usage of the NGINX Ingress Controller * Ingress Controller annotations @@ -78,6 +85,41 @@ curl -I https://bitbucket.example.com/status | Requests routing to different pods | Check if clustering is enabled | | Session cookies not working | Ensure ingress controller supports session affinity | +### Gateway API (HTTPRoute) + +The charts can create a Gateway API `HTTPRoute` instead of an `Ingress` resource. + +```yaml +ingress: + create: false + +gateway: + create: true + hostnames: + - bitbucket.example.com + https: true + parentRefs: + - name: atlassian-gateway + namespace: gateway-system # optional +``` + +The `gateway` stanza supports additional options for fine-tuning the `HTTPRoute`: + +- **`parentRefs`** – list of [ParentReference](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference){.external} objects pointing to your `Gateway` resource (supports `name`, `namespace`, `sectionName`, etc.) +- **`externalPort`** – non-standard port that users connect on (defaults to `443`/`80`) +- **`timeouts`** – `request` and `backendRequest` timeouts (replaces Ingress `proxyReadTimeout`/`proxySendTimeout`) +- **`filters`** – [HTTPRouteFilter](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteFilter){.external} list for header modification, redirects, or URL rewrites +- **`additionalRules`** – extra [HTTPRouteRule](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule){.external} entries for advanced routing (traffic splitting, header-based routing) +- **`annotations`** / **`labels`** – metadata applied to the HTTPRoute resource + +!!!note "Gateway mode without creating an HTTPRoute" + Setting `gateway.hostnames` activates gateway mode for the product's proxy and base-URL configuration even when `gateway.create` is false. This allows use with a pre-existing Gateway or external proxy/load balancer. + +For the full list of values and usage examples, see the [Gateway API guide](../examples/ingress/GATEWAY_API.md). + +!!!important "Sticky sessions with Gateway API" + Session affinity is **not** part of the standard Gateway API `HTTPRoute` spec. You must configure it using your Gateway implementation (for example, Envoy Gateway policy resources or Istio traffic policy). See [Session affinity with Gateway API](../examples/ingress/GATEWAY_API_SESSION_AFFINITY.md) for working examples and fallbacks. + ### Other Ingress Controllers For other ingress controllers ([AWS ALB](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/){.external}, [Google Cloud Load Balancer](https://cloud.google.com/load-balancing){.external}, [Azure Application Gateway](https://azure.microsoft.com/en-us/products/application-gateway){.external}), refer to your controller's documentation for session stickiness configuration. diff --git a/docs/docs/userguide/INSTALLATION.md b/docs/docs/userguide/INSTALLATION.md index 4a33a754c..79a35e0ce 100644 --- a/docs/docs/userguide/INSTALLATION.md +++ b/docs/docs/userguide/INSTALLATION.md @@ -68,7 +68,12 @@ database: Read about [Kubernetes secrets](https://kubernetes.io/docs/concepts/configuration/secret/){.external}. ## 4. Configure Ingress -Using the `values.yaml` file obtained in [step 2](#2-obtain-valuesyaml), configure the [Ingress controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/){.external} provisioned as part of the [Prerequisites](PREREQUISITES.md). The values you provide here will be used to provision an Ingress resource for the controller. Refer to the associated comments within the `values.yaml` file for additional details on how to configure the Ingress resource: +Using the `values.yaml` file obtained in [step 2](#2-obtain-valuesyaml), configure how the product will be exposed outside the cluster. You can use either: + +- a Kubernetes **Ingress** (requires an [Ingress controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/){.external}), or +- a Kubernetes Gateway API **HTTPRoute** (requires a Gateway API controller and a `Gateway`). + +The values you provide here will be used to provision either an `Ingress` or an `HTTPRoute` resource for your chosen controller. Refer to the associated comments within the `values.yaml` file for additional details. ```yaml ingress: @@ -83,10 +88,29 @@ ingress: tlsSecretName: ``` +Alternatively, to use Gateway API: + +```yaml +ingress: + create: false + +gateway: + create: true # Creates an HTTPRoute resource + hostnames: + - + https: true + parentRefs: + - name: + namespace: # optional, defaults to release namespace +``` + !!!info "Ingress configuration" For additional details on Ingress controllers see [the Ingress section of the configuration guide](CONFIGURATION.md#ingress). - See an example of [how to set up a controller](../examples/ingress/CONTROLLERS.md). + See an example of [how to set up an NGINX Ingress Controller](../examples/ingress/INGRESS_NGINX.md) and an overview of [controller options](../examples/ingress/CONTROLLERS.md). + +!!!info "Gateway API configuration" + For Gateway API exposure, start with the [Gateway API controller guide](../examples/ingress/GATEWAY_API.md) and then configure [session affinity](../examples/ingress/GATEWAY_API_SESSION_AFFINITY.md). The Gateway API guide includes a full values reference and examples for timeouts, filters, and advanced routing. ## 5. Configure persistent storage diff --git a/docs/docs/userguide/PREREQUISITES.md b/docs/docs/userguide/PREREQUISITES.md index 34df1105b..a583e86cf 100644 --- a/docs/docs/userguide/PREREQUISITES.md +++ b/docs/docs/userguide/PREREQUISITES.md @@ -12,7 +12,7 @@ In order to deploy Atlassian’s Data Center products, the following is required Before installing the Data Center Helm charts you need to set up your environment: 1. [Create and connect to the Kubernetes cluster](#create-and-connect-to-the-kubernetes-cluster) -2. [Provision an Ingress Controller](#provision-an-ingress-controller) +2. [Provision an Ingress or Gateway API controller](#provision-an-ingress-or-gateway-api-controller) 3. [Provision a database](#provision-a-database) 4. [Configure a shared-home volume](#configure-a-shared-home-volume) 5. [Configure a local-home volume](#configure-local-home-volume) @@ -30,16 +30,22 @@ Before installing the Data Center Helm charts you need to set up your environmen !!!example "" See examples of [provisioning Kubernetes clusters on cloud-based providers](../examples/cluster/CLOUD_PROVIDERS.md). -### :material-directions-fork: Provision an Ingress Controller + +### :material-directions-fork: Provision an Ingress or Gateway API controller * This step is necessary in order to make your Atlassian product available from outside of the Kubernetes cluster after deployment. -* The Kubernetes project supports and maintains ingress controllers for the major cloud providers including; [AWS](https://github.com/kubernetes-sigs/aws-load-balancer-controller#readme){.external}, [GCE](https://github.com/kubernetes/ingress-gce/blob/master/README.md#readme){.external} and [nginx](https://github.com/kubernetes/ingress-nginx/blob/master/README.md#readme){.external}. There are also a number of open-source [third-party projects available](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/){.external}. -* Because different Kubernetes clusters use different ingress configurations/controllers, the Helm charts provide [Ingress Object](https://kubernetes.io/docs/concepts/services-networking/ingress/){.external} templates only. -* The Ingress resource provided as part of the Helm charts is geared toward the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/){.external} and can be configured via the `ingress` stanza in the appropriate `values.yaml` (an alternative controller can be used). -* For more information about the Ingress controller go to the [Ingress section of the configuration guide](CONFIGURATION.md#ingress). +* You can expose the product using either: + * the Kubernetes **Ingress** API (requires an Ingress controller), or + * the Kubernetes **Gateway API** (requires a Gateway API controller). +* For Ingress, the Kubernetes project supports and maintains ingress controllers for the major cloud providers including; [AWS](https://github.com/kubernetes-sigs/aws-load-balancer-controller#readme){.external}, [GCE](https://github.com/kubernetes/ingress-gce/blob/master/README.md#readme){.external} and [nginx](https://github.com/kubernetes/ingress-nginx/blob/master/README.md#readme){.external}. There are also a number of open-source [third-party projects available](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/){.external}. +* For Gateway API, see the Gateway API project docs and the list of implementations: + * Gateway API overview: + * Implementations: +* The Helm charts can create either an `Ingress` (`ingress.create: true`) or an `HTTPRoute` (`gateway.create: true`) resource. These options are mutually exclusive. +* For more information about exposure options and required configuration, see the [Ingress section of the configuration guide](CONFIGURATION.md#ingress). !!!example "" - See an example of [provisioning an NGINX Ingress Controller](../examples/ingress/CONTROLLERS.md). + See examples of [provisioning an NGINX Ingress Controller](../examples/ingress/INGRESS_NGINX.md) and [Gateway API setup](../examples/ingress/GATEWAY_API.md). For an overview, see [Provisioning a traffic entry controller](../examples/ingress/CONTROLLERS.md). ### :material-database: Provision a database diff --git a/docs_internal/ROUTING.md b/docs_internal/ROUTING.md new file mode 100644 index 000000000..cc1ea1815 --- /dev/null +++ b/docs_internal/ROUTING.md @@ -0,0 +1,212 @@ +# Routing: Ingress vs Gateway Internals + +This document explains how the `common.gateway.*` template helpers abstract over +the `ingress` and `gateway` values sections, and why they are shaped this way. +Read this when you need to understand the full picture. + +## The two values sections + +Users configure external access via one of two values sections: + +```yaml +ingress: # K8s Ingress API path + host: ... + https: ... + path: ... + +gateway: # K8s Gateway API path (or external proxy) + hostnames: [ ... ] + https: ... + externalPort: ... + path: ... +``` + +Both sections carry two kinds of settings: + +1. **Product routing** — hostname, https, port, path. These tell the application + how users reach it (proxy settings, base URL, etc.). Present at the top of + each section. +2. **Resource creation** — className, annotations, gatewayName, filters, etc. + These only matter when `create: true` and configure the Ingress/HTTPRoute + resource. Present below the section divider in `values.yaml`. + +The product routing settings are conceptually identical across both sections — +only the key names differ (`host` vs `hostnames`, `port` vs `externalPort`). + +## Mode detection + +The helpers use two concepts to decide which values to read: + +| Helper | Returns `"true"` when | Purpose | +|------------------|----------------------------------------------|----------------------------------------------------| +| `useGatewayMode` | `gateway.hostnames` is non-empty | Determines which section to read values from | +| `isConfigured` | `ingress.host` or `gateway.hostnames` is set | Determines if external access is configured at all | + +Key design decisions: + +- **Mode is implicit.** Setting `gateway.hostnames` activates gateway mode. + There is no explicit toggle — `gateway.create` controls HTTPRoute creation, + not mode selection. +- **Mutual exclusion.** Setting both `ingress.host` and `gateway.hostnames` + is a validation error. The user must choose one path. +- **`gateway.create` only controls the HTTPRoute resource.** A user with a + pre-existing Gateway or external load balancer can set `gateway.hostnames` + without `gateway.create: true` and get correct product configuration + (NOTE: only the first hostname will be used in such case). + +## Helper dependency graph + +``` +isConfigured ──────────────────── used by: statefulset guards, NOTES.txt, bamboo.baseUrl +useGatewayMode ────┬───────────── used by: all helpers below + │ + ├─ https ───── scheme + ├─ hostname │ + ├─ externalPort│ + │ ▼ + │ origin ── used by: product baseUrl, NOTES.txt, SETUP_BASEURL + │ + └─ path ──────────── used by: product path helpers (jira.path, etc.) +``` + +## How values flow into the product + +The helpers feed into two main outputs: + +### 1. Environment variables (statefulset.yaml) + +Guarded by `isConfigured` — only set when a hostname is configured. + +| Env var | Helper | Products | +|----------------------------------------|-------------------|-----------| +| `ATL_PROXY_NAME` / `SERVER_PROXY_NAME` | `hostname` | all | +| `ATL_PROXY_PORT` / `SERVER_PROXY_PORT` | `externalPort` | all | +| `ATL_TOMCAT_SCHEME` / `SERVER_SCHEME` | `scheme` | all | +| `ATL_TOMCAT_SECURE` / `SERVER_SECURE` | `https` | all | +| `ATL_BASE_URL` | `origin` + `path` | bamboo | +| `SETUP_BASEURL` | `origin` | bitbucket | +| `SERVER_CONTEXT_PATH` | `path` | bitbucket | + +### 2. Tomcat server.xml (configmap-server-config.yaml) + +Products that generate `server.xml` via Helm (jira, confluence, bamboo, crowd) +set the `` proxy attributes using these helpers. The `proxyPort` +attribute uses `externalPort` as a default but can be overridden per-product +via `.tomcatConfig.proxyPort`. + +## Supported configurations + +### Standard: Ingress with resource creation + +```yaml +ingress: + create: true + host: app.example.com + https: true +``` + +Creates an Ingress resource. Product configured via `ingress.*` values. + +### Standard: Gateway API with HTTPRoute creation + +```yaml +gateway: + create: true + hostnames: [ app.example.com ] + https: true + gatewayName: my-gateway +``` + +Creates an HTTPRoute. Product configured via `gateway.*` values. + +### External proxy with gateway config (no resource created) + +```yaml +gateway: + create: false + hostnames: [ app.example.com ] + https: true + externalPort: 8443 +``` + +No K8s routing resource created. Product configured via `gateway.*` values. +Use this when traffic is routed by an external load balancer, a pre-existing +Gateway, or any proxy not managed by this chart. + +### External proxy with ingress config (legacy, no resource created) + +```yaml +ingress: + create: false + host: app.example.com + https: true +``` + +Same as above but using `ingress.*` values. This is the legacy way to configure +an external proxy. + +### Invalid: both configured + +```yaml +ingress: + host: app.example.com # ← cannot set both +gateway: + hostnames: [ app.example.com ] # ← cannot set both +``` + +Fails validation with: "Cannot set both gateway.hostnames and ingress.host". + +## Why "externalPort" instead of "port"? + +The `ingress.port` field existed as an undocumented, partially working feature. +It was confusing for two reasons: + +1. **Unclear purpose.** "Port" of what? Users would reasonably assume it + configures the Ingress resource itself — but it doesn't. It never appeared + in the generated Ingress spec. It only fed into the product's proxy env vars + (`ATL_PROXY_PORT` / `SERVER_PROXY_PORT`). +2. **Inconsistent support.** It worked for the proxy port env var but was + ignored by `bamboo.baseUrl` (so `ATL_BASE_URL` was wrong), ignored by all + NOTES.txt outputs (displayed URL omitted the port), and ignored by the + `configmap-server-config.yaml` `proxyPort` attribute (which had its own + separate default). + +The gateway section introduces `externalPort` to fix this: + +- **Clear name.** "External port" immediately communicates: this is the port + users hit to reach the application. Not an internal port, not a container + port, not a gateway listener port. +- **Fully wired.** It flows consistently into env vars, `origin` (URL building), + NOTES.txt output, and Tomcat server.xml configuration. +- **Documented.** It has a clear comment explaining that it does not change + the Gateway or load balancer — it must match the actual port in use. + +The `ingress.port` field is retained for backward compatibility but is not +documented in the default `values.yaml`. New users should use the `gateway` +section where `externalPort` provides a consistent, well-named alternative. + +## Why "common.gateway" naming? + +The helpers are namespaced under `common.gateway` even though they handle both +ingress and gateway cases. This is a forward-looking choice — the Gateway API +is the successor to the Ingress API in Kubernetes, and "gateway" as a general +concept ("the entry point for traffic") fits both use cases. Renaming to +something neutral like `common.routing` was considered but adds no clarity +for the extra churn. + +## Product-specific helpers + +Each product has a `.path` helper that wraps `common.gateway.path` +with the product's own `contextPath` default. The pattern is: + +``` +include "common.gateway.path" (dict + "useGatewayMode" (include "common.gateway.useGatewayMode" .) + "gatewayPath" .Values.gateway.path + "ingressPath" .Values.ingress.path + "contextPath" .Values..service.contextPath +) +``` + +Bamboo additionally has `bamboo.baseUrl` which combines `origin` + `path` +with a localhost fallback when no external access is configured. diff --git a/src/main/charts/bamboo-agent/.helmignore b/src/main/charts/bamboo-agent/.helmignore index 0e8a0eb36..c7423efa9 100644 --- a/src/main/charts/bamboo-agent/.helmignore +++ b/src/main/charts/bamboo-agent/.helmignore @@ -21,3 +21,5 @@ .idea/ *.tmproj .vscode/ +# Ignore non-template files from symlinked common_templates +templates/common_templates/*.md diff --git a/src/main/charts/bamboo/.helmignore b/src/main/charts/bamboo/.helmignore index 0e8a0eb36..c7423efa9 100644 --- a/src/main/charts/bamboo/.helmignore +++ b/src/main/charts/bamboo/.helmignore @@ -21,3 +21,5 @@ .idea/ *.tmproj .vscode/ +# Ignore non-template files from symlinked common_templates +templates/common_templates/*.md diff --git a/src/main/charts/bamboo/Chart.yaml b/src/main/charts/bamboo/Chart.yaml index a475a8f50..0decf82cc 100644 --- a/src/main/charts/bamboo/Chart.yaml +++ b/src/main/charts/bamboo/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: bamboo description: A chart for installing Bamboo Data Center on Kubernetes type: application -version: '2.0.9' +version: '2.0.10' appVersion: 12.1.3 kubeVersion: ">=1.21.x-0" keywords: @@ -20,7 +20,8 @@ deprecated: false annotations: artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: |- - - "Update appVersions for DC apps (#1074)" + - "Add Gateway API support via HTTPRoute resources" + - "Gateway API provides modern alternative to Ingress" dependencies: - name: common version: 1.2.7 diff --git a/src/main/charts/bamboo/templates/NOTES.txt b/src/main/charts/bamboo/templates/NOTES.txt index 4705f695d..b5c9524e5 100644 --- a/src/main/charts/bamboo/templates/NOTES.txt +++ b/src/main/charts/bamboo/templates/NOTES.txt @@ -13,8 +13,8 @@ To see the custom values you used for this release: $ helm get values {{ .Release.Name }} -n {{ .Release.Namespace }} -{{ if .Values.ingress.create -}} -{{ title .Chart.Name }} service URL: {{ ternary "https" "http" .Values.ingress.https -}}://{{ .Values.ingress.host }}{{ include "bamboo.ingressPath" . }} +{{ if eq (include "common.gateway.isConfigured" .) "true" -}} +{{ title .Chart.Name }} service URL: {{ include "bamboo.baseUrl" . }} {{- else }} Get the {{ title .Chart.Name }} URL by running these commands in the same shell: {{- if contains "NodePort" .Values.bamboo.service.type }} diff --git a/src/main/charts/bamboo/templates/_helpers.tpl b/src/main/charts/bamboo/templates/_helpers.tpl index 33b9529ff..cbda467ff 100644 --- a/src/main/charts/bamboo/templates/_helpers.tpl +++ b/src/main/charts/bamboo/templates/_helpers.tpl @@ -62,35 +62,30 @@ Deduce the base URL for bamboo. */}} {{- define "bamboo.baseUrl" -}} - {{- if .Values.ingress.host -}} - {{ ternary "https" "http" .Values.ingress.https -}} - :// - {{- if .Values.ingress.path -}} - {{ .Values.ingress.host}}{{.Values.ingress.path }} - {{- else -}} - {{ .Values.ingress.host}} - {{- end }} - {{- else -}} - {{- print "http://localhost:8085/" }} - {{- end }} +{{- if eq (include "common.gateway.isConfigured" .) "true" -}} +{{- include "common.gateway.origin" . -}}{{ include "bamboo.path" . -}} +{{- else -}} +{{- print "http://localhost:8085/" -}} +{{- end -}} {{- end }} {{/* -Create default value for ingress port +Create default value for the service path. */}} -{{- define "bamboo.ingressPort" -}} -{{ default (ternary "443" "80" .Values.ingress.https) .Values.ingress.port -}} +{{- define "bamboo.path" -}} +{{- include "common.gateway.path" (dict + "useGatewayMode" (include "common.gateway.useGatewayMode" .) + "gatewayPath" .Values.gateway.path + "ingressPath" .Values.ingress.path + "contextPath" .Values.bamboo.service.contextPath +) -}} {{- end }} {{/* -Create default value for ingress path +Alias for backward compatibility with ingress templates. */}} {{- define "bamboo.ingressPath" -}} -{{- if .Values.ingress.path -}} -{{- .Values.ingress.path -}} -{{- else -}} -{{ default ( "/" ) .Values.bamboo.service.contextPath -}} -{{- end }} +{{- include "bamboo.path" . -}} {{- end }} {{/* @@ -447,3 +442,5 @@ Define additional hosts here to allow template overrides when used as a sub char set -e; cp $JAVA_HOME/lib/security/cacerts /var/ssl/cacerts; chmod 664 /var/ssl/cacerts; for crt in /tmp/crt/*.*; do echo "Adding $crt to keystore"; keytool -import -keystore /var/ssl/cacerts -storepass changeit -noprompt -alias $(echo $(basename $crt)) -file $crt; done; {{- end }} {{- end }} + + diff --git a/src/main/charts/bamboo/templates/common_templates b/src/main/charts/bamboo/templates/common_templates new file mode 120000 index 000000000..043f40ba4 --- /dev/null +++ b/src/main/charts/bamboo/templates/common_templates @@ -0,0 +1 @@ +../../common_templates \ No newline at end of file diff --git a/src/main/charts/bamboo/templates/configmap-server-config.yaml b/src/main/charts/bamboo/templates/configmap-server-config.yaml index 2ddf89b13..68bd9ff4e 100644 --- a/src/main/charts/bamboo/templates/configmap-server-config.yaml +++ b/src/main/charts/bamboo/templates/configmap-server-config.yaml @@ -32,10 +32,10 @@ data: protocol="{{ .Values.bamboo.tomcatConfig.protocol | default "HTTP/1.1" }}" redirectPort="{{ .Values.bamboo.tomcatConfig.redirectPort | default "8443" }}" acceptCount="{{ .Values.bamboo.tomcatConfig.acceptCount | default "100" }}" - secure="{{ default (ternary "true" "false" .Values.ingress.https) .Values.bamboo.tomcatConfig.secure }}" - scheme="{{ default (ternary "https" "http" .Values.ingress.https) .Values.bamboo.tomcatConfig.scheme }}" - proxyName="{{ .Values.bamboo.tomcatConfig.proxyName | default .Values.ingress.host }}" - proxyPort="{{ .Values.bamboo.tomcatConfig.proxyPort | default (ternary "443" "80" .Values.ingress.https) }}" + secure="{{ default (include "common.gateway.https" .) .Values.bamboo.tomcatConfig.secure }}" + scheme="{{ default (include "common.gateway.scheme" .) .Values.bamboo.tomcatConfig.scheme }}" + proxyName="{{ .Values.bamboo.tomcatConfig.proxyName | default (include "common.gateway.hostname" .) }}" + proxyPort="{{ .Values.bamboo.tomcatConfig.proxyPort | default (include "common.gateway.externalPort" .) }}" {{- if .Values.bamboo.tomcatConfig.address }} address="{{ .Values.bamboo.tomcatConfig.address }}" diff --git a/src/main/charts/bamboo/templates/httproute.yaml b/src/main/charts/bamboo/templates/httproute.yaml new file mode 100644 index 000000000..535544411 --- /dev/null +++ b/src/main/charts/bamboo/templates/httproute.yaml @@ -0,0 +1,50 @@ +{{- if .Values.gateway.create }} +{{- include "common.gateway.validateConfig" . -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "common.names.fullname" . }} + labels: + {{- include "common.labels.commonLabels" . | nindent 4 }} + {{- with .Values.gateway.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.gateway.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + # Reference to the Gateway + parentRefs: + {{- toYaml .Values.gateway.parentRefs | nindent 2 }} + + # Hostnames to match + hostnames: + {{- range .Values.gateway.hostnames }} + - {{ . | quote }} + {{- end }} + + # Routing rules + rules: + # Default rule - routes to Bamboo service + - matches: + - path: + type: {{ .Values.gateway.pathType }} + value: {{ include "bamboo.path" . }} + {{- with .Values.gateway.timeouts }} + timeouts: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.gateway.filters }} + filters: + {{- toYaml . | nindent 4 }} + {{- end }} + backendRefs: + - name: {{ include "common.names.fullname" . }} + port: {{ .Values.bamboo.service.port }} + weight: 100 + + {{- with .Values.gateway.additionalRules }} + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/src/main/charts/bamboo/templates/statefulset.yaml b/src/main/charts/bamboo/templates/statefulset.yaml index 856d7387b..c9dfcfc7b 100644 --- a/src/main/charts/bamboo/templates/statefulset.yaml +++ b/src/main/charts/bamboo/templates/statefulset.yaml @@ -1,3 +1,4 @@ +{{- include "common.gateway.validateConfig" . -}} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -117,7 +118,7 @@ spec: image: {{ include "bamboo.image" . | quote }} imagePullPolicy: {{ .Values.image.pullPolicy }} env: - {{ if .Values.ingress.https }} + {{ if eq (include "common.gateway.https" .) "true" }} - name: ATL_TOMCAT_SCHEME value: "https" - name: ATL_TOMCAT_SECURE @@ -131,11 +132,11 @@ spec: {{ end }} - name: ATL_TOMCAT_PORT value: {{ .Values.bamboo.ports.http | quote }} - {{ if .Values.ingress.host }} + {{ if eq (include "common.gateway.isConfigured" .) "true" }} - name: ATL_PROXY_NAME - value: {{ .Values.ingress.host | quote }} + value: {{ include "common.gateway.hostname" . | quote }} - name: ATL_PROXY_PORT - value: {{ include "bamboo.ingressPort" . | quote }} + value: {{ include "common.gateway.externalPort" . | quote }} {{ end }} {{- include "bamboo.databaseEnvVars" . | nindent 12 }} - name: SET_PERMISSIONS diff --git a/src/main/charts/bamboo/values.yaml b/src/main/charts/bamboo/values.yaml index f10f7750f..f702f1a13 100644 --- a/src/main/charts/bamboo/values.yaml +++ b/src/main/charts/bamboo/values.yaml @@ -19,10 +19,11 @@ # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#3-configure-database # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#5-configure-persistent-storage # -# To manage external access to the Bamboo instance, an ingress resource can also be configured -# under the 'ingress' stanza. This requires a pre-provisioned ingress controller to be present. +# To manage external access to the Bamboo instance, a Gateway API HTTPRoute or an Ingress +# resource can be configured under the 'gateway' or 'ingress' stanza respectively. +# This requires a pre-provisioned gateway/ingress controller to be present. # -# Additional details on pre-provisioning an ingress controller can be found here: +# Additional details on configuring external access can be found here: # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#4-configure-ingress # ## @@ -350,9 +351,9 @@ volumes: # Ingress configuration # -# To make the Atlassian product available from outside of the K8s cluster an Ingress -# Controller should be pre-provisioned. With this in place the configuration below -# can be used to configure an appropriate Ingress Resource. +# Use this section when routing external traffic to Bamboo via a Kubernetes Ingress +# resource (K8s Ingress API). Requires a pre-provisioned Ingress Controller. +# If using the Gateway API instead, see the 'gateway' section below. # https://atlassian.github.io/data-center-helm-charts/userguide/CONFIGURATION/#ingress # ingress: @@ -362,6 +363,27 @@ ingress: # create: false + # -- The fully-qualified hostname (FQDN) of the Bamboo instance. This value is used + # to configure the product's proxy settings and, when ingress.create is true, + # the Ingress resource routing rules. + # + host: + + # -- Whether users access the application over HTTPS. Set to 'false' if not + # using TLS, e.g. when reaching the service via localhost port-forwarding. + # + https: true + + # -- The base path for the application, e.g. '/bamboo'. + # Defaults to 'bamboo.service.contextPath'. + # + path: + + # --------------------------------------------------------------------------- + # The options below apply only when ingress.create is true. + # They configure the Ingress resource itself and have no effect otherwise. + # --------------------------------------------------------------------------- + # -- Set to true if you want to create an OpenShift Route instead of an Ingress # openShiftRoute: false @@ -413,30 +435,12 @@ ingress: # proxySendTimeout: 60 - # -- The fully-qualified hostname (FQDN) of the Ingress Resource. Traffic coming in on - # this hostname will be routed by the Ingress Resource to the appropriate backend - # Service. - # - host: - - # -- The base path for the Ingress Resource. For example '/bamboo'. Based on a - # 'ingress.host' value of 'company.k8s.com' this would result in a URL of - # 'company.k8s.com/bamboo'. Default value is 'bamboo.service.contextPath' - # - path: - # -- The custom annotations that should be applied to the Ingress Resource. # If using an ingress-nginx controller be sure that the annotations you add # here are compatible with those already defined in the 'ingess.yaml' template # annotations: {} - # -- Set to 'true' if browser communication with the application should be TLS - # (HTTPS) enforced. If not using an ingress and you want to reach the service - # on localhost using port-forwarding then this value should be set to 'false' - # - https: true - # -- The name of the K8s Secret that contains the TLS private key and corresponding # certificate. When utilised, TLS termination occurs at the ingress point where # traffic to the Service and it's Pods is in plaintext. @@ -456,6 +460,121 @@ ingress: # service: static-content-svc # portNumber: 80 +# Gateway API configuration +# +# Use this section when routing external traffic to Bamboo via the Kubernetes +# Gateway API, or when using an external proxy/load balancer without creating +# any K8s routing resource. Only one of 'ingress' or 'gateway' should be used. +# https://gateway-api.sigs.k8s.io/ +# +gateway: + + # -- Set to 'true' if an HTTPRoute Resource should be created. This depends on a + # pre-provisioned Gateway API controller being available and a Gateway resource. + # Cannot be enabled if ingress.create is true. + # + create: false + + # -- The hostnames that should be routed to Bamboo. At least one hostname + # is required when gateway.create is true. Setting hostnames activates gateway + # mode for product configuration even when gateway.create is false, allowing + # use with a pre-existing Gateway or external proxy. + # The first entry is used as the canonical hostname for base URL, proxy + # settings, and NOTES output — list the primary/public hostname first. + # + hostnames: [] + # - bamboo.example.com + # - ci.example.com + + # -- Whether users access the application over HTTPS. + # This does not configure TLS on the Gateway or load balancer — it must match + # how traffic is actually routed to the application. + # + https: true + + # -- The port users connect on. Defaults to 443 (https) or 80 (http). + # This does not change the Gateway or load balancer port — it must match + # the port that is actually used. Only set for non-standard ports. + # + # externalPort: + + # -- The base path for routing. When empty, falls back to the product's + # service.contextPath (same behavior as ingress). Set explicitly to + # override, e.g. "/bamboo". + # + path: + + # --------------------------------------------------------------------------- + # The options below apply only when gateway.create is true. + # They configure the HTTPRoute resource and have no effect otherwise. + # --------------------------------------------------------------------------- + + # -- Reference to the parent Gateway resource. Supports any standard + # parentRef fields (name, namespace, sectionName, etc.). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference + # + parentRefs: [] + # - name: example-gateway + # namespace: example-gateway-namespace + # sectionName: https + + # -- Path matching type. Can be "PathPrefix", "Exact", or "RegularExpression". + # PathPrefix is recommended for most use cases. + # + pathType: "PathPrefix" + + # -- Annotations to add to the HTTPRoute resource. + # + annotations: {} + # kubernetes.io/ingress.class: gateway + # cert-manager.io/cluster-issuer: letsencrypt + + # -- Labels to add to the HTTPRoute resource. + # + labels: {} + # environment: production + # team: platform + + # -- HTTP filters to apply to requests. Can be used to add/remove headers, + # perform redirects, or rewrite URLs. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteFilter + # + filters: [] + # - type: RequestHeaderModifier + # requestHeaderModifier: + # add: + # - name: X-Forwarded-Proto + # value: https + + # -- Advanced routing rules. Use this for complex routing scenarios like + # header-based routing, traffic splitting, or multiple backends. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule + # + additionalRules: [] + # - matches: + # - path: + # type: PathPrefix + # value: /api + # backendRefs: + # - name: bamboo-api + # port: 8085 + + # -- Session affinity (sticky sessions) is required for Atlassian DC products but + # is not part of the standard Gateway API HTTPRoute spec. It must be configured + # separately through your Gateway implementation's own policy resources. + # See docs/docs/examples/ingress/GATEWAY_API_SESSION_AFFINITY.md for working examples. + + # -- Timeout configuration for HTTPRoute rules. + # Note: when migrating from Ingress, these replace proxyReadTimeout and + # proxySendTimeout. There is no Gateway API equivalent for + # proxyConnectTimeout or maxBodySize — those require controller-specific + # policies (e.g. Envoy Gateway BackendTrafficPolicy). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteTimeouts + # + timeouts: + request: "60s" + backendRequest: "60s" + # Bamboo configuration # bamboo: @@ -880,13 +999,13 @@ bamboo: protocol: "HTTP/1.1" redirectPort: "8443" acceptCount: "100" - # secure is retrieved from ingress.https value + # secure is set based on the https setting (gateway.https or ingress.https) secure: - # scheme is set depending on ingress.https value (http if false, https if true) + # scheme is set based on the https setting (http if false, https if true) scheme: - # proxyName is retrieved from ingress.host value + # proxyName is set to the configured hostname (gateway.hostnames[0] or ingress.host) proxyName: - # proxyPort is set depending on ingress.https value (80 if http, 443 if https) + # proxyPort is set to the external port (defaults to 443 for https, 80 for http) proxyPort: maxHttpHeaderSize: "8192" address: diff --git a/src/main/charts/bitbucket/.helmignore b/src/main/charts/bitbucket/.helmignore index 0e8a0eb36..c7423efa9 100644 --- a/src/main/charts/bitbucket/.helmignore +++ b/src/main/charts/bitbucket/.helmignore @@ -21,3 +21,5 @@ .idea/ *.tmproj .vscode/ +# Ignore non-template files from symlinked common_templates +templates/common_templates/*.md diff --git a/src/main/charts/bitbucket/Chart.yaml b/src/main/charts/bitbucket/Chart.yaml index 5d669f841..a8e106a47 100644 --- a/src/main/charts/bitbucket/Chart.yaml +++ b/src/main/charts/bitbucket/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: bitbucket description: A chart for installing Bitbucket Data Center on Kubernetes type: application -version: '2.0.9' +version: '2.0.10' appVersion: 10.2.1 kubeVersion: ">=1.21.x-0" keywords: @@ -20,8 +20,8 @@ deprecated: false annotations: artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: |- - - "fix nodeport svc (#1076)" - - "Update appVersions for DC apps (#1075)" + - "Add Gateway API support via HTTPRoute resources" + - "Gateway API provides modern alternative to Ingress" dependencies: - name: common diff --git a/src/main/charts/bitbucket/templates/NOTES.txt b/src/main/charts/bitbucket/templates/NOTES.txt index 1ccebd353..9501e53c5 100644 --- a/src/main/charts/bitbucket/templates/NOTES.txt +++ b/src/main/charts/bitbucket/templates/NOTES.txt @@ -13,8 +13,8 @@ To see the custom values you used for this release: $ helm get values {{ .Release.Name }} -n {{ .Release.Namespace }} -{{ if .Values.ingress.create -}} -{{ title .Chart.Name }} service URL: {{ ternary "https" "http" .Values.ingress.https -}}://{{ .Values.ingress.host }}{{ include "bitbucket.ingressPath" . }} +{{ if eq (include "common.gateway.isConfigured" .) "true" -}} +{{ title .Chart.Name }} service URL: {{ include "common.gateway.origin" . }}{{ include "bitbucket.path" . }} {{- else }} Get the {{ title .Chart.Name }} URL by running these commands in the same shell: {{- if contains "NodePort" .Values.bitbucket.service.type }} @@ -64,7 +64,7 @@ Get the {{ title .Chart.Name }} URL by running these commands in the same shell: {{- if and (.Values.bitbucket.mesh.enabled) (not .Values.bitbucket.mesh.nodeAutoRegistration)}} -Bitbucket Mesh deployed. You can register Mesh nodes at {{ if .Values.ingress.create }}{{ ternary "https" "http" .Values.ingress.https -}}://{{ .Values.ingress.host }}{{ include "bitbucket.ingressPath" . }}{{ else }}${BITBUCKET_URL}{{ end }}/admin/git/mesh using the following URLs: +Bitbucket Mesh deployed. You can register Mesh nodes at {{ if eq (include "common.gateway.isConfigured" .) "true" }}{{ include "common.gateway.origin" . }}{{ include "bitbucket.path" . }}{{ else }}${BITBUCKET_URL}{{ end }}/admin/git/mesh using the following URLs: {{- range $index := until (.Values.bitbucket.mesh.replicaCount | int) }} {{- with $ }} diff --git a/src/main/charts/bitbucket/templates/_helpers.tpl b/src/main/charts/bitbucket/templates/_helpers.tpl index da95d3983..87700b1bb 100644 --- a/src/main/charts/bitbucket/templates/_helpers.tpl +++ b/src/main/charts/bitbucket/templates/_helpers.tpl @@ -125,26 +125,23 @@ Mesh Pod labels {{- end }} {{- end }} -{{- define "bitbucket.baseUrl" -}} -{{ ternary "https" "http" .Values.ingress.https -}} -:// -{{- .Values.ingress.host -}} -{{ with .Values.ingress.port }}:{{ . }}{{ end }} +{{/* +Create default value for the service path. +*/}} +{{- define "bitbucket.path" -}} +{{- include "common.gateway.path" (dict + "useGatewayMode" (include "common.gateway.useGatewayMode" .) + "gatewayPath" .Values.gateway.path + "ingressPath" .Values.ingress.path + "contextPath" .Values.bitbucket.service.contextPath +) -}} {{- end }} {{/* -Create default value for ingress path +Alias for backward compatibility with ingress templates. */}} {{- define "bitbucket.ingressPath" -}} -{{- if .Values.ingress.path -}} -{{- .Values.ingress.path -}} -{{- else -}} -{{ default ( "/" ) .Values.bitbucket.service.contextPath -}} -{{- end }} -{{- end }} - -{{- define "bitbucket.ingressPort" -}} -{{ default (ternary "443" "80" .Values.ingress.https) .Values.ingress.port -}} +{{- include "bitbucket.path" . -}} {{- end }} {{/* @@ -608,3 +605,5 @@ set -e; cp $JAVA_HOME/lib/security/cacerts /var/ssl/cacerts; chmod 664 /var/ssl/ set -e; cp $JAVA_HOME/lib/security/cacerts /var/ssl/cacerts; chmod 664 /var/ssl/cacerts; for crt in /tmp/crt/*.*; do echo "Adding $crt to keystore"; keytool -import -keystore /var/ssl/cacerts -storepass changeit -noprompt -alias $(echo $(basename $crt)) -file $crt; done; {{- end }} {{- end }} + + diff --git a/src/main/charts/bitbucket/templates/common_templates b/src/main/charts/bitbucket/templates/common_templates new file mode 120000 index 000000000..043f40ba4 --- /dev/null +++ b/src/main/charts/bitbucket/templates/common_templates @@ -0,0 +1 @@ +../../common_templates \ No newline at end of file diff --git a/src/main/charts/bitbucket/templates/httproute.yaml b/src/main/charts/bitbucket/templates/httproute.yaml new file mode 100644 index 000000000..af8377f0f --- /dev/null +++ b/src/main/charts/bitbucket/templates/httproute.yaml @@ -0,0 +1,50 @@ +{{- if .Values.gateway.create }} +{{- include "common.gateway.validateConfig" . -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "common.names.fullname" . }} + labels: + {{- include "common.labels.commonLabels" . | nindent 4 }} + {{- with .Values.gateway.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.gateway.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + # Reference to the Gateway + parentRefs: + {{- toYaml .Values.gateway.parentRefs | nindent 2 }} + + # Hostnames to match + hostnames: + {{- range .Values.gateway.hostnames }} + - {{ . | quote }} + {{- end }} + + # Routing rules + rules: + # Default rule - routes to Bitbucket service + - matches: + - path: + type: {{ .Values.gateway.pathType }} + value: {{ include "bitbucket.path" . }} + {{- with .Values.gateway.timeouts }} + timeouts: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.gateway.filters }} + filters: + {{- toYaml . | nindent 4 }} + {{- end }} + backendRefs: + - name: {{ include "common.names.fullname" . }} + port: {{ .Values.bitbucket.service.port }} + weight: 100 + + {{- with .Values.gateway.additionalRules }} + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/src/main/charts/bitbucket/templates/statefulset.yaml b/src/main/charts/bitbucket/templates/statefulset.yaml index a98179c44..6a6033b46 100644 --- a/src/main/charts/bitbucket/templates/statefulset.yaml +++ b/src/main/charts/bitbucket/templates/statefulset.yaml @@ -1,6 +1,7 @@ {{/* This describes a k8s StatefulSet for deploying Bitbucket for testing */}} {{/* the default can be removed in v2.0.0 */}} {{- $mirror := default dict .Values.bitbucket.mirror }} +{{- include "common.gateway.validateConfig" . -}} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -238,13 +239,13 @@ spec: {{- end }} - name: PLUGIN_SSH_PORT value: {{ .Values.bitbucket.ports.ssh | quote }} - {{ if .Values.ingress.host }} + {{ if eq (include "common.gateway.isConfigured" .) "true" }} - name: SERVER_PROXY_NAME - value: {{ .Values.ingress.host | quote }} + value: {{ include "common.gateway.hostname" . | quote }} - name: SERVER_PROXY_PORT - value: {{ include "bitbucket.ingressPort" . | quote }} + value: {{ include "common.gateway.externalPort" . | quote }} - name: SETUP_BASEURL - value: {{ include "bitbucket.baseUrl" . | quote }} + value: {{ include "common.gateway.origin" . | quote }} {{- with .Values.bitbucket.displayName }} - name: SETUP_DISPLAYNAME value: {{ . | quote }} @@ -255,10 +256,10 @@ spec: {{- end }} {{ end }} - name: SERVER_CONTEXT_PATH - value: {{ include "bitbucket.ingressPath" . | quote }} + value: {{ include "bitbucket.path" . | quote }} - name: SERVER_PORT value: {{ .Values.bitbucket.ports.http | quote }} - {{ if .Values.ingress.https }} + {{ if eq (include "common.gateway.https" .) "true" }} - name: SERVER_SCHEME value: "https" - name: SERVER_SECURE diff --git a/src/main/charts/bitbucket/values.yaml b/src/main/charts/bitbucket/values.yaml index 5813504f9..cb795e529 100644 --- a/src/main/charts/bitbucket/values.yaml +++ b/src/main/charts/bitbucket/values.yaml @@ -11,10 +11,11 @@ # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#3-configure-database # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#5-configure-persistent-storage # -# To manage external access to the Bitbucket instance, an ingress resource can also be configured -# under the 'ingress' stanza. This requires a pre-provisioned ingress controller to be present. +# To manage external access to the Bitbucket instance, a Gateway API HTTPRoute or an Ingress +# resource can be configured under the 'gateway' or 'ingress' stanza respectively. +# This requires a pre-provisioned gateway/ingress controller to be present. # -# Additional details on pre-provisioning an ingress controller can be found here: +# Additional details on configuring external access can be found here: # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#4-configure-ingress # # Unlike the other products, Bitbucket has the added advantage that it can be fully @@ -426,9 +427,9 @@ volumes: # Ingress configuration # -# To make the Atlassian product available from outside the K8s cluster an Ingress -# Controller should be pre-provisioned. With this in place the configuration below -# can be used to configure an appropriate Ingress Resource. +# Use this section when routing external traffic to Bitbucket via a Kubernetes Ingress +# resource (K8s Ingress API). Requires a pre-provisioned Ingress Controller. +# If using the Gateway API instead, see the 'gateway' section below. # https://atlassian.github.io/data-center-helm-charts/userguide/CONFIGURATION/#ingress # ingress: @@ -438,6 +439,27 @@ ingress: # create: false + # -- The fully-qualified hostname (FQDN) of the Bitbucket instance. This value is used + # to configure the product's proxy settings and, when ingress.create is true, + # the Ingress resource routing rules. + # + host: + + # -- Whether users access the application over HTTPS. Set to 'false' if not + # using TLS, e.g. when reaching the service via localhost port-forwarding. + # + https: true + + # -- The base path for the application, e.g. '/bitbucket'. + # Defaults to 'bitbucket.service.contextPath'. + # + path: + + # --------------------------------------------------------------------------- + # The options below apply only when ingress.create is true. + # They configure the Ingress resource itself and have no effect otherwise. + # --------------------------------------------------------------------------- + # -- Set to true if you want to create an OpenShift Route instead of an Ingress # openShiftRoute: false @@ -489,29 +511,12 @@ ingress: # proxySendTimeout: 60 - # -- The fully-qualified hostname (FQDN) of the Ingress Resource. Traffic coming in on - # this hostname will be routed by the Ingress Resource to the appropriate backend - # Service. - # - host: - - # -- The base path for the Ingress Resource. For example '/bitbucket'. Based on a - # 'ingress.host' value of 'company.k8s.com' this would result in a URL of - # 'company.k8s.com/bitbucket'. Default value is 'bitbucket.service.contextPath'. - # - path: - # -- The custom annotations that should be applied to the Ingress Resource. # If using an ingress-nginx controller be sure that the annotations you add # here are compatible with those already defined in the 'ingess.yaml' template # annotations: {} - # -- Set to 'true' if browser communication with the application should be TLS - # (HTTPS) enforced. - # - https: true - # -- The name of the K8s Secret that contains the TLS private key and corresponding # certificate. When utilised, TLS termination occurs at the ingress point where # traffic to the Service, and it's Pods is in plaintext. @@ -531,6 +536,122 @@ ingress: # service: static-content-svc # portNumber: 80 +# Gateway API configuration +# +# Use this section when routing external traffic to Bitbucket via the Kubernetes +# Gateway API, or when using an external proxy/load balancer without creating +# any K8s routing resource. Only one of 'ingress' or 'gateway' should be used. +# https://gateway-api.sigs.k8s.io/ +# +gateway: + + # -- Set to 'true' if an HTTPRoute Resource should be created. This depends on a + # pre-provisioned Gateway API controller being available and a Gateway resource. + # Cannot be enabled if ingress.create is true. + # + create: false + + # -- The hostnames that should be routed to Bitbucket. At least one hostname + # is required when gateway.create is true. Setting hostnames activates gateway + # mode for product configuration even when gateway.create is false, allowing + # use with a pre-existing Gateway or external proxy. + # The first entry is used as the canonical hostname for base URL, proxy + # settings, and NOTES output — list the primary/public hostname first. + # + hostnames: [] + # - bitbucket.example.com + # - bb.example.com + + # -- Whether users access the application over HTTPS. + # This does not configure TLS on the Gateway or load balancer — it must match + # how traffic is actually routed to the application. + # + https: true + + # -- The port users connect on. Defaults to 443 (https) or 80 (http). + # This does not change the Gateway or load balancer port — it must match + # the port that is actually used. Only set for non-standard ports. + # + # externalPort: + + # -- The base path for routing. When empty, falls back to the product's + # service.contextPath (same behavior as ingress). Set explicitly to + # override, e.g. "/bitbucket". + # + path: + + # --------------------------------------------------------------------------- + # The options below apply only when gateway.create is true. + # They configure the HTTPRoute resource and have no effect otherwise. + # --------------------------------------------------------------------------- + + # -- Reference to the parent Gateway resource. Supports any standard + # parentRef fields (name, namespace, sectionName, etc.). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference + # + parentRefs: [] + # - name: example-gateway + # namespace: example-gateway-namespace + # sectionName: https + + # -- Path matching type. Can be "PathPrefix", "Exact", or "RegularExpression". + # PathPrefix is recommended for most use cases. + # + pathType: "PathPrefix" + + # -- Annotations to add to the HTTPRoute resource. + # + annotations: {} + # kubernetes.io/ingress.class: gateway + # cert-manager.io/cluster-issuer: letsencrypt + + # -- Labels to add to the HTTPRoute resource. + # + labels: {} + # environment: production + # team: platform + + # -- HTTP filters to apply to requests. Can be used to add/remove headers, + # perform redirects, or rewrite URLs. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteFilter + # + filters: [] + # - type: RequestHeaderModifier + # requestHeaderModifier: + # add: + # - name: X-Forwarded-Proto + # value: https + + # -- Advanced routing rules. Use this for complex routing scenarios like + # header-based routing, traffic splitting, or multiple backends. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule + # + additionalRules: [] + # - matches: + # - path: + # type: PathPrefix + # value: /api + # backendRefs: + # - name: bitbucket-api-v2 + # port: 8080 + # weight: 100 + + # -- Session affinity (sticky sessions) is required for Atlassian DC products but + # is not part of the standard Gateway API HTTPRoute spec. It must be configured + # separately through your Gateway implementation's own policy resources. + # See docs/docs/examples/ingress/GATEWAY_API_SESSION_AFFINITY.md for working examples. + + # -- Timeout configuration for HTTPRoute rules. + # Note: when migrating from Ingress, these replace proxyReadTimeout and + # proxySendTimeout. There is no Gateway API equivalent for + # proxyConnectTimeout or maxBodySize — those require controller-specific + # policies (e.g. Envoy Gateway BackendTrafficPolicy). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteTimeouts + # + timeouts: + request: "60s" + backendRequest: "60s" + # Bitbucket configuration # bitbucket: diff --git a/src/main/charts/common_templates/README.md b/src/main/charts/common_templates/README.md new file mode 100644 index 000000000..922b82b53 --- /dev/null +++ b/src/main/charts/common_templates/README.md @@ -0,0 +1,48 @@ +# Common Templates + +Shared Helm template helpers that are symlinked into each product chart's `templates/` directory. + +## Why this pattern? + +The existing `common/` library chart is published as a remote Helm dependency. Any change to it +requires a **two-step rollout**: publish the library first, then update each product chart's +`Chart.yaml` to reference the new version. + +This `common_templates/` directory avoids that by using **symlinks**. Each product chart has a +symlink at `templates/common_templates → ../../common_templates`, so Helm picks up these templates +directly. Changes here take effect immediately across all products — no publishing, no version +bumps, single PR. + +## When to use this vs `common/` + +**All new shared templates should go in `common_templates/` (this directory).** + +The `common/` library chart is a legacy pattern that requires a two-step release process for +every change. It should not be used for new shared templates. Existing templates in `common/` +(`_labels.tpl`, `_names.tpl`, `_jmx.tpl`) should be incrementally migrated here when they are +next modified — there is no technical reason to keep them in the library chart. + +## How it works + +Each product chart has a symlink: + +``` +src/main/charts//templates/common_templates → ../../common_templates +``` + +Helm follows symlinks during `helm template` and `helm package`, so: + +- **Local development**: templates are resolved via the symlink +- **Published charts**: `helm package` embeds the actual file content in the `.tgz` — consumers see regular files, not + symlinks + +## Adding a new shared template + +1. Create your `.tpl` file in this directory +2. That's it — all product charts pick it up automatically via the existing symlink + +## Platform note + +Git on Windows doesn't enable symlinks by default. Contributors on Windows need +`git config core.symlinks true` (or run Git as admin) for symlinks to work correctly. + diff --git a/src/main/charts/common_templates/_gateway.tpl b/src/main/charts/common_templates/_gateway.tpl new file mode 100644 index 000000000..bb0cc9fdf --- /dev/null +++ b/src/main/charts/common_templates/_gateway.tpl @@ -0,0 +1,112 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Returns "true" if external access is configured via either ingress or gateway. +True when ingress.host is set or gateway.hostnames is non-empty. +*/}} +{{- define "common.gateway.isConfigured" -}} + {{- ternary "true" "false" (or (not (empty .Values.ingress.host)) (not (empty .Values.gateway.hostnames))) -}} +{{- end -}} + +{{/* +Returns "true" if gateway mode should be used (vs ingress mode) for product setup. +True when gateway.hostnames is non-empty, regardless of whether an HTTPRoute is created. +This allows using gateway config with a pre-existing Gateway/proxy without gateway.create. +*/}} +{{- define "common.gateway.useGatewayMode" -}} + {{- ternary "true" "false" (not (empty .Values.gateway.hostnames)) -}} +{{- end -}} + +{{/* +Validates gateway/ingress configuration. +Ensures mutual exclusion and required fields. +*/}} +{{- define "common.gateway.validateConfig" -}} + {{- if and .Values.gateway.create .Values.ingress.create -}} + {{- fail "ERROR: Cannot enable both gateway.create and ingress.create" -}} + {{- end -}} + {{- if and (not (empty .Values.gateway.hostnames)) (not (empty .Values.ingress.host)) -}} + {{- fail "ERROR: Cannot set both gateway.hostnames and ingress.host — use one or the other" -}} + {{- end -}} + {{- if and .Values.gateway.create (empty .Values.gateway.parentRefs) -}} + {{- fail "ERROR: gateway.parentRefs is required when gateway.create is true" -}} + {{- end -}} + {{- if and .Values.gateway.create (not (empty .Values.gateway.parentRefs)) (not (index .Values.gateway.parentRefs 0).name) -}} + {{- fail "ERROR: gateway.parentRefs[0].name is required when gateway.create is true" -}} + {{- end -}} + {{- if and .Values.gateway.create (not .Values.gateway.hostnames) -}} + {{- fail "ERROR: gateway.hostnames must contain at least one hostname when gateway.create is true" -}} + {{- end -}} +{{- end -}} + +{{/* +Returns "true" or "false" string for whether HTTPS is enabled. +Uses gateway.https if gateway config is active, otherwise ingress.https. +*/}} +{{- define "common.gateway.https" -}} + {{- $useGateway := eq (include "common.gateway.useGatewayMode" .) "true" -}} + {{- ternary .Values.gateway.https .Values.ingress.https $useGateway -}} +{{- end -}} + +{{/* +Returns "https" or "http" based on the current ingress/gateway HTTPS setting. +*/}} +{{- define "common.gateway.scheme" -}} + {{- ternary "https" "http" (eq (include "common.gateway.https" .) "true") -}} +{{- end -}} + +{{/* +Returns the canonical hostname for the service. +Uses first gateway hostname if gateway config is active, otherwise ingress.host. +*/}} +{{- define "common.gateway.hostname" -}} + {{- if eq (include "common.gateway.useGatewayMode" .) "true" -}} + {{- index .Values.gateway.hostnames 0 -}} + {{- else -}} + {{- .Values.ingress.host -}} + {{- end -}} +{{- end -}} + +{{/* +Returns the external port the application is accessed on. +Defaults to "443" (https) or "80" (http). +*/}} +{{- define "common.gateway.externalPort" -}} + {{- if eq (include "common.gateway.useGatewayMode" .) "true" -}} + {{- default (ternary "443" "80" .Values.gateway.https) .Values.gateway.externalPort -}} + {{- else -}} + {{- default (ternary "443" "80" .Values.ingress.https) .Values.ingress.port -}} + {{- end -}} +{{- end -}} + +{{/* +Returns the service path. Handles gateway vs ingress mode with a contextPath fallback. +Usage: +include "common.gateway.path" (dict + "useGatewayMode" (include "common.gateway.useGatewayMode" .) + "gatewayPath" .Values.gateway.path + "ingressPath" .Values.ingress.path + "contextPath" .Values..service.contextPath +) +*/}} +{{- define "common.gateway.path" -}} +{{- $explicitPath := ternary .gatewayPath .ingressPath (eq .useGatewayMode "true") -}} +{{- if $explicitPath -}} + {{- $explicitPath -}} +{{- else -}} + {{- .contextPath | default "/" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns the origin (scheme + host + port) for the service. +Default ports (443/https, 80/http) are omitted from the URL. +Usage: include "common.gateway.origin" . +*/}} +{{- define "common.gateway.origin" -}} +{{- $scheme := include "common.gateway.scheme" . -}} +{{- $host := include "common.gateway.hostname" . -}} +{{- $port := include "common.gateway.externalPort" . -}} +{{- printf "%s://%s" $scheme $host -}} +{{- if ne $port (ternary "443" "80" (eq $scheme "https")) -}}:{{ $port }}{{- end -}} +{{- end -}} diff --git a/src/main/charts/confluence/.helmignore b/src/main/charts/confluence/.helmignore index 0e8a0eb36..c7423efa9 100644 --- a/src/main/charts/confluence/.helmignore +++ b/src/main/charts/confluence/.helmignore @@ -21,3 +21,5 @@ .idea/ *.tmproj .vscode/ +# Ignore non-template files from symlinked common_templates +templates/common_templates/*.md diff --git a/src/main/charts/confluence/Chart.yaml b/src/main/charts/confluence/Chart.yaml index 6c35a4257..2d337ee1b 100644 --- a/src/main/charts/confluence/Chart.yaml +++ b/src/main/charts/confluence/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: confluence description: A chart for installing Confluence Data Center on Kubernetes type: application -version: '2.0.9' +version: '2.0.10' appVersion: 10.2.7 kubeVersion: ">=1.21.x-0" keywords: @@ -20,7 +20,7 @@ deprecated: false annotations: artifacthub.io/containsSecurityUpdates: "true" artifacthub.io/changes: |- - - "Update Helm chart version" + - "Add Gateway API support via HTTPRoute resources" dependencies: - name: common diff --git a/src/main/charts/confluence/templates/NOTES.txt b/src/main/charts/confluence/templates/NOTES.txt index be576cbc3..903b5f238 100644 --- a/src/main/charts/confluence/templates/NOTES.txt +++ b/src/main/charts/confluence/templates/NOTES.txt @@ -13,8 +13,8 @@ To see the custom values you used for this release: $ helm get values {{ .Release.Name }} -n {{ .Release.Namespace }} -{{ if .Values.ingress.create -}} -{{ title .Chart.Name }} service URL: {{ ternary "https" "http" .Values.ingress.https -}}://{{ .Values.ingress.host }}{{ include "confluence.ingressPath" . }} +{{ if eq (include "common.gateway.isConfigured" .) "true" -}} +{{ title .Chart.Name }} service URL: {{ include "common.gateway.origin" . }}{{ include "confluence.path" . }} {{- else }} Get the {{ title .Chart.Name }} URL by running these commands in the same shell: {{- if contains "NodePort" .Values.confluence.service.type }} diff --git a/src/main/charts/confluence/templates/_helpers.tpl b/src/main/charts/confluence/templates/_helpers.tpl index 1514a247e..e13992f30 100644 --- a/src/main/charts/confluence/templates/_helpers.tpl +++ b/src/main/charts/confluence/templates/_helpers.tpl @@ -60,13 +60,6 @@ } {{- end }} -{{/* -Create default value for ingress port -*/}} -{{- define "confluence.ingressPort" -}} -{{ default (ternary "443" "80" .Values.ingress.https) .Values.ingress.port -}} -{{- end }} - {{/* The name the synchrony app within the chart. TODO: This will break if the common.names.name exceeds 63 characters, need to find a more rebust way to do this @@ -205,10 +198,7 @@ Pod labels {{- end }} {{- if .Values.synchrony.enabled -}} {{- if .Values.synchrony.service.url -}}-Dsynchrony.service.url={{ .Values.synchrony.service.url }}/v1 - {{- else -}} - {{- if .Values.ingress.https -}}-Dsynchrony.service.url=https://{{ .Values.ingress.host }}/{{ $synchronyIngressPath }}/v1 - {{- else }}-Dsynchrony.service.url=http://{{ .Values.ingress.host }}/{{ $synchronyIngressPath }}/v1 - {{- end }} + {{- else -}}-Dsynchrony.service.url={{ include "common.gateway.scheme" . }}://{{ include "common.gateway.hostname" . }}/{{ $synchronyIngressPath }}/v1 {{- end }} {{- else -}} -Dsynchrony.btf.disabled=true @@ -216,14 +206,22 @@ Pod labels {{- end -}} {{/* -Create default value for ingress path +Create default value for the service path. */}} -{{- define "confluence.ingressPath" -}} -{{- if .Values.ingress.path -}} -{{- .Values.ingress.path -}} -{{- else -}} -{{ default ( "/" ) .Values.confluence.service.contextPath -}} +{{- define "confluence.path" -}} +{{- include "common.gateway.path" (dict + "useGatewayMode" (include "common.gateway.useGatewayMode" .) + "gatewayPath" .Values.gateway.path + "ingressPath" .Values.ingress.path + "contextPath" .Values.confluence.service.contextPath +) -}} {{- end }} + +{{/* +Alias for backward compatibility with ingress templates. +*/}} +{{- define "confluence.ingressPath" -}} +{{- include "confluence.path" . -}} {{- end }} {{/* @@ -820,3 +818,5 @@ set -e; cp $JAVA_HOME/lib/security/cacerts /var/ssl/cacerts; chmod 664 /var/ssl/ key: OPENSEARCH_INITIAL_ADMIN_PASSWORD {{- end }} {{- end }} + + diff --git a/src/main/charts/confluence/templates/common_templates b/src/main/charts/confluence/templates/common_templates new file mode 120000 index 000000000..043f40ba4 --- /dev/null +++ b/src/main/charts/confluence/templates/common_templates @@ -0,0 +1 @@ +../../common_templates \ No newline at end of file diff --git a/src/main/charts/confluence/templates/configmap-server-config.yaml b/src/main/charts/confluence/templates/configmap-server-config.yaml index 5152f6b03..5e5b41063 100644 --- a/src/main/charts/confluence/templates/configmap-server-config.yaml +++ b/src/main/charts/confluence/templates/configmap-server-config.yaml @@ -25,10 +25,10 @@ data: acceptCount="{{ .Values.confluence.tomcatConfig.acceptCount | default "100" }}" debug="{{ .Values.confluence.tomcatConfig.debug | default "0" }}" URIEncoding="{{ .Values.confluence.tomcatConfig.uriEncoding | default "UTF-8" }}" - secure="{{ default (ternary "true" "false" .Values.ingress.https) .Values.confluence.tomcatConfig.secure }}" - scheme="{{ default (ternary "https" "http" .Values.ingress.https) .Values.confluence.tomcatConfig.scheme }}" - proxyName="{{ .Values.confluence.tomcatConfig.proxyName | default .Values.ingress.host }}" - proxyPort="{{ .Values.confluence.tomcatConfig.proxyPort | default (ternary "443" "80" .Values.ingress.https) }}" + secure="{{ default (include "common.gateway.https" .) .Values.confluence.tomcatConfig.secure }}" + scheme="{{ default (include "common.gateway.scheme" .) .Values.confluence.tomcatConfig.scheme }}" + proxyName="{{ .Values.confluence.tomcatConfig.proxyName | default (include "common.gateway.hostname" .) }}" + proxyPort="{{ .Values.confluence.tomcatConfig.proxyPort | default (include "common.gateway.externalPort" .) }}" maxHttpHeaderSize="{{ .Values.confluence.tomcatConfig.maxHttpHeaderSize | default "8192" }}" /> {{- if .Values.confluence.tunnel.additionalConnector.port }} diff --git a/src/main/charts/confluence/templates/httproute.yaml b/src/main/charts/confluence/templates/httproute.yaml new file mode 100644 index 000000000..de93e950a --- /dev/null +++ b/src/main/charts/confluence/templates/httproute.yaml @@ -0,0 +1,67 @@ +{{- if .Values.gateway.create }} +{{- include "common.gateway.validateConfig" . -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "common.names.fullname" . }} + labels: + {{- include "common.labels.commonLabels" . | nindent 4 }} + {{- with .Values.gateway.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.gateway.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + # Reference to the Gateway + parentRefs: + {{- toYaml .Values.gateway.parentRefs | nindent 2 }} + + # Hostnames to match + hostnames: + {{- range .Values.gateway.hostnames }} + - {{ . | quote }} + {{- end }} + + # Routing rules + rules: + + {{- if .Values.synchrony.enabled }} + # Synchrony rule (collaborative editing) + - matches: + - path: + type: PathPrefix + value: {{ .Values.synchrony.ingress.path | default "/synchrony" }} + {{- with .Values.gateway.timeouts }} + timeouts: + {{- toYaml . | nindent 6 }} + {{- end }} + backendRefs: + - name: {{ include "synchrony.fullname" . }} + port: {{ .Values.synchrony.service.port }} + weight: 100 + {{- end }} + + # Default rule - routes to Confluence service + - matches: + - path: + type: {{ .Values.gateway.pathType }} + value: {{ include "confluence.path" . }} + {{- with .Values.gateway.timeouts }} + timeouts: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.gateway.filters }} + filters: + {{- toYaml . | nindent 4 }} + {{- end }} + backendRefs: + - name: {{ include "common.names.fullname" . }} + port: {{ .Values.confluence.service.port }} + weight: 100 + + {{- with .Values.gateway.additionalRules }} + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/src/main/charts/confluence/templates/statefulset-synchrony.yaml b/src/main/charts/confluence/templates/statefulset-synchrony.yaml index 585a123cb..b1b1a2b24 100644 --- a/src/main/charts/confluence/templates/statefulset-synchrony.yaml +++ b/src/main/charts/confluence/templates/statefulset-synchrony.yaml @@ -131,11 +131,7 @@ spec: {{ if .Values.synchrony.service.url }} value: "{{ .Values.synchrony.service.url }}" {{ else }} - {{ if .Values.ingress.https }} - value: "https://{{ .Values.ingress.host }}/synchrony" - {{ else }} - value: "http://{{ .Values.ingress.host }}/synchrony" - {{- end }} + value: "{{ include "common.gateway.scheme" . }}://{{ include "common.gateway.hostname" . }}/synchrony" {{ end }} {{- include "synchrony.databaseEnvVars" . | nindent 12 }} {{- include "synchrony.clusteringEnvVars" . | nindent 12 }} diff --git a/src/main/charts/confluence/templates/statefulset.yaml b/src/main/charts/confluence/templates/statefulset.yaml index 588796af6..836e94b30 100644 --- a/src/main/charts/confluence/templates/statefulset.yaml +++ b/src/main/charts/confluence/templates/statefulset.yaml @@ -1,3 +1,4 @@ +{{- include "common.gateway.validateConfig" . -}} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -212,7 +213,7 @@ spec: env: {{- include "confluence.sessionVars" . | nindent 12 }} {{- include "confluence.tunnelVars" . | nindent 12 }} - {{ if .Values.ingress.https }} + {{ if eq (include "common.gateway.https" .) "true" }} - name: ATL_TOMCAT_SCHEME value: "https" - name: ATL_TOMCAT_SECURE @@ -224,11 +225,11 @@ spec: {{ end }} - name: ATL_TOMCAT_PORT value: {{ .Values.confluence.ports.http | quote }} - {{ if .Values.ingress.host }} + {{ if eq (include "common.gateway.isConfigured" .) "true" }} - name: ATL_PROXY_NAME - value: {{ .Values.ingress.host | quote }} + value: {{ include "common.gateway.hostname" . | quote }} - name: ATL_PROXY_PORT - value: {{ include "confluence.ingressPort" . | quote }} + value: {{ include "common.gateway.externalPort" . | quote }} {{ end }} - name: ATL_TOMCAT_ACCESS_LOG value: {{ .Values.confluence.accessLog.enabled | quote }} diff --git a/src/main/charts/confluence/values.yaml b/src/main/charts/confluence/values.yaml index 3ecf479cd..6f80f3184 100644 --- a/src/main/charts/confluence/values.yaml +++ b/src/main/charts/confluence/values.yaml @@ -11,10 +11,11 @@ # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#3-configure-database # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#5-configure-persistent-storage # -# To manage external access to the Confluence instance, an ingress resource can also be configured -# under the 'ingress' stanza. This requires a pre-provisioned ingress controller to be present. +# To manage external access to the Confluence instance, a Gateway API HTTPRoute or an Ingress +# resource can be configured under the 'gateway' or 'ingress' stanza respectively. +# This requires a pre-provisioned gateway/ingress controller to be present. # -# Additional details on pre-provisioning an ingress controller can be found here: +# Additional details on configuring external access can be found here: # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#4-configure-ingress # ## @@ -472,9 +473,9 @@ volumes: # Ingress configuration # -# To make the Atlassian product available from outside the K8s cluster an Ingress -# Controller should be pre-provisioned. With this in place the configuration below -# can be used to configure an appropriate Ingress Resource. +# Use this section when routing external traffic to Confluence via a Kubernetes Ingress +# resource (K8s Ingress API). Requires a pre-provisioned Ingress Controller. +# If using the Gateway API instead, see the 'gateway' section below. # https://atlassian.github.io/data-center-helm-charts/userguide/CONFIGURATION/#ingress # ingress: @@ -484,6 +485,27 @@ ingress: # create: false + # -- The fully-qualified hostname (FQDN) of the Confluence instance. This value is used + # to configure the product's proxy settings and, when ingress.create is true, + # the Ingress resource routing rules. + # + host: + + # -- Whether users access the application over HTTPS. Set to 'false' if not + # using TLS, e.g. when reaching the service via localhost port-forwarding. + # + https: true + + # -- The base path for the application, e.g. '/confluence'. + # Defaults to 'confluence.service.contextPath'. + # + path: + + # --------------------------------------------------------------------------- + # The options below apply only when ingress.create is true. + # They configure the Ingress resource itself and have no effect otherwise. + # --------------------------------------------------------------------------- + # -- Set to true if you want to create an OpenShift Route instead of an Ingress # openShiftRoute: false @@ -535,28 +557,12 @@ ingress: # proxySendTimeout: 60 - # -- The fully-qualified hostname (FQDN) of the Ingress Resource. Traffic coming in on - # this hostname will be routed by the Ingress Resource to the appropriate backend - # Service. - # - host: - - # -- The base path for the Ingress Resource. For example '/confluence'. Based on a - # 'ingress.host' value of 'company.k8s.com' this would result in a URL of - # 'company.k8s.com/confluence'. Default value is 'confluence.service.contextPath' - path: - # -- The custom annotations that should be applied to the Ingress Resource. # If using an ingress-nginx controller be sure that the annotations you add # here are compatible with those already defined in the 'ingess.yaml' template # annotations: {} - # -- Set to 'true' if browser communication with the application should be TLS - # (HTTPS) enforced. - # - https: true - # -- The name of the K8s Secret that contains the TLS private key and corresponding # certificate. When utilised, TLS termination occurs at the ingress point where # traffic to the Service, and it's Pods is in plaintext. @@ -576,6 +582,120 @@ ingress: # service: static-content-svc # portNumber: 80 +# Gateway API configuration +# +# Use this section when routing external traffic to Confluence via the Kubernetes +# Gateway API, or when using an external proxy/load balancer without creating +# any K8s routing resource. Only one of 'ingress' or 'gateway' should be used. +# https://gateway-api.sigs.k8s.io/ +# +gateway: + + # -- Set to 'true' if an HTTPRoute Resource should be created. This depends on a + # pre-provisioned Gateway API controller being available and a Gateway resource. + # Cannot be enabled if ingress.create is true. + # + create: false + + # -- The hostnames that should be routed to Confluence. At least one hostname + # is required when gateway.create is true. Setting hostnames activates gateway + # mode for product configuration even when gateway.create is false, allowing + # use with a pre-existing Gateway or external proxy. + # The first entry is used as the canonical hostname for base URL, proxy + # settings, and NOTES output — list the primary/public hostname first. + # + hostnames: [] + # - confluence.example.com + # - wiki.example.com + + # -- Whether users access the application over HTTPS. + # This does not configure TLS on the Gateway or load balancer — it must match + # how traffic is actually routed to the application. + # + https: true + + # -- The port users connect on. Defaults to 443 (https) or 80 (http). + # This does not change the Gateway or load balancer port — it must match + # the port that is actually used. Only set for non-standard ports. + # + # externalPort: + + # -- The base path for routing. When empty, falls back to the product's + # service.contextPath (same behavior as ingress). Set explicitly to + # override, e.g. "/confluence". + # + path: + + # --------------------------------------------------------------------------- + # The options below apply only when gateway.create is true. + # They configure the HTTPRoute resource and have no effect otherwise. + # --------------------------------------------------------------------------- + + # -- Reference to the parent Gateway resource. Supports any standard + # parentRef fields (name, namespace, sectionName, etc.). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference + # + parentRefs: [] + # - name: example-gateway + # namespace: example-gateway-namespace + # sectionName: https + + # -- Path matching type. Can be "PathPrefix", "Exact", or "RegularExpression". + # PathPrefix is recommended for most use cases. + # + pathType: "PathPrefix" + + # -- Annotations to add to the HTTPRoute resource. + # + annotations: {} + # kubernetes.io/ingress.class: gateway + # cert-manager.io/cluster-issuer: letsencrypt + + # -- Labels to add to the HTTPRoute resource. + # + labels: {} + # environment: production + # team: platform + + # -- HTTP filters to apply to requests. Can be used to add/remove headers, + # perform redirects, or rewrite URLs. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteFilter + # + filters: [] + # - type: RequestHeaderModifier + # requestHeaderModifier: + # add: + # - name: X-Forwarded-Proto + # value: https + + # -- Advanced routing rules. Use this for complex routing scenarios like + # header-based routing, traffic splitting, or multiple backends. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule + # + additionalRules: [] + # - matches: + # - path: + # type: PathPrefix + # value: /api + # backendRefs: + # - name: confluence-api + # port: 8090 + + # -- Session affinity (sticky sessions) is required for Atlassian DC products but + # is not part of the standard Gateway API HTTPRoute spec. It must be configured + # separately through your Gateway implementation's own policy resources. + # See docs/docs/examples/ingress/GATEWAY_API_SESSION_AFFINITY.md for working examples. + + # -- Timeout configuration for HTTPRoute rules. + # Note: when migrating from Ingress, these replace proxyReadTimeout and + # proxySendTimeout. There is no Gateway API equivalent for + # proxyConnectTimeout or maxBodySize — those require controller-specific + # policies (e.g. Envoy Gateway BackendTrafficPolicy). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteTimeouts + # + timeouts: + request: "60s" + backendRequest: "60s" # Confluence configuration # @@ -973,13 +1093,13 @@ confluence: acceptCount: "100" debug: "0" uriEncoding: "UTF-8" - # secure is retrieved from ingress.https value + # secure is set based on the https setting (gateway.https or ingress.https) secure: - # scheme is set depending on ingress.https value (http if false, https if true) + # scheme is set based on the https setting (http if false, https if true) scheme: - # proxyName is retrieved from ingress.host value + # proxyName is set to the configured hostname (gateway.hostnames[0] or ingress.host) proxyName: - # proxyPort is set depending on ingress.https value (80 if http, 443 if https) + # proxyPort is set to the external port (defaults to 443 for https, 80 for http) proxyPort: maxHttpHeaderSize: "8192" proxyInternalIps: diff --git a/src/main/charts/crowd/.helmignore b/src/main/charts/crowd/.helmignore index 0e8a0eb36..c7423efa9 100644 --- a/src/main/charts/crowd/.helmignore +++ b/src/main/charts/crowd/.helmignore @@ -21,3 +21,5 @@ .idea/ *.tmproj .vscode/ +# Ignore non-template files from symlinked common_templates +templates/common_templates/*.md diff --git a/src/main/charts/crowd/Chart.yaml b/src/main/charts/crowd/Chart.yaml index d733539bc..8fa329509 100644 --- a/src/main/charts/crowd/Chart.yaml +++ b/src/main/charts/crowd/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: crowd description: A chart for installing Crowd Data Center on Kubernetes type: application -version: '2.0.9' +version: '2.0.10' appVersion: 7.1.5 kubeVersion: ">=1.21.x-0" @@ -21,7 +21,8 @@ deprecated: false annotations: artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: |- - - "Update appVersions for DC apps (#1079)" + - "Add Gateway API support via HTTPRoute resources" + - "Gateway API provides modern alternative to Ingress" dependencies: - name: common diff --git a/src/main/charts/crowd/templates/NOTES.txt b/src/main/charts/crowd/templates/NOTES.txt index c9e9bf64d..f235237f9 100644 --- a/src/main/charts/crowd/templates/NOTES.txt +++ b/src/main/charts/crowd/templates/NOTES.txt @@ -13,8 +13,8 @@ To see the custom values you used for this release: $ helm get values {{ .Release.Name }} -n {{ .Release.Namespace }} -{{ if .Values.ingress.create -}} -{{ title .Chart.Name }} service URL: {{ ternary "https" "http" .Values.ingress.https -}}://{{ .Values.ingress.host }}{{ default ( "" ) .Values.ingress.path }} +{{ if eq (include "common.gateway.isConfigured" .) "true" -}} +{{ title .Chart.Name }} service URL: {{ include "common.gateway.origin" . }}{{ include "crowd.path" . }} {{- else }} Get the {{ title .Chart.Name }} URL by running these commands in the same shell: {{- if contains "NodePort" .Values.crowd.service.type }} diff --git a/src/main/charts/crowd/templates/_helpers.tpl b/src/main/charts/crowd/templates/_helpers.tpl index 10f98d061..0134daa8a 100644 --- a/src/main/charts/crowd/templates/_helpers.tpl +++ b/src/main/charts/crowd/templates/_helpers.tpl @@ -46,13 +46,6 @@ } {{- end }} -{{/* -Create default value for ingress port -*/}} -{{- define "crowd.ingressPort" -}} -{{ default (ternary "443" "80" .Values.ingress.https) .Values.ingress.port -}} -{{- end }} - {{/* The name of the service account to be used. If the name is defined in the chart values, then use that, @@ -372,3 +365,18 @@ Define additional hosts here to allow template overrides when used as a sub char set -e; cp $JAVA_HOME/lib/security/cacerts /var/ssl/cacerts; chmod 664 /var/ssl/cacerts; for crt in /tmp/crt/*.*; do echo "Adding $crt to keystore"; keytool -import -keystore /var/ssl/cacerts -storepass changeit -noprompt -alias $(echo $(basename $crt)) -file $crt; done; {{- end }} {{- end }} + + + +{{/* +Create default value for the service path. +*/}} +{{- define "crowd.path" -}} +{{- include "common.gateway.path" (dict + "useGatewayMode" (include "common.gateway.useGatewayMode" .) + "gatewayPath" .Values.gateway.path + "ingressPath" .Values.ingress.path + "contextPath" .Values.crowd.service.contextPath +) -}} +{{- end }} + diff --git a/src/main/charts/crowd/templates/common_templates b/src/main/charts/crowd/templates/common_templates new file mode 120000 index 000000000..043f40ba4 --- /dev/null +++ b/src/main/charts/crowd/templates/common_templates @@ -0,0 +1 @@ +../../common_templates \ No newline at end of file diff --git a/src/main/charts/crowd/templates/configmap-server-config.yaml b/src/main/charts/crowd/templates/configmap-server-config.yaml index 26f6c7472..d613112f8 100644 --- a/src/main/charts/crowd/templates/configmap-server-config.yaml +++ b/src/main/charts/crowd/templates/configmap-server-config.yaml @@ -32,10 +32,10 @@ data: protocol="{{ .Values.crowd.tomcatConfig.protocol | default "HTTP/1.1" }}" redirectPort="{{ .Values.crowd.tomcatConfig.redirectPort | default "8443" }}" acceptCount="{{ .Values.crowd.tomcatConfig.acceptCount | default "100" }}" - secure="{{ default (ternary "true" "false" .Values.ingress.https) .Values.crowd.tomcatConfig.secure }}" - scheme="{{ default (ternary "https" "http" .Values.ingress.https) .Values.crowd.tomcatConfig.scheme }}" - proxyName="{{ .Values.crowd.tomcatConfig.proxyName | default .Values.ingress.host }}" - proxyPort="{{ .Values.crowd.tomcatConfig.proxyPort | default (ternary "443" "80" .Values.ingress.https) }}" + secure="{{ default (include "common.gateway.https" .) .Values.crowd.tomcatConfig.secure }}" + scheme="{{ default (include "common.gateway.scheme" .) .Values.crowd.tomcatConfig.scheme }}" + proxyName="{{ .Values.crowd.tomcatConfig.proxyName | default (include "common.gateway.hostname" .) }}" + proxyPort="{{ .Values.crowd.tomcatConfig.proxyPort | default (include "common.gateway.externalPort" .) }}" maxHttpHeaderSize="{{ .Values.crowd.tomcatConfig.maxHttpHeaderSize | default "8192" }}" useBodyEncodingForURI="true" URIEncoding="UTF-8" diff --git a/src/main/charts/crowd/templates/httproute.yaml b/src/main/charts/crowd/templates/httproute.yaml new file mode 100644 index 000000000..528d49eab --- /dev/null +++ b/src/main/charts/crowd/templates/httproute.yaml @@ -0,0 +1,50 @@ +{{- if .Values.gateway.create }} +{{- include "common.gateway.validateConfig" . -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "common.names.fullname" . }} + labels: + {{- include "common.labels.commonLabels" . | nindent 4 }} + {{- with .Values.gateway.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.gateway.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + # Reference to the Gateway + parentRefs: + {{- toYaml .Values.gateway.parentRefs | nindent 2 }} + + # Hostnames to match + hostnames: + {{- range .Values.gateway.hostnames }} + - {{ . | quote }} + {{- end }} + + # Routing rules + rules: + # Default rule - routes to Crowd service + - matches: + - path: + type: {{ .Values.gateway.pathType }} + value: {{ include "crowd.path" . }} + {{- with .Values.gateway.timeouts }} + timeouts: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.gateway.filters }} + filters: + {{- toYaml . | nindent 4 }} + {{- end }} + backendRefs: + - name: {{ include "common.names.fullname" . }} + port: {{ .Values.crowd.service.port }} + weight: 100 + + {{- with .Values.gateway.additionalRules }} + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/src/main/charts/crowd/templates/statefulset.yaml b/src/main/charts/crowd/templates/statefulset.yaml index ae1b74f2d..2460f8e8a 100644 --- a/src/main/charts/crowd/templates/statefulset.yaml +++ b/src/main/charts/crowd/templates/statefulset.yaml @@ -1,3 +1,4 @@ +{{- include "common.gateway.validateConfig" . -}} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -192,7 +193,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name - {{ if .Values.ingress.https }} + {{ if eq (include "common.gateway.https" .) "true" }} - name: ATL_TOMCAT_SCHEME value: "https" - name: ATL_TOMCAT_SECURE @@ -204,11 +205,11 @@ spec: {{ end }} - name: ATL_TOMCAT_PORT value: {{ .Values.crowd.ports.http | quote }} - {{ if .Values.ingress.host }} + {{ if eq (include "common.gateway.isConfigured" .) "true" }} - name: ATL_PROXY_NAME - value: {{ .Values.ingress.host | quote }} + value: {{ include "common.gateway.hostname" . | quote }} - name: ATL_PROXY_PORT - value: {{ include "crowd.ingressPort" . | quote }} + value: {{ include "common.gateway.externalPort" . | quote }} {{ end }} - name: ATL_TOMCAT_ACCESS_LOG value: {{ .Values.crowd.accessLog.enabled | quote }} diff --git a/src/main/charts/crowd/values.yaml b/src/main/charts/crowd/values.yaml index 6458e5e05..caede1952 100644 --- a/src/main/charts/crowd/values.yaml +++ b/src/main/charts/crowd/values.yaml @@ -9,10 +9,11 @@ # Additional details on pre-provisioning these required resources can be found here: # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#5-configure-persistent-storage # -# To manage external access to the Crowd instance, an ingress resource can also be configured -# under the 'ingress' stanza. This requires a pre-provisioned ingress controller to be present. +# To manage external access to the Crowd instance, a Gateway API HTTPRoute or an Ingress +# resource can be configured under the 'gateway' or 'ingress' stanza respectively. +# This requires a pre-provisioned gateway/ingress controller to be present. # -# Additional details on pre-provisioning an ingress controller can be found here: +# Additional details on configuring external access can be found here: # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#4-configure-ingress # ## @@ -384,13 +385,13 @@ crowd: protocol: "HTTP/1.1" redirectPort: "8443" acceptCount: "100" - # secure is retrieved from ingress.https value + # secure is set based on the https setting (gateway.https or ingress.https) secure: - # scheme is set depending on ingress.https value (http if false, https if true) + # scheme is set based on the https setting (http if false, https if true) scheme: - # proxyName is retrieved from ingress.host value + # proxyName is set to the configured hostname (gateway.hostnames[0] or ingress.host) proxyName: - # proxyPort is set depending on ingress.https value (80 if http, 443 if https) + # proxyPort is set to the external port (defaults to 443 for https, 80 for http) proxyPort: maxHttpHeaderSize: "8192" accessLogsMaxDays: "-1" @@ -500,9 +501,9 @@ crowd: # Ingress configuration # -# To make the Atlassian product available from outside the K8s cluster an Ingress -# Controller should be pre-provisioned. With this in place the configuration below -# can be used to configure an appropriate Ingress Resource. +# Use this section when routing external traffic to Crowd via a Kubernetes Ingress +# resource (K8s Ingress API). Requires a pre-provisioned Ingress Controller. +# If using the Gateway API instead, see the 'gateway' section below. # https://atlassian.github.io/data-center-helm-charts/userguide/CONFIGURATION/#ingress # ingress: @@ -512,6 +513,26 @@ ingress: # create: false + # -- The fully-qualified hostname (FQDN) of the Crowd instance. This value is used + # to configure the product's proxy settings and, when ingress.create is true, + # the Ingress resource routing rules. + # + host: + + # -- Whether users access the application over HTTPS. Set to 'false' if not + # using TLS, e.g. when reaching the service via localhost port-forwarding. + # + https: true + + # -- The base path for the application, e.g. '/crowd'. + # + path: "/" + + # --------------------------------------------------------------------------- + # The options below apply only when ingress.create is true. + # They configure the Ingress resource itself and have no effect otherwise. + # --------------------------------------------------------------------------- + # -- Set to true if you want to create an OpenShift Route instead of an Ingress # openShiftRoute: false @@ -563,29 +584,12 @@ ingress: # proxySendTimeout: 60 - # -- The fully-qualified hostname (FQDN) of the Ingress Resource. Traffic coming in on - # this hostname will be routed by the Ingress Resource to the appropriate backend - # Service. - # - host: - - # -- The base path for the Ingress Resource. For example '/crowd'. Based on - # 'ingress.host' value of 'company.k8s.com' this would result in a URL of - # 'company.k8s.com/crowd'. - # - path: "/" - # -- The custom annotations that should be applied to the Ingress Resource. # If using an ingress-nginx controller be sure that the annotations you add # here are compatible with those already defined in the 'ingess.yaml' template # annotations: {} - # -- Set to 'true' if browser communication with the application should be TLS - # (HTTPS) enforced. - # - https: true - # -- The name of the K8s Secret that contains the TLS private key and corresponding # certificate. When utilised, TLS termination occurs at the ingress point where # traffic to the Service, and it's Pods is in plaintext. @@ -605,6 +609,120 @@ ingress: # service: static-content-svc # portNumber: 80 +# Gateway API configuration +# +# Use this section when routing external traffic to Crowd via the Kubernetes +# Gateway API, or when using an external proxy/load balancer without creating +# any K8s routing resource. Only one of 'ingress' or 'gateway' should be used. +# https://gateway-api.sigs.k8s.io/ +# +gateway: + + # -- Set to 'true' if an HTTPRoute Resource should be created. This depends on a + # pre-provisioned Gateway API controller being available and a Gateway resource. + # Cannot be enabled if ingress.create is true. + # + create: false + + # -- The hostnames that should be routed to Crowd. At least one hostname + # is required when gateway.create is true. Setting hostnames activates gateway + # mode for product configuration even when gateway.create is false, allowing + # use with a pre-existing Gateway or external proxy. + # The first entry is used as the canonical hostname for base URL, proxy + # settings, and NOTES output — list the primary/public hostname first. + # + hostnames: [] + # - crowd.example.com + # - identity.example.com + + # -- Whether users access the application over HTTPS. + # This does not configure TLS on the Gateway or load balancer — it must match + # how traffic is actually routed to the application. + # + https: true + + # -- The port users connect on. Defaults to 443 (https) or 80 (http). + # This does not change the Gateway or load balancer port — it must match + # the port that is actually used. Only set for non-standard ports. + # + # externalPort: + + # -- The base path for routing. When empty, falls back to the product's + # service.contextPath (same behavior as ingress). Set explicitly to + # override, e.g. "/crowd". + # + path: "/" + + # --------------------------------------------------------------------------- + # The options below apply only when gateway.create is true. + # They configure the HTTPRoute resource and have no effect otherwise. + # --------------------------------------------------------------------------- + + # -- Reference to the parent Gateway resource. Supports any standard + # parentRef fields (name, namespace, sectionName, etc.). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference + # + parentRefs: [] + # - name: example-gateway + # namespace: example-gateway-namespace + # sectionName: https + + # -- Path matching type. Can be "PathPrefix", "Exact", or "RegularExpression". + # PathPrefix is recommended for most use cases. + # + pathType: "PathPrefix" + + # -- Annotations to add to the HTTPRoute resource. + # + annotations: {} + # kubernetes.io/ingress.class: gateway + # cert-manager.io/cluster-issuer: letsencrypt + + # -- Labels to add to the HTTPRoute resource. + # + labels: {} + # environment: production + # team: platform + + # -- HTTP filters to apply to requests. Can be used to add/remove headers, + # perform redirects, or rewrite URLs. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteFilter + # + filters: [] + # - type: RequestHeaderModifier + # requestHeaderModifier: + # add: + # - name: X-Forwarded-Proto + # value: https + + # -- Advanced routing rules. Use this for complex routing scenarios like + # header-based routing, traffic splitting, or multiple backends. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule + # + additionalRules: [] + # - matches: + # - path: + # type: PathPrefix + # value: /api + # backendRefs: + # - name: crowd-api + # port: 8095 + + # -- Session affinity (sticky sessions) is required for Atlassian DC products but + # is not part of the standard Gateway API HTTPRoute spec. It must be configured + # separately through your Gateway implementation's own policy resources. + # See docs/docs/examples/ingress/GATEWAY_API_SESSION_AFFINITY.md for working examples. + + # -- Timeout configuration for HTTPRoute rules. + # Note: when migrating from Ingress, these replace proxyReadTimeout and + # proxySendTimeout. There is no Gateway API equivalent for + # proxyConnectTimeout or maxBodySize — those require controller-specific + # policies (e.g. Envoy Gateway BackendTrafficPolicy). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteTimeouts + # + timeouts: + request: "60s" + backendRequest: "60s" # Fluentd configuration # diff --git a/src/main/charts/jira/.helmignore b/src/main/charts/jira/.helmignore index 0e8a0eb36..c7423efa9 100644 --- a/src/main/charts/jira/.helmignore +++ b/src/main/charts/jira/.helmignore @@ -21,3 +21,5 @@ .idea/ *.tmproj .vscode/ +# Ignore non-template files from symlinked common_templates +templates/common_templates/*.md diff --git a/src/main/charts/jira/Chart.yaml b/src/main/charts/jira/Chart.yaml index e2107d34d..15483692c 100644 --- a/src/main/charts/jira/Chart.yaml +++ b/src/main/charts/jira/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: jira description: A chart for installing Jira Data Center on Kubernetes type: application -version: '2.0.9' +version: '2.0.10' appVersion: 11.3.3 kubeVersion: ">=1.21.x-0" keywords: @@ -22,7 +22,8 @@ deprecated: false annotations: artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: |- - - "Update Helm chart version" + - "Add Gateway API support via HTTPRoute resources" + - "Gateway API provides modern alternative to Ingress" dependencies: - name: common diff --git a/src/main/charts/jira/templates/NOTES.txt b/src/main/charts/jira/templates/NOTES.txt index f3323c049..2ed8f854b 100644 --- a/src/main/charts/jira/templates/NOTES.txt +++ b/src/main/charts/jira/templates/NOTES.txt @@ -13,8 +13,8 @@ To see the custom values you used for this release: $ helm get values {{ .Release.Name }} -n {{ .Release.Namespace }} -{{- if .Values.ingress.create -}} -{{ title .Chart.Name }} service URL: {{ ternary "https" "http" .Values.ingress.https -}}://{{ .Values.ingress.host }}{{ include "jira.ingressPath" . }} +{{- if eq (include "common.gateway.isConfigured" .) "true" -}} +{{ title .Chart.Name }} service URL: {{ include "common.gateway.origin" . }}{{ include "jira.path" . }} {{- else }} Get the {{ title .Chart.Name }} URL by running these commands in the same shell: {{- if contains "NodePort" .Values.jira.service.type }} diff --git a/src/main/charts/jira/templates/_helpers.tpl b/src/main/charts/jira/templates/_helpers.tpl index 4f9cc1b20..075dd99b2 100644 --- a/src/main/charts/jira/templates/_helpers.tpl +++ b/src/main/charts/jira/templates/_helpers.tpl @@ -109,21 +109,22 @@ {{- end }} {{/* -Create default value for ingress port +Create default value for the service path. */}} -{{- define "jira.ingressPort" -}} -{{ default (ternary "443" "80" .Values.ingress.https) .Values.ingress.port -}} +{{- define "jira.path" -}} +{{- include "common.gateway.path" (dict + "useGatewayMode" (include "common.gateway.useGatewayMode" .) + "gatewayPath" .Values.gateway.path + "ingressPath" .Values.ingress.path + "contextPath" .Values.jira.service.contextPath +) -}} {{- end }} {{/* -Create default value for ingress path +Alias for backward compatibility with ingress templates. */}} {{- define "jira.ingressPath" -}} -{{- if .Values.ingress.path -}} -{{- .Values.ingress.path -}} -{{- else -}} -{{ default ( "/" ) .Values.jira.service.contextPath -}} -{{- end }} +{{- include "jira.path" . -}} {{- end }} {{/* @@ -545,3 +546,5 @@ volumeClaimTemplates: set -e; cp $JAVA_HOME/lib/security/cacerts /var/ssl/cacerts; chmod 664 /var/ssl/cacerts; for crt in /tmp/crt/*.*; do echo "Adding $crt to keystore"; keytool -import -keystore /var/ssl/cacerts -storepass changeit -noprompt -alias $(echo $(basename $crt)) -file $crt; done; {{- end }} {{- end }} + + diff --git a/src/main/charts/jira/templates/common_templates b/src/main/charts/jira/templates/common_templates new file mode 120000 index 000000000..043f40ba4 --- /dev/null +++ b/src/main/charts/jira/templates/common_templates @@ -0,0 +1 @@ +../../common_templates \ No newline at end of file diff --git a/src/main/charts/jira/templates/configmap-server-config.yaml b/src/main/charts/jira/templates/configmap-server-config.yaml index 480f9ba43..cc258c3d6 100644 --- a/src/main/charts/jira/templates/configmap-server-config.yaml +++ b/src/main/charts/jira/templates/configmap-server-config.yaml @@ -32,10 +32,10 @@ data: protocol="{{ .Values.jira.tomcatConfig.protocol | default "HTTP/1.1" }}" redirectPort="{{ .Values.jira.tomcatConfig.redirectPort | default "8443" }}" acceptCount="{{ .Values.jira.tomcatConfig.acceptCount | default "100" }}" - secure="{{ default (ternary "true" "false" .Values.ingress.https) .Values.jira.tomcatConfig.secure }}" - scheme="{{ default (ternary "https" "http" .Values.ingress.https) .Values.jira.tomcatConfig.scheme }}" - proxyName="{{ .Values.jira.tomcatConfig.proxyName | default .Values.ingress.host }}" - proxyPort="{{ .Values.jira.tomcatConfig.proxyPort | default (ternary "443" "80" .Values.ingress.https) }}" + secure="{{ default (include "common.gateway.https" .) .Values.jira.tomcatConfig.secure }}" + scheme="{{ default (include "common.gateway.scheme" .) .Values.jira.tomcatConfig.scheme }}" + proxyName="{{ .Values.jira.tomcatConfig.proxyName | default (include "common.gateway.hostname" .) }}" + proxyPort="{{ .Values.jira.tomcatConfig.proxyPort | default (include "common.gateway.externalPort" .) }}" relaxedPathChars="[]|" relaxedQueryChars="[]|{}^\`"<>" diff --git a/src/main/charts/jira/templates/httproute.yaml b/src/main/charts/jira/templates/httproute.yaml new file mode 100644 index 000000000..afd6b7e5e --- /dev/null +++ b/src/main/charts/jira/templates/httproute.yaml @@ -0,0 +1,50 @@ +{{- if .Values.gateway.create }} +{{- include "common.gateway.validateConfig" . -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "common.names.fullname" . }} + labels: + {{- include "common.labels.commonLabels" . | nindent 4 }} + {{- with .Values.gateway.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.gateway.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + # Reference to the Gateway + parentRefs: + {{- toYaml .Values.gateway.parentRefs | nindent 2 }} + + # Hostnames to match + hostnames: + {{- range .Values.gateway.hostnames }} + - {{ . | quote }} + {{- end }} + + # Routing rules + rules: + # Default rule - routes to Jira service + - matches: + - path: + type: {{ .Values.gateway.pathType }} + value: {{ include "jira.path" . }} + {{- with .Values.gateway.timeouts }} + timeouts: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.gateway.filters }} + filters: + {{- toYaml . | nindent 4 }} + {{- end }} + backendRefs: + - name: {{ include "common.names.fullname" . }} + port: {{ .Values.jira.service.port }} + weight: 100 + + {{- with .Values.gateway.additionalRules }} + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/src/main/charts/jira/templates/statefulset.yaml b/src/main/charts/jira/templates/statefulset.yaml index bf92fe615..d03217ac0 100644 --- a/src/main/charts/jira/templates/statefulset.yaml +++ b/src/main/charts/jira/templates/statefulset.yaml @@ -1,3 +1,4 @@ +{{- include "common.gateway.validateConfig" . -}} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -119,7 +120,7 @@ spec: env: {{- include "jira.sessionVars" . | nindent 12 }} {{- include "jira.tunnelVars" . | nindent 12 }} - {{ if .Values.ingress.https }} + {{ if eq (include "common.gateway.https" .) "true" }} - name: ATL_TOMCAT_SCHEME value: "https" - name: ATL_TOMCAT_SECURE @@ -131,11 +132,11 @@ spec: {{ end }} - name: ATL_TOMCAT_PORT value: {{ .Values.jira.ports.http | quote }} - {{ if .Values.ingress.host }} + {{ if eq (include "common.gateway.isConfigured" .) "true" }} - name: ATL_PROXY_NAME - value: {{ .Values.ingress.host | quote }} + value: {{ include "common.gateway.hostname" . | quote }} - name: ATL_PROXY_PORT - value: {{ include "jira.ingressPort" . | quote }} + value: {{ include "common.gateway.externalPort" . | quote }} {{ end }} {{- include "jira.s3StorageEnvVars" . | nindent 12 }} {{- include "jira.databaseEnvVars" . | nindent 12 }} diff --git a/src/main/charts/jira/values.yaml b/src/main/charts/jira/values.yaml index 8ef066712..903371656 100644 --- a/src/main/charts/jira/values.yaml +++ b/src/main/charts/jira/values.yaml @@ -11,10 +11,11 @@ # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#3-configure-database # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#5-configure-persistent-storage # -# To manage external access to the Jira instance, an ingress resource can also be configured -# under the 'ingress' stanza. This requires a pre-provisioned ingress controller to be present. +# To manage external access to the Jira instance, a Gateway API HTTPRoute or an Ingress +# resource can be configured under the 'gateway' or 'ingress' stanza respectively. +# This requires a pre-provisioned gateway/ingress controller to be present. # -# Additional details on pre-provisioning an ingress controller can be found here: +# Additional details on configuring external access can be found here: # https://atlassian.github.io/data-center-helm-charts/userguide/INSTALLATION/#4-configure-ingress # ## @@ -361,9 +362,9 @@ volumes: # Ingress configuration # -# To make the Atlassian product available from outside the K8s cluster an Ingress -# Controller should be pre-provisioned. With this in place the configuration below -# can be used to configure an appropriate Ingress Resource. +# Use this section when routing external traffic to Jira via a Kubernetes Ingress +# resource (K8s Ingress API). Requires a pre-provisioned Ingress Controller. +# If using the Gateway API instead, see the 'gateway' section below. # https://atlassian.github.io/data-center-helm-charts/userguide/CONFIGURATION/#ingress # ingress: @@ -373,6 +374,27 @@ ingress: # create: false + # -- The fully-qualified hostname (FQDN) of the Jira instance. This value is used + # to configure the product's proxy settings and, when ingress.create is true, + # the Ingress resource routing rules. + # + host: + + # -- Whether users access the application over HTTPS. Set to 'false' if not + # using TLS, e.g. when reaching the service via localhost port-forwarding. + # + https: true + + # -- The base path for the application, e.g. '/jira'. + # Defaults to 'jira.service.contextPath'. + # + path: + + # --------------------------------------------------------------------------- + # The options below apply only when ingress.create is true. + # They configure the Ingress resource itself and have no effect otherwise. + # --------------------------------------------------------------------------- + # -- Set to true if you want to create an OpenShift Route instead of an Ingress # openShiftRoute: false @@ -424,29 +446,12 @@ ingress: # proxySendTimeout: 60 - # -- The fully-qualified hostname (FQDN) of the Ingress Resource. Traffic coming in on - # this hostname will be routed by the Ingress Resource to the appropriate backend - # Service. - # - host: - - # -- The base path for the Ingress Resource. For example '/jira'. Based on a - # 'ingress.host' value of 'company.k8s.com' this would result in a URL of - # 'company.k8s.com/jira'. Default value is 'jira.service.contextPath' - # - path: - # -- The custom annotations that should be applied to the Ingress Resource. # If using an ingress-nginx controller be sure that the annotations you add # here are compatible with those already defined in the 'ingess.yaml' template # annotations: {} - # -- Set to 'true' if browser communication with the application should be TLS - # (HTTPS) enforced. - # - https: true - # -- The name of the K8s Secret that contains the TLS private key and corresponding # certificate. When utilised, TLS termination occurs at the ingress point where # traffic to the Service, and it's Pods is in plaintext. @@ -466,6 +471,120 @@ ingress: # service: static-content-svc # portNumber: 80 +# Gateway API configuration +# +# Use this section when routing external traffic to Jira via the Kubernetes +# Gateway API, or when using an external proxy/load balancer without creating +# any K8s routing resource. Only one of 'ingress' or 'gateway' should be used. +# https://gateway-api.sigs.k8s.io/ +# +gateway: + + # -- Set to 'true' if an HTTPRoute Resource should be created. This depends on a + # pre-provisioned Gateway API controller being available and a Gateway resource. + # Cannot be enabled if ingress.create is true. + # + create: false + + # -- The hostnames that should be routed to Jira. At least one hostname + # is required when gateway.create is true. Setting hostnames activates gateway + # mode for product configuration even when gateway.create is false, allowing + # use with a pre-existing Gateway or external proxy. + # The first entry is used as the canonical hostname for base URL, proxy + # settings, and NOTES output — list the primary/public hostname first. + # + hostnames: [] + # - jira.example.com + # - jira-prod.example.com + + # -- Whether users access the application over HTTPS. + # This does not configure TLS on the Gateway or load balancer — it must match + # how traffic is actually routed to the application. + # + https: true + + # -- The port users connect on. Defaults to 443 (https) or 80 (http). + # This does not change the Gateway or load balancer port — it must match + # the port that is actually used. Only set for non-standard ports. + # + # externalPort: + + # -- The base path for routing. When empty, falls back to the product's + # service.contextPath (same behavior as ingress). Set explicitly to + # override, e.g. "/jira". + # + path: + + # --------------------------------------------------------------------------- + # The options below apply only when gateway.create is true. + # They configure the HTTPRoute resource and have no effect otherwise. + # --------------------------------------------------------------------------- + + # -- Reference to the parent Gateway resource. Supports any standard + # parentRef fields (name, namespace, sectionName, etc.). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference + # + parentRefs: [] + # - name: example-gateway + # namespace: example-gateway-namespace + # sectionName: https + + # -- Path matching type. Can be "PathPrefix", "Exact", or "RegularExpression". + # PathPrefix is recommended for most use cases. + # + pathType: "PathPrefix" + + # -- Annotations to add to the HTTPRoute resource. + # + annotations: {} + # kubernetes.io/ingress.class: gateway + # cert-manager.io/cluster-issuer: letsencrypt + + # -- Labels to add to the HTTPRoute resource. + # + labels: {} + # environment: production + # team: platform + + # -- HTTP filters to apply to requests. Can be used to add/remove headers, + # perform redirects, or rewrite URLs. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteFilter + # + filters: [] + # - type: RequestHeaderModifier + # requestHeaderModifier: + # add: + # - name: X-Forwarded-Proto + # value: https + + # -- Advanced routing rules. Use this for complex routing scenarios like + # header-based routing, traffic splitting, or multiple backends. + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule + # + additionalRules: [] + # - matches: + # - path: + # type: PathPrefix + # value: /api + # backendRefs: + # - name: jira-api + # port: 8080 + + # -- Session affinity (sticky sessions) is required for Atlassian DC products but + # is not part of the standard Gateway API HTTPRoute spec. It must be configured + # separately through your Gateway implementation's own policy resources. + # See docs/docs/examples/ingress/GATEWAY_API_SESSION_AFFINITY.md for working examples. + + # -- Timeout configuration for HTTPRoute rules. + # Note: when migrating from Ingress, these replace proxyReadTimeout and + # proxySendTimeout. There is no Gateway API equivalent for + # proxyConnectTimeout or maxBodySize — those require controller-specific + # policies (e.g. Envoy Gateway BackendTrafficPolicy). + # See: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteTimeouts + # + timeouts: + request: "60s" + backendRequest: "60s" # Jira configuration # @@ -839,13 +958,13 @@ jira: protocol: "HTTP/1.1" redirectPort: "8443" acceptCount: "100" - # secure is retrieved from ingress.https value + # secure is set based on the https setting (gateway.https or ingress.https) secure: - # scheme is set depending on ingress.https value (http if false, https if true) + # scheme is set based on the https setting (http if false, https if true) scheme: - # proxyName is retrieved from ingress.host value + # proxyName is set to the configured hostname (gateway.hostnames[0] or ingress.host) proxyName: - # proxyPort is set depending on ingress.https value (80 if http, 443 if https) + # proxyPort is set to the external port (defaults to 443 for https, 80 for http) proxyPort: maxHttpHeaderSize: "8192" stuckThreadDetectionValveThreshold: "120" diff --git a/src/test/config/kind/common-values.yaml b/src/test/config/kind/common-values.yaml index 0ffa2853f..04877465f 100644 --- a/src/test/config/kind/common-values.yaml +++ b/src/test/config/kind/common-values.yaml @@ -108,15 +108,15 @@ podAnnotations: quote: "true" normal: annotation-comes-here -# KinD is deployed with custom settings, namely extraPortMappings for ports 80 and 443 -# to make sure ingress traffic is available at http://localhost -ingress: +# KinD tests use Gateway API with Envoy Gateway. +# Envoy proxy is exposed via NodePort(30080) -> hostPort(80); dc-app.test resolves via /etc/hosts. +gateway: create: true - host: localhost + parentRefs: + - name: atlassian-gateway + hostnames: + - dc-app.test https: false - proxyConnectTimeout: 300 - proxyReadTimeout: 300 - proxySendTimeout: 300 monitoring: exposeJmxMetrics: true diff --git a/src/test/config/kind/envoy-proxy.yaml b/src/test/config/kind/envoy-proxy.yaml new file mode 100644 index 000000000..8fe5a78a6 --- /dev/null +++ b/src/test/config/kind/envoy-proxy.yaml @@ -0,0 +1,30 @@ +# EnvoyProxy tells Envoy Gateway *how* to create the data-plane proxy +# for any Gateway whose GatewayClass references this resource. +# +# Key behaviour: +# - The proxy Service is created as NodePort (not the default ClusterIP/LB). +# - A strategic-merge patch pins nodePort: 30080 for the HTTP listener, +# matching the KinD extraPortMappings (host 80 → node 30080). +# +# This removes the need for any post-hoc `kubectl patch` in configure_kind.sh. +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: kind-proxy-config + namespace: envoy-gateway-system +spec: + provider: + type: Kubernetes + kubernetes: + envoyService: + type: NodePort + # Pin the HTTP listener to a fixed NodePort so it aligns with + # the KinD extraPortMappings entry (host :80 → node :30080). + patch: + type: StrategicMerge + value: + spec: + ports: + - port: 80 + nodePort: 30080 + protocol: TCP diff --git a/src/test/config/kind/gateway.yaml b/src/test/config/kind/gateway.yaml new file mode 100644 index 000000000..b1b32933d --- /dev/null +++ b/src/test/config/kind/gateway.yaml @@ -0,0 +1,21 @@ +# Gateway API Gateway resource for testing +# This Gateway is used by the KinD test environment to enable HTTPRoute testing +# alongside traditional Ingress tests. +# +# The Gateway uses the Envoy Gateway controller (gatewayClassName: eg) +# and listens on port 80 for HTTP traffic. +# +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: atlassian-gateway + namespace: atlassian +spec: + gatewayClassName: eg + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same diff --git a/src/test/config/kind/kind-config.yml b/src/test/config/kind/kind-config.yml index f714d6429..96f6490a4 100644 --- a/src/test/config/kind/kind-config.yml +++ b/src/test/config/kind/kind-config.yml @@ -2,29 +2,15 @@ kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: apiServerAddress: "127.0.0.1" - apiServerPort: 6443 nodes: - role: control-plane - # this is required due to weird nginx controller behavior where it needs to redirect smth to - # /dev/null but can't do it due to permission issues. The workaround is to mount /dev into control plane container - extraMounts: - - hostPath: /dev - containerPath: /dev - # the below config is required to properly install and use Nginx ingress - # and use localhost to access DC applications - kubeadmConfigPatches: - - | - kind: InitConfiguration - nodeRegistration: - kubeletExtraArgs: - node-labels: "ingress-ready=true" + # Previously we mounted /dev + labelled ingress-ready to support ingress-nginx. + # KinD tests now run Gateway API via Envoy Gateway, exposed through a fixed NodePort. extraPortMappings: - - containerPort: 80 + # Route host traffic to Envoy Gateway proxy via NodePort + - containerPort: 30080 hostPort: 80 protocol: TCP - - containerPort: 443 - hostPort: 443 - protocol: TCP # map registry NodePort to host port to be able to # build and push images to an internal registry - containerPort: 32767 diff --git a/src/test/config/openshift/envoy-proxy.yaml b/src/test/config/openshift/envoy-proxy.yaml new file mode 100644 index 000000000..efecd7a21 --- /dev/null +++ b/src/test/config/openshift/envoy-proxy.yaml @@ -0,0 +1,19 @@ +# EnvoyProxy tells Envoy Gateway *how* to create the data-plane proxy +# for any Gateway whose GatewayClass references this resource. +# +# On OpenShift/MicroShift we keep the default ClusterIP and expose +# Envoy via an OpenShift Route (created in the workflow). +# This resource exists so the GatewayClass can use a consistent +# parametersRef pattern across environments. +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: openshift-proxy-config + namespace: envoy-gateway-system +spec: + provider: + type: Kubernetes + kubernetes: + envoyService: + # ClusterIP is fine here — the OpenShift Route handles external access. + type: ClusterIP diff --git a/src/test/config/openshift/gateway.yaml b/src/test/config/openshift/gateway.yaml new file mode 100644 index 000000000..f902c3973 --- /dev/null +++ b/src/test/config/openshift/gateway.yaml @@ -0,0 +1,21 @@ +# Gateway API Gateway resource for OpenShift/MicroShift testing +# This Gateway is used by the OpenShift test environment to enable HTTPRoute testing +# alongside traditional Route tests. +# +# The Gateway uses a generic Gateway controller +# and listens on port 80 for HTTP traffic. +# +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: atlassian-gateway + namespace: atlassian +spec: + gatewayClassName: eg + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same diff --git a/src/test/config/openshift/openshift.yaml b/src/test/config/openshift/openshift.yaml index 7a872add9..37c1c931a 100644 --- a/src/test/config/openshift/openshift.yaml +++ b/src/test/config/openshift/openshift.yaml @@ -12,6 +12,10 @@ volumes: openshift: runWithRestrictedSCC: true -ingress: - openShiftRoute: true - host: atlassian.apps.crc.testing +gateway: + create: true + parentRefs: + - name: atlassian-gateway + hostnames: + - atlassian.apps.crc.testing + https: false diff --git a/src/test/java/test/GatewayTest.java b/src/test/java/test/GatewayTest.java new file mode 100644 index 000000000..2ec4855d8 --- /dev/null +++ b/src/test/java/test/GatewayTest.java @@ -0,0 +1,293 @@ +package test; + +import org.assertj.core.api.AbstractStringAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import test.helm.Helm; +import test.model.Kind; +import test.model.Product; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static test.jackson.JsonNodeAssert.assertThat; + +class GatewayTest { + private Helm helm; + + @BeforeEach + void initHelm(TestInfo testInfo) { + helm = new Helm(testInfo); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_creates_httproute_with_parent_ref_and_hostname(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com")); + + final var httpRoutes = resources.getAll(Kind.HTTPRoute); + assertEquals(1, httpRoutes.size()); + + final var httpRoute = httpRoutes.head(); + assertThat(httpRoute.getNode("spec", "parentRefs").required(0).path("name")) + .hasTextEqualTo("my-gateway"); + assertThat(httpRoute.getNode("spec", "hostnames").required(0)) + .hasTextEqualTo(product + ".example.com"); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_cannot_coexist_with_ingress(Product product) { + assertThrowsAssertionWithMessage( + "ERROR: Cannot enable both gateway.create and ingress.create", + () -> helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "ingress.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com")) + ); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_requires_parentRefs_name(Product product) { + assertThrowsAssertionWithMessage( + "ERROR: gateway.parentRefs[0].name is required when gateway.create is true", + () -> helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].namespace", "gateway-system", + "gateway.hostnames[0]", product + ".example.com")) + ); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_requires_hostnames(Product product) { + assertThrowsAssertionWithMessage( + "ERROR: gateway.hostnames must contain at least one hostname when gateway.create is true", + () -> helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway")) + ); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_custom_path_is_applied_to_route_match(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com", + "gateway.path", "/" + product)); + + final var httpRoute = resources.get(Kind.HTTPRoute); + assertThat(httpRoute.getNode("spec", "rules").required(0) + .path("matches").required(0).path("path").path("value")) + .hasTextEqualTo("/" + product); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_supports_multiple_hostnames(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com", + "gateway.hostnames[1]", product + "-alt.example.com")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + assertThat(httpRoute.getNode("spec", "hostnames").required(0)) + .hasTextEqualTo(product + ".example.com"); + assertThat(httpRoute.getNode("spec", "hostnames").required(1)) + .hasTextEqualTo(product + "-alt.example.com"); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_namespace_is_set_on_parent_ref(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.parentRefs[0].namespace", "gateway-system", + "gateway.hostnames[0]", product + ".example.com")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + assertThat(httpRoute.getNode("spec", "parentRefs").required(0).path("namespace")) + .hasTextEqualTo("gateway-system"); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_supports_all_path_types(Product product) throws Exception { + for (String pathType : new String[]{"PathPrefix", "Exact", "RegularExpression"}) { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com", + "gateway.pathType", pathType)); + + final var httpRoute = resources.get(Kind.HTTPRoute); + assertThat(httpRoute.getNode("spec", "rules").required(0) + .path("matches").required(0).path("path").path("type")) + .hasTextEqualTo(pathType); + } + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_custom_annotations_are_applied(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com", + "gateway.annotations.cert-manager\\.io/cluster-issuer", "letsencrypt")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + assertThat(httpRoute.getNode("metadata", "annotations") + .path("cert-manager.io/cluster-issuer")) + .hasTextEqualTo("letsencrypt"); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_custom_labels_are_applied(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com", + "gateway.labels.environment", "production")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + assertThat(httpRoute.getNode("metadata", "labels").path("environment")) + .hasTextEqualTo("production"); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_backend_ref_targets_product_service(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + final var backendRef = httpRoute.getNode("spec", "rules").required(0) + .path("backendRefs").required(0); + + assertThat(backendRef.path("name")) + .hasTextContaining(product.toString()); + assertEquals(80, backendRef.path("port").asInt()); + assertEquals(100, backendRef.path("weight").asInt()); + } + + @Test + void gateway_routes_synchrony_traffic_when_enabled() throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(Product.confluence, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", "confluence.example.com", + "synchrony.enabled", "true")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + + assertThat(httpRoute.getNode("spec", "rules").required(0) + .path("backendRefs").required(0).path("name")) + .hasTextContaining("synchrony"); + + assertThat(httpRoute.getNode("spec", "rules").required(1) + .path("backendRefs").required(0).path("name")) + .hasTextContaining("confluence"); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_custom_timeouts_are_applied(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com", + "gateway.timeouts.request", "120s", + "gateway.timeouts.backendRequest", "60s")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + final var rule = httpRoute.getNode("spec", "rules").required(0); + assertThat(rule.path("timeouts").path("request")) + .hasTextEqualTo("120s"); + assertThat(rule.path("timeouts").path("backendRequest")) + .hasTextEqualTo("60s"); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_has_default_timeouts(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + final var rule = httpRoute.getNode("spec", "rules").required(0); + assertThat(rule.path("timeouts").path("request")) + .hasTextEqualTo("60s"); + assertThat(rule.path("timeouts").path("backendRequest")) + .hasTextEqualTo("60s"); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent", "crowd"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_default_path_is_root(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + assertThat(httpRoute.getNode("spec", "rules").required(0) + .path("matches").required(0).path("path").path("value")) + .hasTextEqualTo("/"); + } + + @Test + void gateway_default_path_uses_context_path_for_crowd() throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(Product.crowd, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", "crowd.example.com")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + assertThat(httpRoute.getNode("spec", "rules").required(0) + .path("matches").required(0).path("path").path("value")) + .hasTextEqualTo("/"); + } + + @ParameterizedTest + @EnumSource(value = Product.class, names = {"bamboo_agent"}, mode = EnumSource.Mode.EXCLUDE) + void gateway_default_path_type_is_prefix(Product product) throws Exception { + final var resources = helm.captureKubeResourcesFromHelmChart(product, Map.of( + "gateway.create", "true", + "gateway.parentRefs[0].name", "my-gateway", + "gateway.hostnames[0]", product + ".example.com")); + + final var httpRoute = resources.get(Kind.HTTPRoute); + assertThat(httpRoute.getNode("spec", "rules").required(0) + .path("matches").required(0).path("path").path("type")) + .hasTextEqualTo("PathPrefix"); + } + + private static void assertThrowsAssertionWithMessage(String expectedErrorMessage, Executable fn) { + assertThatString( + assertThrows(AssertionError.class, fn).getMessage() + ).contains(expectedErrorMessage); + } + private static AbstractStringAssert assertThatString(String text) { + return org.assertj.core.api.Assertions.assertThat(text); + } +} diff --git a/src/test/java/test/IngressTest.java b/src/test/java/test/IngressTest.java index 9585de4f4..93f586a5c 100644 --- a/src/test/java/test/IngressTest.java +++ b/src/test/java/test/IngressTest.java @@ -334,7 +334,7 @@ void bamboo_atl_base_path_when_no_ingress_path(Product product) throws Exception "ingress.https", "true")); resources.getStatefulSet(product.getHelmReleaseName()).getContainer().getEnv() - .assertHasValue("ATL_BASE_URL", "https://myhost.mydomain"); + .assertHasValue("ATL_BASE_URL", "https://myhost.mydomain/"); } @ParameterizedTest diff --git a/src/test/java/test/model/Kind.java b/src/test/java/test/model/Kind.java index 1adbafe91..9fa46f788 100644 --- a/src/test/java/test/model/Kind.java +++ b/src/test/java/test/model/Kind.java @@ -4,5 +4,5 @@ * The different types of Kubernetes resource we use. */ public enum Kind { - StatefulSet, Deployment, ServiceAccount, ConfigMap, Secret, Service, Pod, Job, ClusterRole, Role, ClusterRoleBinding, RoleBinding, PersistentVolume, PersistentVolumeClaim, Ingress, ServiceMonitor, PodDisruptionBudget + StatefulSet, Deployment, ServiceAccount, ConfigMap, Secret, Service, Pod, Job, ClusterRole, Role, ClusterRoleBinding, RoleBinding, PersistentVolume, PersistentVolumeClaim, Ingress, HTTPRoute, ServiceMonitor, PodDisruptionBudget } diff --git a/src/test/resources/expected_helm_output/bamboo-agent/output.yaml b/src/test/resources/expected_helm_output/bamboo-agent/output.yaml index 30eea7dd5..d4671320b 100644 --- a/src/test/resources/expected_helm_output/bamboo-agent/output.yaml +++ b/src/test/resources/expected_helm_output/bamboo-agent/output.yaml @@ -46,7 +46,6 @@ spec: template: metadata: annotations: - checksum/config-jvm: dc046bb9cc091982418b9dbc914846069c346905db0b7ca4545a961dfa0e7f7b labels: app.kubernetes.io/name: bamboo-agent app.kubernetes.io/instance: unittest-bamboo-agent diff --git a/src/test/resources/expected_helm_output/bamboo/output.yaml b/src/test/resources/expected_helm_output/bamboo/output.yaml index ef7c92a34..2114dbf9a 100644 --- a/src/test/resources/expected_helm_output/bamboo/output.yaml +++ b/src/test/resources/expected_helm_output/bamboo/output.yaml @@ -5,7 +5,7 @@ kind: ServiceAccount metadata: name: unittest-bamboo labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -17,7 +17,7 @@ kind: ConfigMap metadata: name: unittest-bamboo-jvm-config labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -34,7 +34,7 @@ kind: ConfigMap metadata: name: unittest-bamboo-jmx-config labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -62,7 +62,7 @@ kind: ConfigMap metadata: name: unittest-bamboo-helm-values labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -251,6 +251,20 @@ data: imageRepo: fluent/fluentd-kubernetes-daemonset imageTag: v1.11.5-debian-elasticsearch7-1.2 resources: {} + gateway: + additionalRules: [] + annotations: {} + create: false + filters: [] + hostnames: [] + https: true + labels: {} + parentRefs: [] + path: null + pathType: PathPrefix + timeouts: + backendRequest: 60s + request: 60s hostNamespaces: {} image: pullPolicy: IfNotPresent @@ -371,7 +385,7 @@ kind: Service metadata: name: unittest-bamboo-jms labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -394,7 +408,7 @@ kind: Service metadata: name: unittest-bamboo-jmx labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -417,7 +431,7 @@ kind: Service metadata: name: unittest-bamboo labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -441,7 +455,7 @@ kind: StatefulSet metadata: name: unittest-bamboo labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -458,9 +472,8 @@ spec: template: metadata: annotations: - checksum/config-jvm: 6c2426b81340577644941f19819ed153ebb37ae2c3836fa6638a9e62c00085f9 labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -627,7 +640,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" @@ -671,7 +684,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: bamboo-2.0.9 + helm.sh/chart: bamboo-2.0.10 app.kubernetes.io/name: bamboo app.kubernetes.io/instance: unittest-bamboo app.kubernetes.io/version: "12.1.3" diff --git a/src/test/resources/expected_helm_output/bitbucket/output.yaml b/src/test/resources/expected_helm_output/bitbucket/output.yaml index bbb3c3a27..9449496aa 100644 --- a/src/test/resources/expected_helm_output/bitbucket/output.yaml +++ b/src/test/resources/expected_helm_output/bitbucket/output.yaml @@ -5,7 +5,7 @@ kind: ServiceAccount metadata: name: unittest-bitbucket labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -17,7 +17,7 @@ kind: ConfigMap metadata: name: unittest-bitbucket-jvm-config-mesh labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -35,7 +35,7 @@ kind: ConfigMap metadata: name: unittest-bitbucket-jvm-config labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -54,7 +54,7 @@ kind: ConfigMap metadata: name: unittest-bitbucket-jmx-config labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -76,7 +76,7 @@ kind: ConfigMap metadata: name: unittest-bitbucket-helm-values labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -292,6 +292,20 @@ data: imageRepo: fluent/fluentd-kubernetes-daemonset imageTag: v1.11.5-debian-elasticsearch7-1.2 resources: {} + gateway: + additionalRules: [] + annotations: {} + create: false + filters: [] + hostnames: [] + https: true + labels: {} + parentRefs: [] + path: null + pathType: PathPrefix + timeouts: + backendRequest: 60s + request: 60s hostNamespaces: {} image: pullPolicy: IfNotPresent @@ -450,7 +464,7 @@ kind: Service metadata: name: unittest-bitbucket-jmx labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -476,7 +490,7 @@ kind: Service metadata: name: unittest-bitbucket-mesh-0 labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -502,7 +516,7 @@ kind: Service metadata: name: unittest-bitbucket-mesh-1 labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -528,7 +542,7 @@ kind: Service metadata: name: unittest-bitbucket-mesh-2 labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -554,7 +568,7 @@ kind: Service metadata: name: unittest-bitbucket labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -586,7 +600,7 @@ kind: StatefulSet metadata: name: unittest-bitbucket-mesh labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -604,7 +618,6 @@ spec: template: metadata: annotations: - checksum/config-jvm: 5fd297317f6a4912733590b9173f61562d02dc369c01683e6f9def9b38955124 labels: app.kubernetes.io/name: bitbucket-mesh app.kubernetes.io/instance: unittest-bitbucket @@ -707,7 +720,7 @@ kind: StatefulSet metadata: name: unittest-bitbucket labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -725,9 +738,8 @@ spec: template: metadata: annotations: - checksum/config-jvm: 213b5c2db2d1985012fc80d73db49125b74168c3ef2c96040ff532e546526c90 labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -851,7 +863,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" @@ -883,7 +895,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: bitbucket-2.0.9 + helm.sh/chart: bitbucket-2.0.10 app.kubernetes.io/name: bitbucket app.kubernetes.io/instance: unittest-bitbucket app.kubernetes.io/version: "10.2.1" diff --git a/src/test/resources/expected_helm_output/confluence/output.yaml b/src/test/resources/expected_helm_output/confluence/output.yaml index ef6897926..1b19f7d78 100644 --- a/src/test/resources/expected_helm_output/confluence/output.yaml +++ b/src/test/resources/expected_helm_output/confluence/output.yaml @@ -5,7 +5,7 @@ kind: ServiceAccount metadata: name: unittest-confluence labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -17,7 +17,7 @@ kind: ConfigMap metadata: name: unittest-confluence-jvm-config labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -40,7 +40,7 @@ kind: ConfigMap metadata: name: unittest-confluence-jmx-config labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -62,7 +62,7 @@ kind: ConfigMap metadata: name: unittest-confluence-helm-values labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -243,6 +243,20 @@ data: imageRepo: fluent/fluentd-kubernetes-daemonset imageTag: v1.11.5-debian-elasticsearch7-1.2 resources: {} + gateway: + additionalRules: [] + annotations: {} + create: false + filters: [] + hostnames: [] + https: true + labels: {} + parentRefs: [] + path: null + pathType: PathPrefix + timeouts: + backendRequest: 60s + request: 60s hostNamespaces: {} image: pullPolicy: IfNotPresent @@ -490,7 +504,7 @@ kind: Service metadata: name: unittest-confluence-jmx labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -513,7 +527,7 @@ kind: Service metadata: name: unittest-confluence-synchrony labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence-synchrony app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -540,7 +554,7 @@ kind: Service metadata: name: unittest-confluence labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -568,7 +582,7 @@ kind: StatefulSet metadata: name: unittest-confluence-synchrony labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence-synchrony app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -585,9 +599,8 @@ spec: template: metadata: annotations: - checksum/config-jvm: 5c7e4f3183d49bd4e8c82a29b06246e551e4120042495652f1f9b27a0599a882 labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence-synchrony app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -653,7 +666,7 @@ kind: StatefulSet metadata: name: unittest-confluence labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -670,9 +683,8 @@ spec: template: metadata: annotations: - checksum/config-jvm: 5fb6580d3d86adefce4a3cdf588b618930426168fab196e4059dffd36c1e17d2 labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -807,7 +819,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" @@ -838,7 +850,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: confluence-2.0.9 + helm.sh/chart: confluence-2.0.10 app.kubernetes.io/name: confluence app.kubernetes.io/instance: unittest-confluence app.kubernetes.io/version: "10.2.7" diff --git a/src/test/resources/expected_helm_output/crowd/output.yaml b/src/test/resources/expected_helm_output/crowd/output.yaml index b6327a5e9..74c58c5f2 100644 --- a/src/test/resources/expected_helm_output/crowd/output.yaml +++ b/src/test/resources/expected_helm_output/crowd/output.yaml @@ -5,7 +5,7 @@ kind: ServiceAccount metadata: name: unittest-crowd labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" @@ -17,7 +17,7 @@ kind: ConfigMap metadata: name: unittest-crowd-jvm-config labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" @@ -36,7 +36,7 @@ kind: ConfigMap metadata: name: unittest-crowd-jmx-config labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" @@ -64,7 +64,7 @@ kind: ConfigMap metadata: name: unittest-crowd-helm-values labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" @@ -197,6 +197,20 @@ data: imageRepo: fluent/fluentd-kubernetes-daemonset imageTag: v1.11.5-debian-elasticsearch7-1.2 resources: {} + gateway: + additionalRules: [] + annotations: {} + create: false + filters: [] + hostnames: [] + https: true + labels: {} + parentRefs: [] + path: / + pathType: PathPrefix + timeouts: + backendRequest: 60s + request: 60s hostNamespaces: {} image: pullPolicy: IfNotPresent @@ -317,7 +331,7 @@ kind: Service metadata: name: unittest-crowd-jmx labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" @@ -340,7 +354,7 @@ kind: Service metadata: name: unittest-crowd labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" @@ -364,7 +378,7 @@ kind: StatefulSet metadata: name: unittest-crowd labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" @@ -381,9 +395,8 @@ spec: template: metadata: annotations: - checksum/config-jvm: 13f17fbad9620fd17fc56093ce0afa4b2320724668cbe4cc94b5a219379d3e62 labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" @@ -517,7 +530,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" @@ -548,7 +561,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: crowd-2.0.9 + helm.sh/chart: crowd-2.0.10 app.kubernetes.io/name: crowd app.kubernetes.io/instance: unittest-crowd app.kubernetes.io/version: "7.1.5" diff --git a/src/test/resources/expected_helm_output/jira/output.yaml b/src/test/resources/expected_helm_output/jira/output.yaml index 133a90b43..cbd240b64 100644 --- a/src/test/resources/expected_helm_output/jira/output.yaml +++ b/src/test/resources/expected_helm_output/jira/output.yaml @@ -5,7 +5,7 @@ kind: ServiceAccount metadata: name: unittest-jira labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" @@ -17,7 +17,7 @@ kind: ConfigMap metadata: name: unittest-jira-jvm-config labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" @@ -36,7 +36,7 @@ kind: ConfigMap metadata: name: unittest-jira-jmx-config labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" @@ -62,7 +62,7 @@ kind: ConfigMap metadata: name: unittest-jira-helm-values labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" @@ -109,6 +109,20 @@ data: imageRepo: fluent/fluentd-kubernetes-daemonset imageTag: v1.11.5-debian-elasticsearch7-1.2 resources: {} + gateway: + additionalRules: [] + annotations: {} + create: false + filters: [] + hostnames: [] + https: true + labels: {} + parentRefs: [] + path: null + pathType: PathPrefix + timeouts: + backendRequest: 60s + request: 60s hostNamespaces: {} image: pullPolicy: IfNotPresent @@ -361,7 +375,7 @@ kind: Service metadata: name: unittest-jira-jmx labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" @@ -384,7 +398,7 @@ kind: Service metadata: name: unittest-jira labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" @@ -408,7 +422,7 @@ kind: StatefulSet metadata: name: unittest-jira labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" @@ -425,9 +439,8 @@ spec: template: metadata: annotations: - checksum/config-jvm: 5d632191c42871616de74827f73c9c72e383ec5c85f91e28f69c079802792851 labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" @@ -562,7 +575,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" @@ -594,7 +607,7 @@ metadata: "helm.sh/hook": test "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" labels: - helm.sh/chart: jira-2.0.9 + helm.sh/chart: jira-2.0.10 app.kubernetes.io/name: jira app.kubernetes.io/instance: unittest-jira app.kubernetes.io/version: "11.3.3" diff --git a/src/test/scripts/kind/configure_kind.sh b/src/test/scripts/kind/configure_kind.sh index 6ebd75971..e986b22a6 100755 --- a/src/test/scripts/kind/configure_kind.sh +++ b/src/test/scripts/kind/configure_kind.sh @@ -6,19 +6,105 @@ kubectl cluster-info echo "[INFO]: current-context:" $(kubectl config current-context) echo "[INFO]: environment-kubeconfig:" "${KUBECONFIG}" -kubectl create namespace atlassian +kubectl create namespace atlassian --dry-run=client -o yaml | kubectl apply -f - # even though there's a kind command to load a local image directly to KinD container runtime # let's deploy an insecure registry in case we need it for any further tests echo "[INFO]: Deploy ephemeral container registry" kubectl apply -f src/test/config/kind/registry.yaml -echo "[INFO]: Deploy Nginx ingress controller" -kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml -kubectl wait --for=condition=ready pod \ - --selector=app.kubernetes.io/component=controller \ - --timeout=300s \ - -n ingress-nginx +# Install Gateway API CRDs and Controller +if [ -z "${SKIP_GATEWAY_API}" ]; then + echo "[INFO]: Installing Gateway API CRDs" + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml + kubectl apply --server-side=true -f https://raw.githubusercontent.com/envoyproxy/gateway/v1.2.5/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml + + echo "[INFO]: Waiting for Gateway API CRDs to be established" + kubectl wait --for condition=established --timeout=60s crd/gateways.gateway.networking.k8s.io + kubectl wait --for condition=established --timeout=60s crd/httproutes.gateway.networking.k8s.io + kubectl wait --for condition=established --timeout=60s crd/gatewayclasses.gateway.networking.k8s.io + kubectl wait --for condition=established --timeout=60s crd/envoyproxies.gateway.envoyproxy.io + + echo "[INFO]: Installing Envoy Gateway" + # Envoy Gateway uses OCI registry, not a traditional Helm repo + helm install eg oci://docker.io/envoyproxy/gateway-helm \ + --version v1.2.5 \ + --create-namespace \ + --namespace envoy-gateway-system \ + --set deployment.envoyGateway.resources.requests.cpu=50m \ + --set deployment.envoyGateway.resources.requests.memory=100Mi \ + --skip-crds \ + --timeout=300s \ + --wait + + echo "[INFO]: Waiting for Envoy Gateway to be ready" + kubectl wait --for=condition=available deployment/envoy-gateway \ + --namespace envoy-gateway-system \ + --timeout=300s + + # EnvoyProxy CR tells the controller to create the data-plane proxy Service + # as NodePort with nodePort 30080 — matching the KinD extraPortMappings. + # This must be applied BEFORE the GatewayClass so the controller picks it up. + echo "[INFO]: Applying EnvoyProxy configuration (NodePort 30080)" + kubectl apply -f src/test/config/kind/envoy-proxy.yaml + + # GatewayClass references the EnvoyProxy CR via parametersRef so every + # Gateway using this class gets the NodePort configuration automatically. + echo "[INFO]: Creating GatewayClass 'eg' with parametersRef" + cat << EOF | kubectl apply -f - +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: kind-proxy-config + namespace: envoy-gateway-system +EOF + + echo "[INFO]: Waiting for GatewayClass 'eg' to be accepted" + kubectl wait --for=condition=Accepted gatewayclass/eg --timeout=180s || { + echo "[ERROR]: GatewayClass not accepted in time" + kubectl get gatewayclass/eg -o yaml + exit 1 + } + + echo "[INFO]: Creating test Gateway resource in atlassian namespace" + kubectl apply -f src/test/config/kind/gateway.yaml + + echo "[INFO]: Waiting for Gateway to be reconciled" + # In KinD there is no real LoadBalancer implementation, so the Gateway may never become + # fully Programmed (AddressNotAssigned). Instead, wait for: + # 1) Gateway Accepted=True (control-plane picked it up) + # 2) The Envoy proxy Deployment for this Gateway to become Available (data-plane ready) + kubectl wait --for=condition=Accepted gateway/atlassian-gateway -n atlassian --timeout=300s || { + echo "[ERROR]: Gateway not accepted in time" + kubectl describe gateway/atlassian-gateway -n atlassian + exit 1 + } + + echo "[INFO]: Waiting for Envoy proxy deployment for the Gateway" + kubectl wait --for=condition=Available deployment \ + -n envoy-gateway-system \ + -l gateway.envoyproxy.io/owning-gateway-name=atlassian-gateway \ + --timeout=300s || { + echo "[ERROR]: Envoy proxy deployment not ready in time" + kubectl get deployments -n envoy-gateway-system -o wide + kubectl get pods -n envoy-gateway-system -o wide + exit 1 + } + + # Add /etc/hosts entry so dc-app.test resolves to localhost on the runner. + echo "[INFO]: Adding dc-app.test to /etc/hosts" + echo "127.0.0.1 dc-app.test" | sudo tee -a /etc/hosts + + echo "[INFO]: Gateway API installation complete" +else + echo "[INFO]: Skipping Gateway API installation (SKIP_GATEWAY_API is set)" +fi # this is for local runs, because existing nfs server images does not run on arm64 platforms # instead, we create a hostPath RWX volume and override the default common settings diff --git a/src/test/scripts/kind/deploy_app.sh b/src/test/scripts/kind/deploy_app.sh index 4de15d4a2..8e825c993 100755 --- a/src/test/scripts/kind/deploy_app.sh +++ b/src/test/scripts/kind/deploy_app.sh @@ -1,5 +1,32 @@ #!/usr/bin/env bash +# Poll until a command succeeds or timeout is reached. +# Usage: wait_for "description" timeout_seconds interval_seconds command [args...] +wait_for() { + local desc="$1"; shift + local timeout="$1"; shift + local interval="$1"; shift + local elapsed=0 + + while ! "$@" >/dev/null 2>&1; do + if [ $elapsed -ge $timeout ]; then + echo "[ERROR]: Timed out after ${elapsed}s waiting for: ${desc}" + return 1 + fi + echo "[INFO]: Waiting for ${desc}... (${elapsed}/${timeout}s)" + sleep $interval + elapsed=$((elapsed + interval)) + done + echo "[INFO]: ${desc} — ready" +} + +# Check if a URL returns HTTP 200. +# Usage: check_http_200 url [extra-curl-args...] +check_http_200() { + local url="$1"; shift + test "$(curl -s -o /dev/null -w '%{http_code}' "$@" "$url")" = "200" +} + # Deploy CloudNativePG operator and PostgreSQL cluster deploy_postgres() { echo "[INFO]: Installing CloudNativePG operator" @@ -47,36 +74,29 @@ deploy_postgres() { # Wait for operator to be ready echo "[INFO]: Waiting for CloudNativePG operator to be ready" - for i in {1..60}; do - if kubectl get crd clusters.postgresql.cnpg.io >/dev/null 2>&1; then - echo "[INFO]: CloudNativePG CRDs are available" - break - fi - echo "[INFO]: Waiting for CloudNativePG CRDs to be available... ($i/60)" - sleep 5 - done - + wait_for "CloudNativePG CRDs" 300 5 kubectl get crd clusters.postgresql.cnpg.io || { + echo "[ERROR]: CloudNativePG CRDs not available" + exit 1 + } # Wait for operator deployment to exist echo "[INFO]: Waiting for operator deployment to be created..." - for i in {1..30}; do - if kubectl get deployment -n cnpg-system -l app.kubernetes.io/name=cloudnative-pg >/dev/null 2>&1; then - echo "[INFO]: Operator deployment found" - # MicroShift/OpenShift: grant anyuid SCC to operator SA to satisfy UID range constraints - if kubectl api-resources | grep -q "securitycontextconstraints"; then - echo "[INFO]: Detected SCC support; granting 'anyuid' SCC to operator service account" - SA_NAME="cnpg-operator-cloudnative-pg" - # Try with oc if available, otherwise patch SCC directly - if command -v oc >/dev/null 2>&1; then - oc adm policy add-scc-to-user anyuid -z "$SA_NAME" -n cnpg-system || true - else - kubectl patch scc anyuid --type=json -p='[{"op":"add","path":"/users/-","value":"system:serviceaccount:cnpg-system:'"$SA_NAME"'"}]' || true - fi - fi - break + wait_for "CloudNativePG operator deployment" 60 2 \ + kubectl get deployment -n cnpg-system -l app.kubernetes.io/name=cloudnative-pg || { + echo "[WARNING]: CloudNativePG operator deployment not found" + # this will be checked again below - keeping old behavior to minimize risk of this change + } + + # MicroShift/OpenShift: grant anyuid SCC to operator SA to satisfy UID range constraints + if kubectl api-resources | grep -q "securitycontextconstraints"; then + echo "[INFO]: Detected SCC support; granting 'anyuid' SCC to operator service account" + SA_NAME="cnpg-operator-cloudnative-pg" + # Try with oc if available, otherwise patch SCC directly + if command -v oc >/dev/null 2>&1; then + oc adm policy add-scc-to-user anyuid -z "$SA_NAME" -n cnpg-system || true + else + kubectl patch scc anyuid --type=json -p='[{"op":"add","path":"/users/-","value":"system:serviceaccount:cnpg-system:'"$SA_NAME"'"}]' || true fi - echo "[INFO]: Waiting for operator deployment... ($i/30)" - sleep 2 - done + fi # Verify operator is actually running echo "[DEBUG]: CloudNativePG operator deployments:" @@ -339,12 +359,17 @@ deploy_app() { fi } -# Resolve the ingress/gateway hostname based on the deployment environment. +# Resolve the routing hostname based on the deployment environment. +# +# - KinD: Envoy Gateway proxy is exposed via NodePort(30080) -> hostPort(80) +# and the workflow adds /etc/hosts so dc-app.test resolves locally. +# - OpenShift/MicroShift: Envoy proxy is exposed via an OpenShift Route on the +# CRC apps domain. get_routing_hostname() { if [ -n "${OPENSHIFT_VALUES}" ]; then echo "atlassian.apps.crc.testing" else - echo "localhost" + echo "dc-app.test" fi } @@ -356,17 +381,17 @@ get_routing_hostname() { # during setup triggers the wizard to advance to the next step. wait_for_bamboo_setup() { echo "[INFO]: Waiting for Bamboo server unattended setup to complete..." + SETUP_HOSTNAME=$(get_routing_hostname) SETUP_TIMEOUT=300 SETUP_ELAPSED=0 while [ ${SETUP_ELAPSED} -lt ${SETUP_TIMEOUT} ]; do - RESPONSE=$(curl -s -o /dev/null -w "%{http_code}|%{redirect_url}" --max-redirs 0 http://${SETUP_HOSTNAME}/ 2>/dev/null || echo "000|") + RESPONSE=$(curl -s -o /dev/null -w "%{http_code}|%{redirect_url}" --max-redirs 0 "http://${SETUP_HOSTNAME}/" 2>/dev/null || echo "000|") HTTP_CODE=$(echo "$RESPONSE" | cut -d'|' -f1) REDIRECT_URL=$(echo "$RESPONSE" | cut -d'|' -f2) if echo "${REDIRECT_URL}" | grep -q "bootstrap\|setup"; then - # Server is in setup mode — trigger setup advancement by following the redirect chain - curl -s -o /dev/null -L --max-redirs 10 http://${SETUP_HOSTNAME}/ 2>/dev/null || true + curl -s -o /dev/null -L --max-redirs 10 "http://${SETUP_HOSTNAME}/" 2>/dev/null || true echo "[INFO]: Bamboo setup in progress (HTTP ${HTTP_CODE} → ${REDIRECT_URL}). Triggering setup... (${SETUP_ELAPSED}s/${SETUP_TIMEOUT}s)" elif [ "${HTTP_CODE}" = "302" ] && echo "${REDIRECT_URL}" | grep -q "userlogin"; then echo "[INFO]: Bamboo server setup complete (redirecting to login page)" @@ -382,37 +407,38 @@ wait_for_bamboo_setup() { done echo "[WARNING]: Bamboo setup did not complete within ${SETUP_TIMEOUT}s. Proceeding with agent deployment anyway." - echo "[DEBUG]: Full response from GET / with redirects:" - curl -v -s -L --max-redirs 10 http://${SETUP_HOSTNAME}/ 2>&1 || true + curl -v -s -L --max-redirs 10 "http://${SETUP_HOSTNAME}/" 2>&1 || true } -verify_ingress() { +verify_gateway_ingress() { STATUS_ENDPOINT_PATH="status" if [ ${DC_APP} == "bamboo" ]; then STATUS_ENDPOINT_PATH="rest/api/latest/status" elif [ ${DC_APP} == "crowd" ]; then STATUS_ENDPOINT_PATH="crowd/status" fi - echo "[INFO]: Checking ${DC_APP} status" - # give ingress controller a few seconds before polling - sleep 5 + + echo "[INFO]: Checking ${DC_APP} status via Gateway API" + + wait_for "HTTPRoute ${DC_APP}" 60 2 kubectl get httproute/${DC_APP} -n atlassian || { + echo "[ERROR]: HTTPRoute ${DC_APP} not found in atlassian namespace" + kubectl get httproute -n atlassian || true + exit 1 + } + HOSTNAME=$(get_routing_hostname) - for i in {1..10}; do - STATUS=$(curl -s -o /dev/null -w '%{http_code}' http://${HOSTNAME}/${STATUS_ENDPOINT_PATH}) - if [ $STATUS -ne 200 ]; then - echo "[ERROR]: Status code is not 200. Waiting 10 seconds" - sleep 10 - else - echo "[INFO]: Received status ${STATUS}" - curl -s http://${HOSTNAME}/${STATUS_ENDPOINT_PATH} - echo -e "\n" - break - fi - done - if [ $STATUS -ne 200 ]; then - curl -v http://${HOSTNAME}/${STATUS_ENDPOINT_PATH} - exit 1 - fi + STATUS_URL="http://${HOSTNAME}/${STATUS_ENDPOINT_PATH}" + + wait_for "${DC_APP} HTTP 200 via Gateway" 120 10 \ + check_http_200 "${STATUS_URL}" || { + echo "[ERROR]: ${DC_APP} did not return HTTP 200 via Gateway" + curl -v "${STATUS_URL}" + exit 1 + } + + echo "[INFO]: ${DC_APP} responded successfully via Gateway" + curl -s "${STATUS_URL}" + echo -e "\n" } verify_metrics() { @@ -479,6 +505,73 @@ verify_openshift_analytics() { fi } +verify_gateway() { + echo "[INFO]: Verifying HTTPRoute resource for ${DC_APP}" + if ! kubectl get httproute/${DC_APP} -n atlassian >/dev/null 2>&1; then + echo "[ERROR]: HTTPRoute ${DC_APP} not found in atlassian namespace" + kubectl get httproute -n atlassian || true + exit 1 + fi + + echo "[INFO]: Checking HTTPRoute status" + + # Wait on Gateway API parent conditions (Envoy Gateway reports conditions under + # `.status.parents[*].conditions`). Use a JSONPath filter by type. + # Note: Don't escape the double-quotes inside the single-quoted JSONPath. + + kubectl wait \ + --for=jsonpath='{.status.parents[0].conditions[?(@.type=="Accepted")].status}'=True \ + httproute/${DC_APP} -n atlassian --timeout=180s || { + echo "[ERROR]: HTTPRoute not accepted" + echo "[DEBUG]: HTTPRoute status (YAML)" + kubectl get httproute/${DC_APP} -n atlassian -o yaml | sed -n '/^status:/,$p' || true + echo "[DEBUG]: HTTPRoute status.parents (JSON)" + kubectl get httproute/${DC_APP} -n atlassian -o json | jq '.status.parents' || true + echo "[DEBUG]: Gateway status (YAML)" + kubectl get gateway/atlassian-gateway -n atlassian -o yaml | sed -n '/^status:/,$p' || true + echo "[DEBUG]: Envoy Gateway deployments/pods" + kubectl get deployments -n envoy-gateway-system -o wide || true + kubectl get pods -n envoy-gateway-system -o wide || true + kubectl describe httproute/${DC_APP} -n atlassian + exit 1 + } + + kubectl wait \ + --for=jsonpath='{.status.parents[0].conditions[?(@.type=="ResolvedRefs")].status}'=True \ + httproute/${DC_APP} -n atlassian --timeout=180s || { + echo "[ERROR]: HTTPRoute ResolvedRefs condition not met" + echo "[DEBUG]: HTTPRoute status.parents (JSON)" + kubectl get httproute/${DC_APP} -n atlassian -o json | jq '.status.parents' || true + kubectl describe httproute/${DC_APP} -n atlassian + exit 1 + } + + echo "[INFO]: HTTPRoute is Accepted and ResolvedRefs verified" + + echo "[INFO]: Verifying Gateway attachment" + GATEWAY_NAME=$(kubectl get httproute/${DC_APP} -n atlassian -o jsonpath='{.spec.parentRefs[0].name}') + echo "[INFO]: HTTPRoute attached to Gateway: ${GATEWAY_NAME}" + + if [ -z "${GATEWAY_NAME}" ]; then + echo "[ERROR]: No Gateway referenced in HTTPRoute" + exit 1 + fi + + echo "[INFO]: Checking Gateway status" + kubectl get gateway/${GATEWAY_NAME} -n atlassian -o yaml + + # Verify hostnames are configured + HOSTNAMES=$(kubectl get httproute/${DC_APP} -n atlassian -o jsonpath='{.spec.hostnames[*]}') + echo "[INFO]: HTTPRoute hostnames: ${HOSTNAMES}" + + if [ -z "${HOSTNAMES}" ]; then + echo "[ERROR]: No hostnames configured on HTTPRoute" + exit 1 + fi + + echo "[INFO]: Gateway API verification complete for ${DC_APP}" +} + # create 2 NodePort services to expose each DC pod, required for functional tests # where communication between nodes and cache replication is tested create_backdoor_services() {