diff --git a/endpoint-exposer/entrypoint/entrypoint b/endpoint-exposer/entrypoint/entrypoint index 81c3f2b..45d50d7 100755 --- a/endpoint-exposer/entrypoint/entrypoint +++ b/endpoint-exposer/entrypoint/entrypoint @@ -46,6 +46,15 @@ for arg in "$@"; do esac done +# Fall back to deriving SERVICE_PATH from the script's own location when the +# caller doesn't pass --service-path. WORKING_DIRECTORY points at the +# `entrypoint/` directory inside the service; the parent is the service root +# (e.g. `/root/.np/nullplatform/services/endpoint-exposer`). Without this +# fallback, every absolute path below becomes `/workflows/...` instead of +# `/workflows/...`, and the np CLI fails with +# "failed to read workflow file: open /workflows//.yaml". +SERVICE_PATH="${SERVICE_PATH:-$(cd "$WORKING_DIRECTORY/.." && pwd)}" + OVERRIDES_PATH="${OVERRIDES_PATH:-$SERVICE_PATH/overrides}" export SERVICE_PATH diff --git a/endpoint-exposer/entrypoint/service b/endpoint-exposer/entrypoint/service index 877b2d2..cdf2292 100755 --- a/endpoint-exposer/entrypoint/service +++ b/endpoint-exposer/entrypoint/service @@ -10,7 +10,7 @@ case "$SERVICE_ACTION_TYPE" in ;; esac -INGRESS_TYPE="${INGRESS_TYPE:-alb}" +INGRESS_TYPE="${INGRESS_TYPE:-istio}" echo "INGRESS_TYPE is set to '$INGRESS_TYPE'" echo "OVERRIDES_PATH is set to '$OVERRIDES_PATH'" diff --git a/endpoint-exposer/install/installation.md b/endpoint-exposer/install/installation.md index 11cd9d9..eb265c4 100644 --- a/endpoint-exposer/install/installation.md +++ b/endpoint-exposer/install/installation.md @@ -22,7 +22,7 @@ git clone https://github.com/nullplatform/services /root/.np/nullplatform/servic git clone https://github.com/nullplatform/tofu-modules /root/.np/nullplatform/tofu-modules ``` -> The `repo_path` variable defaults to `/root/.np/nullplatform/services/endpoint-exposer`. Adjust if you clone elsewhere. +> The agent cmdline resolves to `////entrypoint/entrypoint`, which defaults to `/root/.np/nullplatform/services/endpoint-exposer/entrypoint/entrypoint`. Adjust the variables if you clone elsewhere. ### 2. Configure variables @@ -35,14 +35,18 @@ Edit `terraform.tfvars` with your values: | Variable | Required | Description | |---|---|---| -| `nrn` | ✅ | Nullplatform Resource Name (`organization:account`) | -| `np_api_key` | ✅ | Nullplatform API key | +| `nrn` | ✅ | Nullplatform Resource Name (`organization=:account=`) | +| `np_api_key` | ✅ | Nullplatform API key used by the agent | | `tags_selectors` | ✅ | Tags to select the agent (e.g. `{ environment = "production" }`) | -| `github_token` | ✅ | GitHub token with `contents: read` on `nullplatform/services` | -| `git_branch` | — | Branch to fetch specs from (default: `main`) | -| `repo_path` | — | Path where endpoint-exposer is located on the agent | -| `overrides_enabled` | — | Set `true` to enable config overrides | -| `overrides_repo_path` | — | Full path to the overrides directory on the agent | +| `github_token` | — | Only required if `repository_org`/`repository_name` point at a private fork. Not needed for the public `nullplatform/services` repo. | +| `repository_org` | — | Org that owns the spec repository (default: `nullplatform`) | +| `repository_name` | — | Spec repository name (default: `services`) | +| `repository_branch` | — | Branch to fetch specs from (default: `main`) | +| `spec_path` | — | In-repo path to `specs/service-spec.json.tpl` (default: `endpoint-exposer/install`) | +| `agent_service_path` | — | In-repo path where the agent runtime lives (default: `endpoint-exposer`) | +| `service_name` | — | Display name in nullplatform (default: `Endpoint Exposer`) | +| `overrides_enabled` | — | Set `true` to pass `--overrides-path` to the agent | +| `overrides_repo_path` | — | Absolute path to the overrides directory on the agent (required when `overrides_enabled = true`) | ### 3. Initialize OpenTofu @@ -59,25 +63,39 @@ tofu plan tofu apply ``` +## Domains + +The `publicDomain` / `privateDomain` fields in the service spec are free-text strings. Developers type the concrete FQDN at scope-creation time (via the nullplatform UI, CLI, or API). The base domain must resolve to the appropriate Istio gateway in the target cluster (public or private). + +## Spec fields governed by Terraform + +A few top-level fields in `install/specs/service-spec.json.tpl` are **overridden by the `service_definition` module at apply time**, so their value in the `.tpl` is ignored: + +| Spec field | Source at apply time | +|---|---| +| `name` | `var.service_name` | +| `visible_to` | `concat([var.nrn], var.extra_visibile_to_nrns)` | + +Do not add `{{ env.Getenv ... }}` template expressions to other fields expecting runtime substitution — there is no template engine in the pipeline (the module reads the spec with `data "http"` + `jsondecode()`). Any template string in a non-overridden field will reach the nullplatform API as a literal. + ## Overrides -If the account requires local configuration overrides (e.g. from a networking repo), enable the override flag so the agent appends `--overrides-path` to its command: +If the account requires local configuration overrides (e.g. from a networking repo), enable the override flag so the agent receives `--overrides-path` as an argument: ```hcl overrides_enabled = true overrides_repo_path = "/root/.np/nullplatform/scopes-networking/endpoint-exposer" ``` -This results in the agent running: +The agent cmdline becomes: ``` -/root/.np/nullplatform/services/endpoint-exposer/entrypoint \ - --service-path=/root/.np/nullplatform/services/endpoint-exposer \ +/root/.np/nullplatform/services/endpoint-exposer/entrypoint/entrypoint \ --overrides-path=/root/.np/nullplatform/scopes-networking/endpoint-exposer ``` ## Updating specs -To push spec changes after editing templates in `specs/`: +To push spec changes after editing templates in `install/specs/`: -1. Merge your branch to `main` (or update `git_branch` in tfvars) +1. Merge your branch to `main` (or update `repository_branch` in tfvars) 2. Run `tofu apply` — the module fetches templates from GitHub on each run diff --git a/endpoint-exposer/install/prerequisites.md b/endpoint-exposer/install/prerequisites.md index 3416347..4dd1b77 100644 --- a/endpoint-exposer/install/prerequisites.md +++ b/endpoint-exposer/install/prerequisites.md @@ -8,7 +8,7 @@ The agent pod must have the following repository cloned at the expected path: |---|---| | [nullplatform/services](https://github.com/nullplatform/services) | `/root/.np/nullplatform/services/endpoint-exposer` | -Override the default path via the `repo_path` variable in `terraform.tfvars`. +Override the default path via the `repository_org` / `repository_name` / `agent_service_path` variables in `terraform.tfvars`. ## Required tooling on the agent pod @@ -61,6 +61,6 @@ A `Gateway` resource must exist in the cluster for both public and private traff ## GitHub Token -A GitHub personal access token with `contents: read` permission on the `nullplatform/services` repository is required to fetch spec templates during `tofu apply`. +The `service_definition` module fetches spec templates from GitHub at `tofu apply` time via authenticated or anonymous HTTP. Since `nullplatform/services` is a **public** repository, **no token is required** for the default setup. -Set it in `terraform.tfvars` as `github_token`. +If you point `repository_org` / `repository_name` at a private fork, provide a GitHub personal access token with `contents: read` permission on that repo via the `github_token` variable in `terraform.tfvars`. diff --git a/endpoint-exposer/install/specs/service-spec.json.tpl b/endpoint-exposer/install/specs/service-spec.json.tpl index a5c9af7..1d6b921 100644 --- a/endpoint-exposer/install/specs/service-spec.json.tpl +++ b/endpoint-exposer/install/specs/service-spec.json.tpl @@ -1,9 +1,7 @@ { - "name": "{{ env.Getenv \"SERVICE_NAME\" | default \"Endpoint Exposer\" }}", + "name": "Endpoint Exposer", "type": "dependency", - "visible_to": [ - "{{ env.Getenv \"NRN\" }}" - ], + "visible_to": [], "dimensions": {}, "scopes": {}, "assignable_to": "any", @@ -145,8 +143,7 @@ "method", "path", "scope", - "visibility", - "environment" + "visibility" ], "properties": { "path": { @@ -210,22 +207,18 @@ } }, "publicDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Public Domain", + "description": "Base domain for routes with visibility=public. Tenant-specific — provide the FQDN that resolves to the public Istio gateway of the target cluster.", "editableOn": [ "create", "update" ] }, "privateDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Private Domain", + "description": "Base domain for routes with visibility=private. Tenant-specific — provide the FQDN that resolves to the private (internal) Istio gateway of the target cluster.", "editableOn": [ "create", "update" @@ -464,11 +457,9 @@ } }, "publicDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Public Domain", + "description": "Base domain for routes with visibility=public. Tenant-specific — provide the FQDN that resolves to the public Istio gateway of the target cluster.", "target": "publicDomain", "editableOn": [ "create", @@ -476,11 +467,9 @@ ] }, "privateDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Private Domain", + "description": "Base domain for routes with visibility=private. Tenant-specific — provide the FQDN that resolves to the private (internal) Istio gateway of the target cluster.", "target": "privateDomain", "editableOn": [ "create", @@ -628,8 +617,7 @@ "method", "path", "scope", - "visibility", - "environment" + "visibility" ], "properties": { "path": { @@ -695,11 +683,9 @@ } }, "publicDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Public Domain", + "description": "Base domain for routes with visibility=public. Tenant-specific — provide the FQDN that resolves to the public Istio gateway of the target cluster.", "target": "publicDomain", "editableOn": [ "create", @@ -707,11 +693,9 @@ ] }, "privateDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Private Domain", + "description": "Base domain for routes with visibility=private. Tenant-specific — provide the FQDN that resolves to the private (internal) Istio gateway of the target cluster.", "target": "privateDomain", "editableOn": [ "create", @@ -867,8 +851,7 @@ "method", "path", "scope", - "visibility", - "environment" + "visibility" ], "properties": { "path": { @@ -932,22 +915,18 @@ } }, "publicDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Public Domain", + "description": "Base domain for routes with visibility=public. Tenant-specific — provide the FQDN that resolves to the public Istio gateway of the target cluster.", "editableOn": [ "create", "update" ] }, "privateDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Private Domain", + "description": "Base domain for routes with visibility=private. Tenant-specific — provide the FQDN that resolves to the private (internal) Istio gateway of the target cluster.", "editableOn": [ "create", "update" @@ -1094,8 +1073,7 @@ "method", "path", "scope", - "visibility", - "environment" + "visibility" ], "properties": { "path": { @@ -1159,22 +1137,18 @@ } }, "publicDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Public Domain", + "description": "Base domain for routes with visibility=public. Tenant-specific — provide the FQDN that resolves to the public Istio gateway of the target cluster.", "editableOn": [ "create", "update" ] }, "privateDomain": { - "enum": [ - "hello.idp.poc.nullapps.io" - ], "type": "string", "title": "Private Domain", + "description": "Base domain for routes with visibility=private. Tenant-specific — provide the FQDN that resolves to the private (internal) Istio gateway of the target cluster.", "editableOn": [ "create", "update" diff --git a/endpoint-exposer/install/tofu/.terraform.lock.hcl b/endpoint-exposer/install/tofu/.terraform.lock.hcl index df1275e..955d031 100644 --- a/endpoint-exposer/install/tofu/.terraform.lock.hcl +++ b/endpoint-exposer/install/tofu/.terraform.lock.hcl @@ -1,25 +1,9 @@ # This file is maintained automatically by "tofu init". # Manual edits may be lost in future updates. -provider "registry.opentofu.org/hashicorp/external" { - version = "2.3.5" - hashes = [ - "h1:VsIY+hWGvWHaGvGTSKZslY13lPeAtSTxfZRPbpLMMhs=", - "zh:1fb9aca1f068374a09d438dba84c9d8ba5915d24934a72b6ef66ef6818329151", - "zh:3eab30e4fcc76369deffb185b4d225999fc82d2eaaa6484d3b3164a4ed0f7c49", - "zh:4f8b7a4832a68080f0bf4f155b56a691832d8a91ce8096dac0f13a90081abc50", - "zh:5ff1935612db62e48e4fe6cfb83dfac401b506a5b7b38342217616fbcab70ce0", - "zh:993192234d327ec86726041eb6d1efb001e41f32e4518ad8b9b162130b65ee9a", - "zh:ce445e68282a2c4b2d1f994a2730406df4ea47914c0932fb4a7eb040a7ec7061", - "zh:e305e17216840c54194141fb852839c2cedd6b41abd70cf8d606d6e88ed40e64", - "zh:edba65fb241d663c09aa2cbf75026c840e963d5195f27000f216829e49811437", - "zh:f306cc6f6ec9beaf75bdcefaadb7b77af320b1f9b56d8f50df5ebd2189a93148", - "zh:fb2ff9e1f86796fda87e1f122d40568912a904da51d477461b850d81a0105f3d", - ] -} - provider "registry.opentofu.org/hashicorp/http" { - version = "3.5.0" + version = "3.5.0" + constraints = "~> 3.0" hashes = [ "h1:eClUBisXme48lqiUl3U2+H2a2mzDawS9biqfkd9synw=", "zh:0a2b33494eec6a91a183629cf217e073be063624c5d3f70870456ddb478308e9", @@ -35,23 +19,6 @@ provider "registry.opentofu.org/hashicorp/http" { ] } -provider "registry.opentofu.org/hashicorp/null" { - version = "3.2.4" - hashes = [ - "h1:i+WKhUHL2REY5EGmiHjfUljJB8UKZ9QdhdM5uTeUhC4=", - "zh:1769783386610bed8bb1e861a119fe25058be41895e3996d9216dd6bb8a7aee3", - "zh:32c62a9387ad0b861b5262b41c5e9ed6e940eda729c2a0e58100e6629af27ddb", - "zh:339bf8c2f9733fce068eb6d5612701144c752425cebeafab36563a16be460fb2", - "zh:36731f23343aee12a7e078067a98644c0126714c4fe9ac930eecb0f2361788c4", - "zh:3d106c7e32a929e2843f732625a582e562ff09120021e510a51a6f5d01175b8d", - "zh:74bcb3567708171ad83b234b92c9d63ab441ef882b770b0210c2b14fdbe3b1b6", - "zh:90b55bdbffa35df9204282251059e62c178b0ac7035958b93a647839643c0072", - "zh:ae24c0e5adc692b8f94cb23a000f91a316070fdc19418578dcf2134ff57cf447", - "zh:b5c10d4ad860c4c21273203d1de6d2f0286845edf1c64319fa2362df526b5f58", - "zh:e05bbd88e82e1d6234988c85db62fd66f11502645838fff594a2ec25352ecd80", - ] -} - provider "registry.opentofu.org/integrations/github" { version = "6.11.1" constraints = "~> 6.0" @@ -77,7 +44,7 @@ provider "registry.opentofu.org/integrations/github" { provider "registry.opentofu.org/nullplatform/nullplatform" { version = "0.0.83" - constraints = ">= 0.0.67, < 0.1.0" + constraints = ">= 0.0.67, ~> 0.0.67, < 0.1.0" hashes = [ "h1:OLoy7qBT2p/P9pxLL1QBT/NND3A/ik6JordAv8loU8E=", "zh:0e9c83e413ea5a8b960520805d2bcda2b73fef7532dc9f95f84814b7fb8a0c91", diff --git a/endpoint-exposer/install/tofu/main.tf b/endpoint-exposer/install/tofu/main.tf index 9443186..1bb3901 100644 --- a/endpoint-exposer/install/tofu/main.tf +++ b/endpoint-exposer/install/tofu/main.tf @@ -6,44 +6,49 @@ module "service_definition" { source = "../../../../tofu-modules/nullplatform/service_definition" - nrn = var.nrn - np_api_key = var.np_api_key - - # Spec templates are fetched from the nullplatform/services GitHub repository - git_repo = var.git_repo - git_ref = var.git_branch - git_service_path = var.git_service_path - use_tpl_files = true - - git_password = var.github_token - - service_name = var.service_name - service_description = var.service_description + nrn = var.nrn + + # Spec templates are fetched from GitHub via HTTP and parsed as JSON. + # The module expects specs at `/specs/service-spec.json.tpl` + # (plus `specs/actions/*.json.tpl` and `specs/links/*.json.tpl` if any). + repository_org = var.repository_org + repository_name = var.repository_name + repository_branch = var.repository_branch + service_path = var.spec_path + repository_token = var.github_token + + service_name = var.service_name + + # Override the module's default `available_links = ["connect"]`: this service + # doesn't ship a `specs/links/connect.json.tpl`, and fetching a non-existent + # template would make `jsondecode()` abort planning. + available_links = [] } ################################################################################ # Service Definition Agent Association # Creates the notification channel that connects nullplatform events to the agent. +# +# The module constructs the agent cmdline as +# `${base_clone_path}/${repository_service_spec_repo}/${service_path}/entrypoint/entrypoint` +# so the agent must have the repo cloned at that location. `service_path` here +# is the runtime path (e.g. `endpoint-exposer`), NOT the specs path (which +# includes the `install/` prefix for this service). ################################################################################ module "service_definition_agent_association" { source = "../../../../tofu-modules/nullplatform/service_definition_agent_association" - nrn = var.nrn - api_key = var.np_api_key - - service_specification_id = module.service_definition.service_specification_id - service_specification_slug = module.service_definition.service_specification_slug - + nrn = var.nrn + api_key = var.np_api_key tags_selectors = var.tags_selectors - agent_command = { - type = "exec" - data = { - cmdline = "${var.repo_path}/entrypoint" - } - } + service_specification_slug = module.service_definition.service_specification_slug + repository_service_spec_repo = "${var.repository_org}/${var.repository_name}" + service_path = var.agent_service_path - service_path = var.repo_path - workflow_override_path = var.overrides_enabled ? var.overrides_repo_path : null + # Pass `--overrides-path=` to the entrypoint when local config + # overrides are enabled. The entrypoint handles the flag; the module just + # forwards arguments verbatim. + agent_arguments = var.overrides_enabled ? ["--overrides-path=${var.overrides_repo_path}"] : [] } diff --git a/endpoint-exposer/install/tofu/terraform.tfvars.example b/endpoint-exposer/install/tofu/terraform.tfvars.example index 8d04f2e..168cc7d 100644 --- a/endpoint-exposer/install/tofu/terraform.tfvars.example +++ b/endpoint-exposer/install/tofu/terraform.tfvars.example @@ -5,39 +5,36 @@ # Required ################################################################################ -nrn = "organization=2" +nrn = "organization=:account=" np_api_key = "your-nullplatform-api-key" tags_selectors = { environment = "production" } -github_token = "your-github-personal-access-token" +# Only needed if the spec repository is private. nullplatform/services is +# public, so leave commented out unless you're pointing at a fork. +# github_token = "your-github-personal-access-token" ################################################################################ -# Repository (override if using a fork or different branch) +# Repository (override if pointing at a fork or a non-default branch) ################################################################################ -# git_repo = "nullplatform/services" -# git_branch = "main" -# git_service_path = "endpoint-exposer/install" - -################################################################################ -# Agent -################################################################################ - -# repo_path = "/root/.np/nullplatform/services/endpoint-exposer" +# repository_org = "nullplatform" +# repository_name = "services" +# repository_branch = "main" +# spec_path = "endpoint-exposer/install" +# agent_service_path = "endpoint-exposer" ################################################################################ # Service Definition (override to customize the display name) ################################################################################ -# service_name = "Endpoint Exposer" -# service_description = "HTTP routing management via Kubernetes Gateway API" +# service_name = "Endpoint Exposer" ################################################################################ -# Overrides (optional — appends --overrides-path to the agent cmdline) +# Overrides (optional — passes --overrides-path to the agent entrypoint) ################################################################################ -# overrides_enabled = true -# overrides_repo_path = "/root/.np/nullplatform/scopes-networking/endpoint-exposer" +# overrides_enabled = true +# overrides_repo_path = "/root/.np/nullplatform/scopes-networking/endpoint-exposer" diff --git a/endpoint-exposer/install/tofu/variables.tf b/endpoint-exposer/install/tofu/variables.tf index 3c7b481..58c6e2f 100644 --- a/endpoint-exposer/install/tofu/variables.tf +++ b/endpoint-exposer/install/tofu/variables.tf @@ -8,7 +8,7 @@ variable "nrn" { } variable "np_api_key" { - description = "Nullplatform API key for authentication" + description = "Nullplatform API key used by the agent to authenticate with nullplatform" type = string sensitive = true } @@ -19,7 +19,7 @@ variable "tags_selectors" { } variable "github_token" { - description = "GitHub personal access token for fetching spec templates from nullplatform/services" + description = "GitHub token for fetching spec templates. Only required when the spec repository is private." type = string sensitive = true default = null @@ -27,34 +27,40 @@ variable "github_token" { ################################################################################ # Repository +# Where the service specs and runtime code live. Both are assumed to be in the +# same repository; `spec_path` and `agent_service_path` can differ if the specs +# are nested deeper (as is the case for endpoint-exposer with its `install/` +# subdirectory). ################################################################################ -variable "git_repo" { - description = "GitHub repository containing spec templates (org/repo format)" +variable "repository_org" { + description = "GitHub organization owning the service repository" type = string - default = "nullplatform/services" + default = "nullplatform" } -variable "git_branch" { - description = "Git branch to use when fetching spec templates" +variable "repository_name" { + description = "Name of the service repository" + type = string + default = "services" +} + +variable "repository_branch" { + description = "Branch of the service repository to fetch specs from. Must be a short branch name (e.g. \"main\"), not a full ref." type = string default = "main" } -variable "git_service_path" { - description = "Path within the repository where install/specs/ is located" +variable "spec_path" { + description = "Path within the repository where `specs/service-spec.json.tpl` lives (used at registration time by the service_definition module)" type = string default = "endpoint-exposer/install" } -################################################################################ -# Agent -################################################################################ - -variable "repo_path" { - description = "Local path where the endpoint-exposer directory is located on the agent" +variable "agent_service_path" { + description = "Path within the repository where the runtime `entrypoint/entrypoint` lives (used at execution time by the agent)" type = string - default = "/root/.np/nullplatform/services/endpoint-exposer" + default = "endpoint-exposer" } ################################################################################ @@ -67,24 +73,26 @@ variable "service_name" { default = "Endpoint Exposer" } -variable "service_description" { - description = "Description of the service" - type = string - default = "HTTP routing management via Kubernetes Gateway API" -} - ################################################################################ -# Overrides +# Overrides (optional) +# When enabled, the agent receives `--overrides-path=` as +# an argument, so the entrypoint can layer tenant-specific configuration on +# top of the in-repo defaults. ################################################################################ variable "overrides_enabled" { - description = "Append --overrides-path to the agent cmdline for local config overrides" + description = "Append `--overrides-path` to the agent arguments for local config overrides" type = bool default = false } variable "overrides_repo_path" { - description = "Full path to the overrides directory on the agent" + description = "Absolute path inside the agent container where the overrides directory is located. Required when overrides_enabled = true." type = string default = null + + validation { + condition = var.overrides_repo_path == null || startswith(coalesce(var.overrides_repo_path, "/"), "/") + error_message = "overrides_repo_path must be an absolute path (start with /)." + } } diff --git a/endpoint-exposer/scripts/istio/build_ingress_with_rule b/endpoint-exposer/scripts/istio/build_ingress_with_rule index b7a997c..2518a4e 100755 --- a/endpoint-exposer/scripts/istio/build_ingress_with_rule +++ b/endpoint-exposer/scripts/istio/build_ingress_with_rule @@ -5,6 +5,21 @@ set -euo pipefail +# Ensure a path starts with "/". The Kubernetes Gateway API rejects +# non-absolute values for the `Exact` and `PathPrefix` match types with: +# spec.rules[N].matches[M].path: Invalid value: ...: value must be an +# absolute path and start with '/' when type one of ['Exact', 'PathPrefix'] +# Developers often enter "health" in the UI expecting it to become "/health", +# so normalize here instead of surfacing a validator error per route. +ensure_absolute_path() { + local path="$1" + if [[ "$path" != /* ]]; then + echo "/$path" + else + echo "$path" + fi +} + # Detect path type and convert path value accordingly # Returns: "type:value" format detect_path_type() { @@ -17,6 +32,7 @@ detect_path_type() { if [[ -z "$prefix_path" ]]; then prefix_path="/" fi + prefix_path=$(ensure_absolute_path "$prefix_path") echo "PathPrefix:$prefix_path" return fi @@ -27,11 +43,14 @@ detect_path_type() { local regex_path="${path//:+([^\/])/[^/]+}" # For bash pattern replacement, we need to handle it differently regex_path=$(echo "$path" | sed 's/:[^/]*/[^\/]+/g') + # RegularExpression values are not required to be absolute by Gateway API, + # so we leave them untouched. echo "RegularExpression:$regex_path" return fi # Default: Exact match + path=$(ensure_absolute_path "$path") echo "Exact:$path" }