diff --git a/.github/workflows/docker-dependency-updater.yml b/.github/workflows/docker-dependency-updater.yml index 006465ff..c7a9172a 100644 --- a/.github/workflows/docker-dependency-updater.yml +++ b/.github/workflows/docker-dependency-updater.yml @@ -86,7 +86,7 @@ jobs: - name: Stop if an update PR is already open id: existing-pr - uses: actions/github-script@v8 + uses: actions/github-script@v9 env: UPDATE_BRANCH: ${{ steps.config.outputs.update_branch }} with: @@ -246,7 +246,7 @@ jobs: PROBE_REPORT: ${{ needs.config.outputs.report_path }} - name: Upload dependency report - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 timeout-minutes: 3 with: name: docker-dependency-report @@ -362,7 +362,7 @@ jobs: REPORT_PATH: ${{ needs.config.outputs.report_path }} - name: Upload Copilot session - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 timeout-minutes: 3 with: name: copilot-docker-dependency-session @@ -374,7 +374,7 @@ jobs: - name: Create pull request id: create-pr if: steps.changes.outputs.changed == 'true' - uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 timeout-minutes: 5 with: commit-message: ${{ needs.config.outputs.commit_message }} diff --git a/.github/workflows/docker-ops.yml b/.github/workflows/docker-ops.yml index 7fa23b49..c6a800ec 100644 --- a/.github/workflows/docker-ops.yml +++ b/.github/workflows/docker-ops.yml @@ -1,5 +1,5 @@ --- -name: Docker Ops +name: Build/Release "on": push: @@ -21,6 +21,7 @@ name: Docker Ops jobs: docker-ops: + name: Docker Ops permissions: contents: write security-events: write @@ -35,3 +36,43 @@ jobs: version_config_path: ci/git-version.yml secrets: docker_token: ${{ secrets.DOCKER_TOKEN }} + + runtime-output: + name: Runtime Output + needs: docker-ops + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Build worker image + run: make build IMAGE_NAME=worker-runtime-output TAG="${GITHUB_SHA}" + + - name: Capture runtime output + run: | + mkdir -p runtime-output + docker run --rm \ + -e WORKER_RUNTIME_OUTPUT=true \ + "worker-runtime-output:${GITHUB_SHA}" \ + true \ + > runtime-output/runtime.json + jq -e '.env | type == "object"' runtime-output/runtime.json + jq -c . runtime-output/runtime.json + + - name: Write runtime output summary + run: | + { + echo "### Worker runtime output" + echo + echo '```json' + jq . runtime-output/runtime.json + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload runtime output artifact + uses: actions/upload-artifact@v7 + with: + name: worker-runtime-output + path: runtime-output/runtime.json diff --git a/.rabbit/context.yaml b/.rabbit/context.yaml index 80914ee1..6c7fe7e7 100644 --- a/.rabbit/context.yaml +++ b/.rabbit/context.yaml @@ -6,7 +6,7 @@ generator: tool: dev.kit repo: https://github.com/udx/dev.kit version: 0.13.0 - generated_at: 2026-05-31T09:26:48Z + generated_at: 2026-06-15T11:33:01Z sources: homepage: https://udx.dev/kit repository: https://github.com/udx/dev.kit diff --git a/Dockerfile b/Dockerfile index e9c4266b..36e3995f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,8 +53,8 @@ RUN apt-get update && \ jq=1.8.1-3ubuntu1.1 \ zip=3.0-15ubuntu2 \ unzip=6.0-28ubuntu7 \ - nano=8.4-1 \ - vim=2:9.1.0967-1ubuntu6.5 \ + nano=8.4-1ubuntu0.1 \ + vim=2:9.1.0967-1ubuntu6.6 \ python3.13=3.13.7-1ubuntu0.4 \ python3.13-venv=3.13.7-1ubuntu0.4 \ supervisor=4.2.5-3 && \ @@ -65,7 +65,7 @@ RUN apt-get update && \ ln -s /opt/az/bin/az /usr/local/bin/az && \ # Clean up pip cache and temp files rm -rf /root/.cache/pip && \ - find /opt/az -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true && \ + (find /opt/az -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true) && \ apt-get clean && \ rm -rf /tmp/* /var/tmp/* && \ # Set up sources.list.d for child images @@ -74,15 +74,15 @@ RUN apt-get update && \ # Configure the timezone RUN echo $TZ > /etc/timezone && \ - rm /etc/localtime && \ - ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ - dpkg-reconfigure -f noninteractive tzdata + rm -f /etc/localtime && \ + ln -snf /usr/share/zoneinfo/$TZ /etc/localtime # Install yq (architecture-aware) RUN ARCH=$(uname -m) && \ if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; elif [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; fi && \ - curl -sL https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_${ARCH}.tar.gz | tar xz && \ - mv yq_linux_${ARCH} /usr/bin/yq && \ + curl -fsSL "https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_${ARCH}.tar.gz" -o /tmp/yq.tar.gz && \ + tar -xzf /tmp/yq.tar.gz -C /tmp && \ + mv /tmp/yq_linux_${ARCH} /usr/bin/yq && \ rm -rf /tmp/* # Install Google Cloud SDK (architecture-aware) diff --git a/Makefile b/Makefile index 9dbb97c7..95d1b06a 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,7 @@ run: clean fi @docker run $(if $(INTERACTIVE),-it,-d) --rm --name $(CONTAINER_NAME) \ --env-file $(ENV_FILE) \ + $(RUN_ENV) \ $(foreach vol,$(VOLUMES),-v $(vol)) \ $(DOCKER_IMAGE) $(COMMAND) @if [ "$(INTERACTIVE)" = "true" ]; then \ @@ -106,8 +107,9 @@ test: clean COMMAND="/home/udx/test/main.sh" @printf "$(COLOR_BLUE)$(SYM_ARROW) Following test output...$(COLOR_RESET)\n" @docker logs -f $(CONTAINER_NAME) & LOGS_PID=$$!; \ - docker wait $(CONTAINER_NAME) > /dev/null; EXIT_CODE=$$?; \ + EXIT_CODE=$$(docker wait $(CONTAINER_NAME)); \ kill $$LOGS_PID 2>/dev/null || true; \ + wait $$LOGS_PID 2>/dev/null || true; \ exit $$EXIT_CODE @$(MAKE) clean || exit 1 @printf "$(COLOR_GREEN)$(SYM_SUCCESS) Tests completed successfully$(COLOR_RESET)\n" diff --git a/Makefile.help b/Makefile.help index f89db0e6..228f1a01 100644 --- a/Makefile.help +++ b/Makefile.help @@ -48,4 +48,5 @@ help: @echo " COMMAND (default: $(COMMAND))" @echo " MULTIPLATFORM (default: $(MULTIPLATFORM))" @echo " DOCKER_IMAGE (default: $(DOCKER_IMAGE))" - @echo " FOLLOW_LOGS (default: $(FOLLOW_LOGS))" \ No newline at end of file + @echo " FOLLOW_LOGS (default: $(FOLLOW_LOGS))" + @echo " RUN_ENV (default: $(RUN_ENV))" diff --git a/Makefile.variables b/Makefile.variables index c4853a5f..b0eee4a5 100644 --- a/Makefile.variables +++ b/Makefile.variables @@ -14,6 +14,7 @@ DEBUG ?= false COMMAND ?= MULTIPLATFORM ?= false FOLLOW_LOGS ?= false +RUN_ENV ?= # Colors for better visibility COLOR_RESET=\033[0m @@ -26,4 +27,4 @@ COLOR_YELLOW=\033[33m SYM_ARROW=➜ SYM_SUCCESS=✔ SYM_ERROR=✖ -SYM_WARNING=⚠ \ No newline at end of file +SYM_WARNING=⚠ diff --git a/README.md b/README.md index 0ec732a6..73c19d55 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ More examples available in [src/examples/README.md](src/examples/README.md). - [Secrets](docs/secrets.md) - secret references and provider auth boundaries - [Services](docs/services.md) - `services.yaml` process config - [Deployment](docs/deployment.md) - Docker, Kubernetes, and CI usage +- [Runtime Output](docs/runtime-output.md) - redacted runtime contract capture - [Development](docs/development.md) - Build, test, and child image workflow - [Reference Docs](docs/references/README.md) - provider auth options and container structure - [Examples](src/examples/README.md) - Runnable samples diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index 1c4cb7e8..374f41aa 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -2,19 +2,29 @@ # shellcheck disable=SC1091 source "${WORKER_LIB_DIR}/utils.sh" - -log_info "Welcome to UDX Worker Container. Initializing environment..." - # shellcheck disable=SC1091 source "${WORKER_LIB_DIR}/worker_config.sh" # shellcheck disable=SC1091 source "${WORKER_LIB_DIR}/secrets.sh" -configure_environment || exit 1 - # shellcheck disable=SC1091 source "${WORKER_LIB_DIR}/runtime_output.sh" + +if runtime_output_enabled; then + exec 3>&1 + exec 1>&2 + export WORKER_RUNTIME_OUTPUT_FD=3 +fi + +log_info "Welcome to UDX Worker Container. Initializing environment..." + +configure_environment || exit 1 + emit_runtime_output || exit 1 +if runtime_output_enabled; then + exec 3>&- +fi + # Start the process manager log_info "Starting process manager..." "${WORKER_LIB_DIR}/process_manager.sh" & diff --git a/docs/config.md b/docs/config.md index 472d8205..67dbbb60 100644 --- a/docs/config.md +++ b/docs/config.md @@ -81,14 +81,13 @@ docker run --rm \ ## Runtime Output -By default the worker does not print runtime config details or write output files. The entrypoint logs a short hint that output can be enabled. +By default the worker does not print runtime config details. Set `WORKER_RUNTIME_OUTPUT=true` to emit the redacted runtime contract JSON on stdout. -Set `WORKER_OUTPUT_FILE` when a deployment or workflow needs runtime config evidence. The worker writes redacted JSON runtime metadata to that path. - -Workflow-specific outputs such as `$GITHUB_OUTPUT`, `$GITHUB_STEP_SUMMARY`, or platform annotations should be generated by the workflow from the JSON file. +See `docs/runtime-output.md` for the output contract, capture examples, and CI artifact usage. ## Related Docs - `docs/services.md` - `docs/secrets.md` - `docs/deployment.md` +- `docs/runtime-output.md` diff --git a/docs/deployment.md b/docs/deployment.md index d39fb1da..ce5baa81 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -52,3 +52,4 @@ For orchestrators such as Kubernetes, mount `worker.yaml` and `services.yaml` th - `docs/config.md` - `docs/services.md` - `docs/secrets.md` +- `docs/runtime-output.md` diff --git a/docs/runtime-output.md b/docs/runtime-output.md new file mode 100644 index 00000000..c9d10d6b --- /dev/null +++ b/docs/runtime-output.md @@ -0,0 +1,65 @@ +# Runtime Output + +## Overview + +Runtime output is an opt-in contract for workflows and deployments that need structured evidence of the worker runtime configuration. + +Set `WORKER_RUNTIME_OUTPUT=true` to emit redacted runtime JSON on stdout. In this mode the entrypoint sends setup logs, process-manager logs, and command output to stderr so stdout remains reserved for the JSON contract. The worker still continues normal runtime execution. + +## When To Use + +Use this when you need to: + +- Capture resolved non-secret runtime env values in CI. +- Upload a runtime evidence artifact before release. +- Inspect which config paths were used by the container. +- Confirm which env names were intentionally redacted. + +## Contract + +The JSON output contains: + +- `generated_at`: UTC timestamp for the emitted contract. +- `paths.worker_config`: `worker.yaml` path selected by the entrypoint. +- `paths.services_config`: `services.yaml` path selected by the entrypoint. +- `paths.environment`: generated worker environment file path. +- `env`: resolved non-secret environment variables. +- `redacted`: environment variable names omitted because they are configured as secrets or secret references. + +Secrets and secret references are not printed in `env`. Their names appear in `redacted` instead. + +## Examples + +### Runtime Contract With Normal Processes + +```bash +docker run \ + -e WORKER_RUNTIME_OUTPUT=true \ + usabilitydynamics/udx-worker:latest > runtime-output/runtime.json +``` + +### Runtime Contract Only + +Use a short command when the workflow only needs the contract and should exit after capture. + +```bash +docker run --rm \ + -e WORKER_RUNTIME_OUTPUT=true \ + usabilitydynamics/udx-worker:latest \ + true > runtime-output/runtime.json +``` + +### Validate The Artifact + +```bash +jq -e '.env | type == "object"' runtime-output/runtime.json +jq -e '.redacted | type == "array"' runtime-output/runtime.json +``` + +Workflow-specific outputs such as `$GITHUB_OUTPUT`, `$GITHUB_STEP_SUMMARY`, uploaded artifacts, or platform annotations should be generated by the workflow from this JSON file. + +## Related Docs + +- `docs/config.md` +- `docs/deployment.md` +- `docs/secrets.md` diff --git a/docs/secrets.md b/docs/secrets.md index 8f9f8844..c3ff25d6 100644 --- a/docs/secrets.md +++ b/docs/secrets.md @@ -87,4 +87,5 @@ worker env resolve gcp/my-project/api-key - `docs/config.md` - `docs/services.md` +- `docs/runtime-output.md` - `docs/references/cloud-providers-auth.md` diff --git a/lib/env_handler.sh b/lib/env_handler.sh index 1ad6d663..833c03f1 100644 --- a/lib/env_handler.sh +++ b/lib/env_handler.sh @@ -5,6 +5,7 @@ source "${WORKER_LIB_DIR}/utils.sh" # Environment file location WORKER_ENV_FILE="${WORKER_ENV_FILE:-/etc/worker/environment}" +WORKER_ENV_REDACTION_FILE="${WORKER_ENV_REDACTION_FILE:-${WORKER_ENV_FILE}.redacted}" ensure_env_file() { local env_dir @@ -26,6 +27,43 @@ ensure_env_file() { } } +reset_env_redactions() { + local redaction_dir + redaction_dir=$(dirname "$WORKER_ENV_REDACTION_FILE") + + mkdir -p "$redaction_dir" || { + log_error "Environment" "Failed to create redaction directory: $redaction_dir" + return 1 + } + + install -m 600 /dev/null "$WORKER_ENV_REDACTION_FILE" || { + log_error "Environment" "Failed to initialize redaction file: $WORKER_ENV_REDACTION_FILE" + return 1 + } +} + +mark_env_value_redacted() { + local name="$1" + + if [[ -z "$name" ]]; then + log_error "Environment" "Variable name not provided for redaction" + return 1 + fi + + if ! [[ "$name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then + log_error "Environment" "Invalid redaction variable name: $name" + return 1 + fi + + if [[ ! -f "$WORKER_ENV_REDACTION_FILE" ]]; then + reset_env_redactions || return 1 + fi + + if ! grep -Fxq "$name" "$WORKER_ENV_REDACTION_FILE"; then + printf '%s\n' "$name" >> "$WORKER_ENV_REDACTION_FILE" + fi +} + upsert_env_value() { local name="$1" local value="$2" @@ -76,6 +114,7 @@ generate_env_file() { log_info "Environment" "Loading environment variables from configuration" ensure_env_file || return 1 + reset_env_redactions || return 1 while IFS= read -r entry; do local key value @@ -134,6 +173,7 @@ _resolve_and_append_secrets() { has_failures=true else upsert_env_value "$name" "$value" || has_failures=true + mark_env_value_redacted "$name" || has_failures=true log_success "Environment" "Resolved secret for $name" fi done < <(echo "$secrets_json" | jq -c 'to_entries[]') diff --git a/lib/runtime_output.sh b/lib/runtime_output.sh index 736dc2d4..d3c8ac7c 100644 --- a/lib/runtime_output.sh +++ b/lib/runtime_output.sh @@ -5,9 +5,12 @@ source "${WORKER_LIB_DIR}/utils.sh" # shellcheck source=${WORKER_LIB_DIR}/worker_config.sh disable=SC1091 source "${WORKER_LIB_DIR}/worker_config.sh" +WORKER_ENV_FILE="${WORKER_ENV_FILE:-/etc/worker/environment}" +WORKER_ENV_REDACTION_FILE="${WORKER_ENV_REDACTION_FILE:-${WORKER_ENV_FILE}.redacted}" + build_runtime_output_json() { local config_json="$1" - local worker_config_path services_config_path env_json secrets_json + local worker_config_path services_config_path env_json redacted_json worker_config_path=$(get_worker_config_path) services_config_path="${HOME}/.config/worker/services.yaml" @@ -15,8 +18,8 @@ build_runtime_output_json() { services_config_path="${WORKER_CONFIG_DIR}/services.yaml" fi - env_json=$(echo "$config_json" | jq '.config.env // {} | with_entries(.value = "redacted")' 2>/dev/null) || return 1 - secrets_json=$(echo "$config_json" | jq '.config.secrets // {} | with_entries(.value = "redacted")' 2>/dev/null) || return 1 + env_json=$(build_runtime_env_json "$config_json") || return 1 + redacted_json=$(build_runtime_redacted_json "$config_json") || return 1 jq -n \ --arg worker_config_path "$worker_config_path" \ @@ -24,7 +27,7 @@ build_runtime_output_json() { --arg worker_env_file "$WORKER_ENV_FILE" \ --arg generated_at "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ --argjson env "$env_json" \ - --argjson secrets "$secrets_json" \ + --argjson redacted "$redacted_json" \ '{ generated_at: $generated_at, paths: { @@ -33,16 +36,104 @@ build_runtime_output_json() { environment: $worker_env_file }, env: $env, - secrets: $secrets, - secret_values: "redacted" + redacted: $redacted }' } +is_runtime_output_redacted_name() { + local config_json="$1" + local name="$2" + + if [[ -f "$WORKER_ENV_REDACTION_FILE" ]] && grep -Fxq "$name" "$WORKER_ENV_REDACTION_FILE"; then + return 0 + fi + + echo "$config_json" | jq -e --arg name "$name" --arg pattern "^(${SUPPORTED_SECRET_PROVIDERS})/.+/.+" ' + (.config.secrets // {} | has($name)) or + ((.config.env // {} | .[$name] // "" | tostring) | test($pattern)) + ' >/dev/null +} + +build_runtime_env_json() { + local config_json="$1" + local names name value json + + if [[ ! -f "$WORKER_ENV_FILE" ]]; then + log_error "Runtime output" "Environment file not found: $WORKER_ENV_FILE" + return 1 + fi + + names=$(grep "^export " "$WORKER_ENV_FILE" | cut -d'=' -f1 | cut -d' ' -f2) + json="{}" + while IFS= read -r name; do + if [[ -z "$name" ]] || is_runtime_output_redacted_name "$config_json" "$name"; then + continue + fi + + value=$(get_env_value "$name") || return 1 + json=$(echo "$json" | jq --arg key "$name" --arg value "$value" '. + {($key): $value}') || return 1 + done <<< "$names" + + echo "$json" | jq -S . +} + +build_runtime_redacted_json() { + local config_json="$1" + local names name json + + json=$(echo "$config_json" | jq -c --arg pattern "^(${SUPPORTED_SECRET_PROVIDERS})/.+/.+" ' + [ + (.config.secrets // {} | keys[]?), + (.config.env // {} | to_entries[]? | select(((.value // "") | tostring) | test($pattern)) | .key) + ] + ') || return 1 + + if [[ -f "$WORKER_ENV_FILE" ]]; then + names=$(grep "^export " "$WORKER_ENV_FILE" | cut -d'=' -f1 | cut -d' ' -f2) + while IFS= read -r name; do + if [[ -n "$name" ]] && is_runtime_output_redacted_name "$config_json" "$name"; then + json=$(echo "$json" | jq --arg name "$name" '. + [$name]') || return 1 + fi + done <<< "$names" + fi + + if [[ -f "$WORKER_ENV_REDACTION_FILE" ]]; then + while IFS= read -r name; do + if [[ -n "$name" ]]; then + json=$(echo "$json" | jq --arg name "$name" '. + [$name]') || return 1 + fi + done < "$WORKER_ENV_REDACTION_FILE" + fi + + echo "$json" | jq -S 'unique' +} + +runtime_output_enabled() { + case "${WORKER_RUNTIME_OUTPUT:-false}" in + true|TRUE|1|yes|YES|on|ON) + return 0 + ;; + *) + return 1 + ;; + esac +} + +emit_runtime_output_stdout() { + local runtime_json="$1" + + if [[ -n "${WORKER_RUNTIME_OUTPUT_FD:-}" ]]; then + printf '%s\n' "$runtime_json" >&"${WORKER_RUNTIME_OUTPUT_FD}" + else + printf '%s\n' "$runtime_json" + fi +} + emit_runtime_output() { local config_json runtime_json - if [[ -z "${WORKER_OUTPUT_FILE:-}" ]]; then - log_info "Runtime output disabled. Set WORKER_OUTPUT_FILE to write redacted JSON runtime config for workflow/deployment integrations." + if ! runtime_output_enabled; then + log_info "Runtime output disabled. Set WORKER_RUNTIME_OUTPUT=true to emit redacted JSON runtime config for workflow/deployment integrations." return 0 fi @@ -52,8 +143,5 @@ emit_runtime_output() { return 1 fi - mkdir -p "$(dirname "$WORKER_OUTPUT_FILE")" || return 1 - install -m 600 /dev/null "$WORKER_OUTPUT_FILE" || return 1 - printf '%s\n' "$runtime_json" > "$WORKER_OUTPUT_FILE" - log_info "Runtime output written to $WORKER_OUTPUT_FILE" + emit_runtime_output_stdout "$runtime_json" || return 1 } diff --git a/src/configs/worker.yaml b/src/configs/worker.yaml index 9358ed77..b44440a0 100644 --- a/src/configs/worker.yaml +++ b/src/configs/worker.yaml @@ -3,5 +3,5 @@ kind: workerConfig version: udx.io/worker-v1/config config: env: - WORKER_OUTPUT_FILE: "" + WORKER_RUNTIME_OUTPUT: "false" secrets: {} diff --git a/test/modules/25_runtime_output.sh b/test/modules/25_runtime_output.sh new file mode 100755 index 00000000..ee33108b --- /dev/null +++ b/test/modules/25_runtime_output.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Source test helpers +# shellcheck source=../test_helpers.sh disable=SC1091 +source "/home/udx/test/test_helpers.sh" + +print_header "Runtime Output Tests" + +# shellcheck source=/home/udx/lib/runtime_output.sh disable=SC1091 +source "${WORKER_LIB_DIR}/runtime_output.sh" + +print_info "Testing: runtime output redacts configured secrets" +RUNTIME_ENV_FILE=$(mktemp) +ORIGINAL_WORKER_ENV_FILE="$WORKER_ENV_FILE" +ORIGINAL_WORKER_ENV_REDACTION_FILE="${WORKER_ENV_REDACTION_FILE:-}" +export WORKER_ENV_FILE="$RUNTIME_ENV_FILE" +export WORKER_ENV_REDACTION_FILE="${RUNTIME_ENV_FILE}.redacted" + +{ + printf 'export PUBLIC_VALUE=%q\n' "visible value" + printf 'export CONFIG_SECRET=%q\n' "resolved secret" + printf 'export CONFIG_REF=%q\n' "resolved reference" + printf 'export DEPLOYMENT_SECRET=%q\n' "resolved deployment secret" +} > "$WORKER_ENV_FILE" +reset_env_redactions +mark_env_value_redacted "DEPLOYMENT_SECRET" +upsert_env_value "DEPLOYMENT_SECRET_TWO" "resolved deployment secret two" +mark_env_value_redacted "DEPLOYMENT_SECRET_TWO" + +CONFIG_JSON='{ + "config": { + "env": { + "PUBLIC_VALUE": "visible value", + "CONFIG_REF": "gcp/project-id/secret-name" + }, + "secrets": { + "CONFIG_SECRET": "aws/secret-name/us-west-2", + "CONFIG_ONLY_SECRET": "aws/config-only/us-west-2" + } + } +}' + +RUNTIME_OUTPUT=$(build_runtime_output_json "$CONFIG_JSON") +export WORKER_ENV_FILE="$ORIGINAL_WORKER_ENV_FILE" +if [[ -n "$ORIGINAL_WORKER_ENV_REDACTION_FILE" ]]; then + export WORKER_ENV_REDACTION_FILE="$ORIGINAL_WORKER_ENV_REDACTION_FILE" +else + unset WORKER_ENV_REDACTION_FILE +fi +rm -f "$RUNTIME_ENV_FILE" "${RUNTIME_ENV_FILE}.redacted" + +if ! echo "$RUNTIME_OUTPUT" | jq -e '.env.PUBLIC_VALUE == "visible value"' >/dev/null; then + print_error "runtime output missing non-secret env value" + exit 1 +fi + +if echo "$RUNTIME_OUTPUT" | jq -e '.env.CONFIG_SECRET or .env.CONFIG_REF or .env.DEPLOYMENT_SECRET or .env.DEPLOYMENT_SECRET_TWO' >/dev/null; then + print_error "runtime output leaked a redacted env value" + exit 1 +fi + +if ! echo "$RUNTIME_OUTPUT" | jq -e '.redacted == ["CONFIG_ONLY_SECRET", "CONFIG_REF", "CONFIG_SECRET", "DEPLOYMENT_SECRET", "DEPLOYMENT_SECRET_TWO"]' >/dev/null; then + print_error "runtime output redacted list is incorrect" + exit 1 +fi + +STDOUT_OUTPUT=$(WORKER_RUNTIME_OUTPUT=true emit_runtime_output_stdout "$RUNTIME_OUTPUT") +if ! echo "$STDOUT_OUTPUT" | jq -e '.env.PUBLIC_VALUE == "visible value"' >/dev/null; then + print_error "runtime output JSON is invalid" + exit 1 +fi + +print_success "All runtime output tests passed"