diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96680ad..80aeab5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,16 +7,11 @@ on: jobs: shell-tests: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install yq v4 (linux) - if: runner.os == 'Linux' + - name: Install yq v4 run: | if command -v yq && yq --version | grep -qE 'version v?4\.'; then exit 0; fi sudo curl -fsSL --retry 3 --retry-delay 2 \ @@ -25,10 +20,6 @@ jobs: sudo chmod +x /usr/local/bin/yq yq --version - - name: Install yq v4 (macos) - if: runner.os == 'macOS' - run: brew install yq && yq --version - - run: bash tests/metadata_test.sh - run: bash tests/parse_test.sh @@ -93,3 +84,48 @@ jobs: test "${{ steps.cfg3.outputs.build_context }}" = "." test "${{ steps.cfg3.outputs.dockerfile_path }}" = "Dockerfile" echo "missing-service fallback outputs OK" + + - name: Run action with include_env=true (environments present) + id: cfg4 + uses: ./ + with: + working_directory: tests/fixtures + service_name: smoke-svc + default_build_context: '.' + default_dockerfile_path: Dockerfile + include_env: 'true' + + - name: Verify environments output is populated + # IMPORTANT: env-passthrough (not direct ${{ }} in run:). The + # environments YAML can contain quotes / $ / backticks that the + # shell would otherwise re-interpret. This step doubles as a + # contract test for the README's recommended usage pattern. + env: + ENVS: ${{ steps.cfg4.outputs.environments }} + run: | + set -e + test -n "$ENVS" + printf '%s\n' "$ENVS" > /tmp/envs.yaml + yq '.[].name' /tmp/envs.yaml | grep -q '^dev$' + yq '.[].name' /tmp/envs.yaml | grep -q '^prod$' + echo "include_env=true outputs OK" + + - name: Run action with include_env=true against fixture without environments + id: cfg5 + continue-on-error: true + uses: ./ + with: + working_directory: tests/fixtures-no-env + service_name: smoke-svc + default_build_context: '.' + default_dockerfile_path: Dockerfile + include_env: 'true' + + - name: Verify include_env=true with no env block fails 'Not supported yet' + run: | + set -e + if [ "${{ steps.cfg5.outcome }}" != "failure" ]; then + echo "FAIL: include_env=true with no env block should have failed (outcome=${{ steps.cfg5.outcome }})" + exit 1 + fi + echo "include_env=true 'Not supported yet' contract OK" diff --git a/README.md b/README.md index 36d2afe..b7c0abd 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ This action parses the Skyhook configuration file and extracts service-specific | `config_path` | Path to the skyhook config file relative to working_directory | No | `.skyhook/skyhook.yaml` | | `default_build_context` | Value emitted as `build_context` whenever it would otherwise be empty: YAML field absent, config file missing, or service missing. The action does not pick a value for you - callers must pass a non-empty default, otherwise the action fails loudly. | Yes | - | | `default_dockerfile_path` | Value emitted as `dockerfile_path` whenever it would otherwise be empty (same triggers as `default_build_context`). Must be non-empty. | Yes | - | +| `include_env` | When `true`, also emit the `environments` block from the local skyhook.yaml as the `environments` output. When `true` and the local config has no `environments` block (key absent, or any YAML null spelling: `null` / `Null` / `NULL` / `~` / bare `environments:`), the action exits 1 with `Not supported yet`. When `true` and the config file itself is missing, the action also exits 1 with `Not supported yet`. Both cases are reserved for a future external-repository fetch path. | No | `false` | +| `git_token` | Reserved for a future release: token used to fetch the `environments` block from an external repository when `include_env=true` and the block is not present locally. Currently unused - the action fails `Not supported yet` in that scenario regardless of this input. | No | `''` | ## Outputs @@ -52,6 +54,7 @@ This action parses the Skyhook configuration file and extracts service-specific | `dockerfile_path` | Dockerfile path relative to repo root. Falls back to `default_dockerfile_path` when absent/empty in config or when config/service is not found. | | `config_found` | Whether the config file was found (`true`/`false`) | | `service_found` | Whether the service was found in config (`true`/`false`) | +| `environments` | YAML block of environments from the local skyhook.yaml when `include_env=true` and the block is present. Empty when `include_env=false`. When `include_env=true` and the block is absent, the action exits 1 instead of emitting this output. | > When a default is applied because a YAML field was absent, the action emits a `::notice::` line. When the entire config file or service is missing, the action emits a `::warning::` (more prominent in the run UI) — so the source of every emitted value is visible at a glance. @@ -111,6 +114,65 @@ jobs: image: ${{ inputs.image }} ``` +## Example: include the environments block + +```yaml +- name: Read service config + environments + id: config + uses: skyhook-io/read-config@v1 + with: + working_directory: code + service_name: my-service + default_build_context: '.' + default_dockerfile_path: Dockerfile + include_env: 'true' + +- name: Use environments + env: + # IMPORTANT: pass via env, not via direct expression interpolation. The + # environments YAML can contain double quotes, `$`, backticks, etc., which + # would be re-interpreted by the shell if inlined into the `run:` script. + ENVS: ${{ steps.config.outputs.environments }} + run: | + printf '%s\n' "$ENVS" > /tmp/envs.yaml + yq '.[].name' /tmp/envs.yaml +``` + +For a `skyhook.yaml` containing: + +```yaml +environments: + - name: autopush + clusterName: nonprod-cluster-us-east1 + cloudProvider: gcp + account: koalabackend + location: us-east1-b + namespace: autopush + - name: dev + clusterName: nonprod-cluster-us-east1 + cloudProvider: gcp + account: koalabackend + location: us-east1-b + namespace: dev + - name: prod + clusterName: prod-cluster-us-east1 + cloudProvider: gcp + account: koalabackend + location: us-east1-b + namespace: prod + - name: ephemeral + clusterName: "" + namespace: ephemeral +``` + +the `environments` output contains the YAML list above (without the top-level `environments:` key). + +If `include_env=true` and the local `skyhook.yaml` has no `environments:` block (key absent, or any YAML null spelling: `null` / `Null` / `NULL` / `~` / bare `environments:`) the action exits 1 with `Not supported yet`. Same exit if the config file itself is missing. An explicit empty list (`environments: []`) is passed through as-is - the action returns whatever the file declares. + +`environments` is orthogonal to service lookup: when `include_env=true` and the block is present, the output is populated even if the named service is missing. (The service-missing `::warning::` still fires for the per-service outputs.) + +`git_token` is declared so callers can wire it in advance, but the external-repository code path is not yet implemented. + ## Behavior matrix Let `BC` = `default_build_context` input, `DF` = `default_dockerfile_path` input. @@ -127,6 +189,11 @@ Let `BC` = `default_build_context` input, `DF` = `default_dockerfile_path` input | Config file missing, both defaults non-empty | `false` | `false` | `""` | `BC` | `DF` | | Any of the above where the relevant default is empty | n/a | n/a | n/a | n/a | **action exits 1** | | Duplicate service names in config | n/a | n/a | n/a | n/a | action exits 1 | +| `include_env=true`, `environments` present in local config | unchanged | unchanged | unchanged | unchanged | unchanged - and `environments` output is populated | +| `include_env=true`, `environments: []` (explicit empty list) | unchanged | unchanged | unchanged | unchanged | unchanged - `environments` output is the literal `[]` (pass-through) | +| `include_env=true`, no `environments` block (key absent, `null`, `~`, `Null`, `NULL`, or bare) | n/a | n/a | n/a | n/a | **action exits 1 - `Not supported yet`** | +| `include_env=true`, local config file missing | n/a | n/a | n/a | n/a | **action exits 1 - `Not supported yet`** | +| `include_env=false` (default) | unchanged | unchanged | unchanged | unchanged | `environments` output is empty | The action **always** emits a non-empty `build_context` and `dockerfile_path` on success, so the consuming workflow can drop `||` fallbacks: diff --git a/action.yml b/action.yml index 3a8ba1a..83c70b7 100644 --- a/action.yml +++ b/action.yml @@ -26,6 +26,23 @@ inputs: Value emitted as `dockerfile_path` whenever it would otherwise be empty (same triggers as `default_build_context`). Must be non-empty. required: true + include_env: + description: | + When `true`, also emit the `environments` block from the local skyhook.yaml as the + `environments` output. If `true` and the local config has no `environments` block (key absent, + or any YAML null spelling: `null` / `Null` / `NULL` / `~` / bare `environments:`), the action + exits 1 with "Not supported yet". Same exit if the config file itself is missing. An explicit + empty list (`environments: []`) is passed through as-is. Sourcing environments from an external + repository is reserved for a future release. Default `false`. + required: false + default: 'false' + git_token: + description: | + Reserved for a future release: token used to fetch the `environments` block from an external + repository when `include_env=true` and the block is not present locally. Currently unused - + the action fails with "Not supported yet" in that scenario regardless of this input. + required: false + default: '' outputs: # Service configuration @@ -57,6 +74,15 @@ outputs: service_found: description: 'Whether the service was found in config (true/false)' value: ${{ steps.parse.outputs.service_found }} + environments: + description: | + YAML-formatted block of environments from the local skyhook.yaml when `include_env=true` and + the block is present (multi-line, passed back verbatim - including the `[]` literal for an + explicit empty list). Empty string when `include_env=false` (the default). When + `include_env=true` and the block is absent, the action exits 1 ("Not supported yet") instead + of emitting this output. Consume via env-passthrough (see README) to preserve quotes / $ / + backticks across shell expansion. + value: ${{ steps.parse.outputs.environments }} runs: using: 'composite' @@ -124,6 +150,8 @@ runs: CONFIG_PATH: ${{ inputs.config_path }} DEFAULT_BUILD_CONTEXT: ${{ inputs.default_build_context }} DEFAULT_DOCKERFILE_PATH: ${{ inputs.default_dockerfile_path }} + INCLUDE_ENV: ${{ inputs.include_env }} + GIT_TOKEN: ${{ inputs.git_token }} run: bash "$GITHUB_ACTION_PATH/scripts/parse.sh" branding: diff --git a/scripts/parse.sh b/scripts/parse.sh index 7f32e78..85dac63 100755 --- a/scripts/parse.sh +++ b/scripts/parse.sh @@ -9,6 +9,12 @@ # config/service is not found. The action does not pick a default # for the caller; this is intentional so callers must opt in. # DEFAULT_DOCKERFILE_PATH - REQUIRED. Same contract as DEFAULT_BUILD_CONTEXT, for dockerfile_path. +# INCLUDE_ENV - "true" to additionally emit the `environments` block from the local +# config as the `environments` output. Anything else (including unset) +# is treated as false. When "true" and the local config has no +# `environments` block (or the file is missing), the script exits 1 +# with "Not supported yet" - external-repo support is not implemented. +# GIT_TOKEN - Reserved for the future external-repo path. Currently unused. # GITHUB_OUTPUT - file to append outputs to (required by GitHub Actions; tests pass a tempfile) # # Exits non-zero with ::error:: on: @@ -34,6 +40,7 @@ WORKING_DIR="${WORKING_DIR:-.}" WORKING_DIR="${WORKING_DIR%/}" CONFIG_PATH="${CONFIG_PATH:-.skyhook/skyhook.yaml}" SERVICE_NAME="${SERVICE_NAME:-}" +INCLUDE_ENV="${INCLUDE_ENV:-false}" if [ -z "$SERVICE_NAME" ]; then echo "::error::service_name input is required and must be non-empty" @@ -97,14 +104,62 @@ require_nonempty_defaults_for_unmatched() { fi } +# Emit (or refuse to emit) the `environments` output. Safe to call from any +# exit path that has confirmed CONFIG_FILE exists, OR from the empty-on-disable +# case (INCLUDE_ENV != "true"). +# +# Contract: +# - INCLUDE_ENV != "true" -> emit empty string (the default; backwards-compatible). +# - INCLUDE_ENV == "true" + `environments` block present -> emit it as YAML (heredoc form +# preserves multi-line structure across $GITHUB_OUTPUT). +# - INCLUDE_ENV == "true" + `environments` block absent -> exit 1 "Not supported yet" +# (external-repo lookup via GIT_TOKEN is reserved for a future release). +emit_environments() { + if [ "$INCLUDE_ENV" != "true" ]; then + write_output environments "" + echo " environments: skipped (include_env=${INCLUDE_ENV})" + return 0 + fi + # Detect "no environments" structurally via yq's tag, not by string-matching + # the value: YAML has four null spellings (null / Null / NULL / ~), plus a + # bare key with no value (also null), plus an outright-missing key. yq's + # tag is `!!null` in all of those cases; anything else means the key was + # explicitly set to a value (including `[]`, which passes through verbatim + # per the user spec - if `environments` is in the yaml, return it). + local env_tag environments + if ! env_tag=$(yq e '.environments | tag' "$CONFIG_FILE"); then + echo "::error::Failed to parse $CONFIG_FILE while reading 'environments'" + exit 1 + fi + if [ "$env_tag" = "!!null" ]; then + echo "::error::include_env=true but no 'environments' block in '${CONFIG_FILE}' - Not supported yet (external-repo environments source is not implemented; git_token is reserved for that future path)" + exit 1 + fi + if ! environments=$(yq e '.environments' "$CONFIG_FILE"); then + echo "::error::Failed to parse $CONFIG_FILE while reading 'environments'" + exit 1 + fi + write_output environments "$environments" + echo " environments: emitted (include_env=true, tag=${env_tag})" +} + # Config file missing if [ ! -f "$CONFIG_FILE" ]; then + # include_env=true means the caller wants the environments block from the local file. + # No local file -> there's nothing to return, and the external-repo path is not built yet. + if [ "$INCLUDE_ENV" = "true" ]; then + echo "::error::include_env=true but the local config file '${CONFIG_FILE}' is missing - Not supported yet (external-repo environments source is not implemented)" + exit 1 + fi echo "Config file not found: $CONFIG_FILE" require_nonempty_defaults_for_unmatched "Config file not found at '${CONFIG_FILE}'" echo "::warning::Config file not found - emitting build_context='${DEFAULT_BUILD_CONTEXT}' and dockerfile_path='${DEFAULT_DOCKERFILE_PATH}' from default_build_context / default_dockerfile_path inputs" write_output config_found false write_output service_found false write_unmatched_outputs + # Safe: INCLUDE_ENV is guaranteed != "true" here (fail-fast above caught the true case), + # so emit_environments will just write an empty string. + emit_environments exit 0 fi @@ -127,6 +182,7 @@ if [ -z "$SERVICE_INDEX" ]; then echo "::warning::Service '${SERVICE_NAME}' not found - emitting build_context='${DEFAULT_BUILD_CONTEXT}' and dockerfile_path='${DEFAULT_DOCKERFILE_PATH}' from default_build_context / default_dockerfile_path inputs" write_output service_found false write_unmatched_outputs + emit_environments exit 0 fi @@ -185,6 +241,7 @@ write_output deployment_repo "$DEPLOYMENT_REPO" write_output deployment_repo_path "$DEPLOYMENT_REPO_PATH" write_output build_context "$BUILD_CONTEXT" write_output dockerfile_path "$DOCKERFILE_PATH" +emit_environments echo "Parsed service configuration:" echo " name: ${NAME}" @@ -193,3 +250,5 @@ echo " deployment_repo: ${DEPLOYMENT_REPO}" echo " deployment_repo_path: ${DEPLOYMENT_REPO_PATH}" echo " build_context: ${BUILD_CONTEXT}" echo " dockerfile_path: ${DOCKERFILE_PATH}" +# Note: the environments log line is emitted by emit_environments() above so it +# also fires on the early-exit paths (config-missing, service-not-found). diff --git a/tests/fixtures-no-env/.skyhook/skyhook.yaml b/tests/fixtures-no-env/.skyhook/skyhook.yaml new file mode 100644 index 0000000..796acfb --- /dev/null +++ b/tests/fixtures-no-env/.skyhook/skyhook.yaml @@ -0,0 +1,11 @@ +# Fixture for the CI 'include_env=true with no environments block' smoke step. +# Intentionally has no `environments:` key so the action exits 1 with +# "Not supported yet" - the smoke job uses continue-on-error and asserts that +# outcome. +services: + - name: smoke-svc + path: services/smoke + buildTool: + docker: + buildContext: services/smoke + dockerfilePath: services/smoke/Dockerfile diff --git a/tests/fixtures/.skyhook/skyhook.yaml b/tests/fixtures/.skyhook/skyhook.yaml index 2743b8a..96b7cda 100644 --- a/tests/fixtures/.skyhook/skyhook.yaml +++ b/tests/fixtures/.skyhook/skyhook.yaml @@ -12,3 +12,11 @@ services: buildTool: docker: dockerfilePath: services/default/Dockerfile + +environments: + - name: dev + clusterName: dev-cluster + namespace: dev + - name: prod + clusterName: prod-cluster + namespace: prod diff --git a/tests/parse_test.sh b/tests/parse_test.sh index 8fb9ec1..c3ec0f6 100755 --- a/tests/parse_test.sh +++ b/tests/parse_test.sh @@ -436,6 +436,222 @@ assert_eq "$(read_output "$out" deployment_repo)" "key=value-equals" "literal '= expected_multiline=$'line1\nline2' assert_eq "$(read_output "$out" deployment_repo_path)" "$expected_multiline" "multiline value round-trips through heredoc" +# --- include_env tests ------------------------------------------------------ +# Lightweight runner that lets a test pick INCLUDE_ENV per call. Kept separate +# from run_parse() so the existing tests (which assert INCLUDE_ENV defaults to +# off) keep proving the backwards-compatible path. +run_parse_env() { + local working_dir="$1" svc="$2" include_env="$3" cfg_path="${4:-.skyhook/skyhook.yaml}" + local out_file err_file + out_file="$WORK/gh_output.$RANDOM" + err_file="$WORK/gh_err.$RANDOM" + : >"$out_file" + set +e + WORKING_DIR="$working_dir" \ + SERVICE_NAME="$svc" \ + CONFIG_PATH="$cfg_path" \ + DEFAULT_BUILD_CONTEXT="$DEFAULT_BC" \ + DEFAULT_DOCKERFILE_PATH="$DEFAULT_DF" \ + INCLUDE_ENV="$include_env" \ + GITHUB_OUTPUT="$out_file" \ + bash "$PARSE_SCRIPT" >"$err_file" 2>&1 + RC=$? + set -e + COMBINED="$(cat "$err_file")" + OUT_FILE="$out_file" +} + +# Fixture with both services and an environments block - covers the happy path +# AND the "service-not-found but environments present" case in one fixture. +ENV_DIR="$WORK/with-envs" +mkdir -p "$ENV_DIR/.skyhook" +cat >"$ENV_DIR/.skyhook/skyhook.yaml" <<'YAML' +services: + - name: svc-a + path: services/a + buildTool: + docker: + buildContext: services/a + dockerfilePath: services/a/Dockerfile +environments: + - name: autopush + clusterName: nonprod-cluster-us-east1 + cloudProvider: gcp + account: koalabackend + location: us-east1-b + namespace: autopush + - name: dev + clusterName: nonprod-cluster-us-east1 + cloudProvider: gcp + account: koalabackend + location: us-east1-b + namespace: dev + - name: prod + clusterName: prod-cluster-us-east1 + cloudProvider: gcp + account: koalabackend + location: us-east1-b + namespace: prod + - name: ephemeral + clusterName: "" + namespace: ephemeral +YAML + +# --- include_env=true + environments present + service found => emitted --- +run_parse_env "$ENV_DIR" "svc-a" "true" +[ "$RC" -eq 0 ] || { echo "FAIL: include_env=true with env block + valid service should succeed"; echo "$COMBINED"; exit 1; } +got_env=$(read_output "$OUT_FILE" environments) +assert_contains "$got_env" "name: autopush" "include_env=true: environments output contains 'autopush'" +assert_contains "$got_env" "name: ephemeral" "include_env=true: environments output contains 'ephemeral'" +assert_contains "$got_env" "clusterName: prod-cluster-us-east1" "include_env=true: nested fields survive heredoc round-trip" +# Sanity: the OTHER outputs are untouched by include_env=true. +assert_eq "$(read_output "$OUT_FILE" service_found)" "true" "include_env=true: service_found still true" +assert_eq "$(read_output "$OUT_FILE" build_context)" "services/a" "include_env=true: build_context unchanged" +# Log line: a future change to the wording must update the test deliberately. +assert_contains "$COMBINED" "environments: emitted" "include_env=true: log records the emission" + +# --- include_env=true + environments present + service MISSING => env still emitted --- +# environments is a top-level concern, independent of service lookup. Confirming +# the not-found service branch doesn't drop the environments output. +run_parse_env "$ENV_DIR" "nonexistent" "true" +[ "$RC" -eq 0 ] || { echo "FAIL: include_env=true with env block + missing service should succeed (defaults flow through)"; echo "$COMBINED"; exit 1; } +got_env=$(read_output "$OUT_FILE" environments) +assert_contains "$got_env" "name: autopush" "include_env=true + missing service: env still emitted" +assert_eq "$(read_output "$OUT_FILE" service_found)" "false" "include_env=true + missing service: service_found=false" + +# --- include_env=false (default) + env block present => environments empty --- +# Regression guard: env data must NOT leak unless include_env=true. +run_parse_env "$ENV_DIR" "svc-a" "false" +[ "$RC" -eq 0 ] || { echo "FAIL: include_env=false should succeed even when env block present"; echo "$COMBINED"; exit 1; } +assert_eq "$(read_output "$OUT_FILE" environments)" "" "include_env=false: environments output stays empty" +assert_contains "$COMBINED" "environments: skipped" "include_env=false: log records the skip" + +# --- include_env=true + missing service still logs the emission (consistency guard) --- +# emit_environments() is called from the service-not-found branch too. The log +# line must fire there so users see the same diagnostic regardless of branch. +run_parse_env "$ENV_DIR" "nonexistent" "true" +assert_contains "$COMBINED" "environments: emitted" "include_env=true + missing service: log records the emission" + +# --- include_env=true + no environments block in config => Not supported yet --- +NOENV_DIR="$WORK/no-envs" +mkdir -p "$NOENV_DIR/.skyhook" +cat >"$NOENV_DIR/.skyhook/skyhook.yaml" <<'YAML' +services: + - name: only-svc + path: services/only + buildTool: + docker: + buildContext: services/only + dockerfilePath: services/only/Dockerfile +YAML +run_parse_env "$NOENV_DIR" "only-svc" "true" +[ "$RC" -ne 0 ] || { echo "FAIL: include_env=true with no env block should exit non-zero"; echo "$COMBINED"; exit 1; } +assert_contains "$COMBINED" "Not supported yet" "include_env=true + no env block: error mentions 'Not supported yet'" +assert_contains "$COMBINED" "no 'environments' block" "include_env=true + no env block: error names the missing key" + +# --- include_env=true + environments: null => Not supported yet --- +# Explicit null is semantically "no environments" - same outcome as missing key. +NULL_ENV_DIR="$WORK/null-envs" +mkdir -p "$NULL_ENV_DIR/.skyhook" +cat >"$NULL_ENV_DIR/.skyhook/skyhook.yaml" <<'YAML' +services: + - name: only-svc + path: services/only + buildTool: + docker: + buildContext: services/only + dockerfilePath: services/only/Dockerfile +environments: null +YAML +run_parse_env "$NULL_ENV_DIR" "only-svc" "true" +[ "$RC" -ne 0 ] || { echo "FAIL: include_env=true with 'environments: null' should exit non-zero"; echo "$COMBINED"; exit 1; } +assert_contains "$COMBINED" "Not supported yet" "include_env=true + environments:null: 'Not supported yet' error" + +# --- include_env=true + YAML null aliases (~, Null, NULL) => Not supported yet --- +# Regression guard: the original textual check only matched lowercase "null". +# The structural (tag-based) check must catch every YAML null spelling so +# users can't accidentally bypass the failure with idiomatic alternatives. +alias_idx=0 +for spelling in '~' 'Null' 'NULL'; do + alias_idx=$((alias_idx + 1)) + # Use an index-based dir name (not the spelling itself): 'Null' and 'NULL' + # collide on case-insensitive filesystems like macOS APFS, which would + # silently overwrite the previous iteration's fixture. + ALIAS_DIR="$WORK/null-alias-$alias_idx" + mkdir -p "$ALIAS_DIR/.skyhook" + cat >"$ALIAS_DIR/.skyhook/skyhook.yaml" < Not supported yet --- +# YAML parses a key with no value as null, same logical state as the explicit-null case. +BARE_ENV_DIR="$WORK/bare-envs" +mkdir -p "$BARE_ENV_DIR/.skyhook" +cat >"$BARE_ENV_DIR/.skyhook/skyhook.yaml" <<'YAML' +services: + - name: only-svc + path: services/only + buildTool: + docker: + buildContext: services/only + dockerfilePath: services/only/Dockerfile +environments: +YAML +run_parse_env "$BARE_ENV_DIR" "only-svc" "true" +[ "$RC" -ne 0 ] || { echo "FAIL: include_env=true with bare 'environments:' should exit non-zero"; echo "$COMBINED"; exit 1; } +assert_contains "$COMBINED" "Not supported yet" "include_env=true + bare environments: 'Not supported yet' error" + +# --- include_env=true + environments: [] => emits "[]" (pass-through, per spec) --- +# Spec: "If include true and it in the skyhook.yaml, return it". An empty list +# IS in the yaml - the user explicitly wrote []. We return the literal data; +# the caller decides whether an empty list is meaningful. This test pins the +# behavior so a future "fail on empty" change has to be deliberate. +EMPTY_LIST_DIR="$WORK/empty-list-envs" +mkdir -p "$EMPTY_LIST_DIR/.skyhook" +cat >"$EMPTY_LIST_DIR/.skyhook/skyhook.yaml" <<'YAML' +services: + - name: only-svc + path: services/only + buildTool: + docker: + buildContext: services/only + dockerfilePath: services/only/Dockerfile +environments: [] +YAML +run_parse_env "$EMPTY_LIST_DIR" "only-svc" "true" +[ "$RC" -eq 0 ] || { echo "FAIL: include_env=true with empty list should succeed (pass-through)"; echo "$COMBINED"; exit 1; } +assert_eq "$(read_output "$OUT_FILE" environments)" "[]" "include_env=true + empty list: emits '[]' verbatim" + +# --- include_env=true + config file missing => Not supported yet --- +run_parse_env "$WORK/no-such-dir-env" "anything" "true" +[ "$RC" -ne 0 ] || { echo "FAIL: include_env=true with missing config file should exit non-zero"; echo "$COMBINED"; exit 1; } +assert_contains "$COMBINED" "Not supported yet" "include_env=true + missing config: error mentions 'Not supported yet'" +assert_contains "$COMBINED" "the local config file" "include_env=true + missing config: error mentions local config file" + +# --- include_env=false + config file missing => existing fallback path still works --- +# (Regression guard: the new fail-fast path must NOT trigger when include_env != "true".) +run_parse_env "$WORK/no-such-dir-env" "anything" "false" +[ "$RC" -eq 0 ] || { echo "FAIL: include_env=false + missing config should still fall through to defaults"; echo "$COMBINED"; exit 1; } +assert_eq "$(read_output "$OUT_FILE" config_found)" "false" "include_env=false + missing config: config_found=false" +assert_eq "$(read_output "$OUT_FILE" environments)" "" "include_env=false + missing config: environments empty" + +# --- include_env unset behaves like include_env=false (default) --- +# This is what the existing run_parse() helper has been exercising all along; +# spot-check it explicitly so a future "default true" mistake gets caught. +out=$(run_parse "$ENV_DIR" svc-a) +assert_eq "$(read_output "$out" environments)" "" "INCLUDE_ENV unset: environments output empty (default off)" + # --- action.yml structure sanity --- grep -q 'bash "\$GITHUB_ACTION_PATH/scripts/parse.sh"' "$ACTION_FILE" || { echo "FAIL: action.yml does not call scripts/parse.sh"; exit 1; } grep -q 'version v?4\\.' "$ACTION_FILE" || { echo "FAIL: action.yml does not validate yq v4"; exit 1; } @@ -446,6 +662,11 @@ grep -q "default_build_context:" "$ACTION_FILE" || { echo "FAIL: action.yml does grep -q "default_dockerfile_path:" "$ACTION_FILE" || { echo "FAIL: action.yml does not declare default_dockerfile_path input"; exit 1; } grep -q "DEFAULT_BUILD_CONTEXT:" "$ACTION_FILE" || { echo "FAIL: action.yml does not wire DEFAULT_BUILD_CONTEXT env"; exit 1; } grep -q "DEFAULT_DOCKERFILE_PATH:" "$ACTION_FILE" || { echo "FAIL: action.yml does not wire DEFAULT_DOCKERFILE_PATH env"; exit 1; } +grep -q "include_env:" "$ACTION_FILE" || { echo "FAIL: action.yml does not declare include_env input"; exit 1; } +grep -q "git_token:" "$ACTION_FILE" || { echo "FAIL: action.yml does not declare git_token input"; exit 1; } +grep -q "environments:" "$ACTION_FILE" || { echo "FAIL: action.yml does not declare environments output"; exit 1; } +grep -q "INCLUDE_ENV:" "$ACTION_FILE" || { echo "FAIL: action.yml does not wire INCLUDE_ENV env"; exit 1; } +grep -q "GIT_TOKEN:" "$ACTION_FILE" || { echo "FAIL: action.yml does not wire GIT_TOKEN env"; exit 1; } # Both default inputs must be required:true (no implicit defaults) awk '/^ default_build_context:/{f=1} f && /required: true/{print "OK"; exit} f && /^ [a-z]/ && !/^ default_build_context:/{exit}' "$ACTION_FILE" | grep -q OK \ || { echo "FAIL: default_build_context is not required: true"; exit 1; }