From c1d341f91c6b3b4d8c434fce6429e0f43e56c076 Mon Sep 17 00:00:00 2001 From: fqjony <12067297+fqjony@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:12:29 +0000 Subject: [PATCH 01/16] chore(deps): update Docker dependency pins --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e9c4266b..2e41b1c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,7 +53,7 @@ RUN apt-get update && \ jq=1.8.1-3ubuntu1.1 \ zip=3.0-15ubuntu2 \ unzip=6.0-28ubuntu7 \ - nano=8.4-1 \ + nano=8.4-1ubuntu0.1 \ vim=2:9.1.0967-1ubuntu6.5 \ python3.13=3.13.7-1ubuntu0.4 \ python3.13-venv=3.13.7-1ubuntu0.4 \ From 46ba15336f94dbb83c186f5ef4ff1e6b2ba8d4bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:22:57 +0000 Subject: [PATCH 02/16] Bump actions/github-script from 8 to 9 in /.github/workflows Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v8...v9) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-dependency-updater.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-dependency-updater.yml b/.github/workflows/docker-dependency-updater.yml index 006465ff..e02aaf54 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: From 3d362b70b344d09f19a3ba4f36206757a3a6215f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:23:01 +0000 Subject: [PATCH 03/16] Bump actions/upload-artifact from 5 to 7 in /.github/workflows Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-dependency-updater.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-dependency-updater.yml b/.github/workflows/docker-dependency-updater.yml index 006465ff..adc8ffc6 100644 --- a/.github/workflows/docker-dependency-updater.yml +++ b/.github/workflows/docker-dependency-updater.yml @@ -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 From 12bbb0108f7736e9a84d9eb05f87a82aa2522acb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:23:07 +0000 Subject: [PATCH 04/16] Bump peter-evans/create-pull-request in /.github/workflows Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.11 to 8.1.1. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/22a9089034f40e5a961c8808d113e2c98fb63676...5f6978faf089d4d20b00c7766989d076bb2fc7f1) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.1.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-dependency-updater.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-dependency-updater.yml b/.github/workflows/docker-dependency-updater.yml index 006465ff..933312d8 100644 --- a/.github/workflows/docker-dependency-updater.yml +++ b/.github/workflows/docker-dependency-updater.yml @@ -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 }} From b9601d7cf668516605923e24200825ab96baed24 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Mon, 15 Jun 2026 13:56:08 +0300 Subject: [PATCH 05/16] fix: stabilize worker image build --- Dockerfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index e9c4266b..6129290f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ RUN apt-get update && \ zip=3.0-15ubuntu2 \ unzip=6.0-28ubuntu7 \ nano=8.4-1 \ - vim=2:9.1.0967-1ubuntu6.5 \ + 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) From 7b471fdf46f0f0a6e7c6c78851851792e49355f3 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Mon, 15 Jun 2026 13:56:17 +0300 Subject: [PATCH 06/16] feat: emit worker runtime output --- docs/config.md | 12 +++- lib/runtime_output.sh | 101 ++++++++++++++++++++++++++---- src/configs/worker.yaml | 1 + test/modules/25_runtime_output.sh | 63 +++++++++++++++++++ 4 files changed, 163 insertions(+), 14 deletions(-) create mode 100755 test/modules/25_runtime_output.sh diff --git a/docs/config.md b/docs/config.md index 472d8205..d5e3b8dc 100644 --- a/docs/config.md +++ b/docs/config.md @@ -83,9 +83,17 @@ docker run --rm \ 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. -Set `WORKER_OUTPUT_FILE` when a deployment or workflow needs runtime config evidence. The worker writes redacted JSON runtime metadata to that path. +Set `WORKER_OUTPUT_FILE` when a deployment or workflow needs runtime config evidence. The worker writes JSON runtime metadata to that path after `worker.yaml`, deployment environment overrides, and secret references have been applied. -Workflow-specific outputs such as `$GITHUB_OUTPUT`, `$GITHUB_STEP_SUMMARY`, or platform annotations should be generated by the workflow from the JSON file. +Set `WORKER_OUTPUT_LOG=true` to also emit the same JSON to container logs as a single minified line prefixed with `WORKER_RUNTIME_OUTPUT_JSON=`. This is useful for Kubernetes or workflow systems where the next step reads container logs instead of a mounted file. + +The output contains: + +- `env`: resolved non-secret environment variables. +- `redacted`: environment variable names that were intentionally omitted because they are configured as secrets or secret references. +- `paths`: the config and environment files used to build the output. + +Workflow-specific outputs such as `$GITHUB_OUTPUT`, `$GITHUB_STEP_SUMMARY`, uploaded artifacts, or platform annotations should be generated by the workflow from the JSON file or the structured log line. ## Related Docs diff --git a/lib/runtime_output.sh b/lib/runtime_output.sh index 736dc2d4..c2a90c23 100644 --- a/lib/runtime_output.sh +++ b/lib/runtime_output.sh @@ -7,7 +7,7 @@ source "${WORKER_LIB_DIR}/worker_config.sh" 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 +15,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 +24,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 +33,87 @@ 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" + + 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 + + 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 [[ -n "$name" ]] && is_runtime_output_redacted_name "$config_json" "$name"; then + json=$(echo "$json" | jq --arg name "$name" '. + [$name]') || return 1 + fi + done <<< "$names" + + echo "$json" | jq -S 'unique' +} + +runtime_output_log_enabled() { + case "${WORKER_OUTPUT_LOG:-false}" in + true|TRUE|1|yes|YES|on|ON) + return 0 + ;; + *) + return 1 + ;; + esac +} + +emit_runtime_output_log() { + local runtime_json="$1" + local compact_json + + compact_json=$(echo "$runtime_json" | jq -c .) || return 1 + printf 'WORKER_RUNTIME_OUTPUT_JSON=%s\n' "$compact_json" +} + 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 [[ -z "${WORKER_OUTPUT_FILE:-}" ]] && ! runtime_output_log_enabled; then + log_info "Runtime output disabled. Set WORKER_OUTPUT_FILE or WORKER_OUTPUT_LOG=true to emit redacted JSON runtime config for workflow/deployment integrations." return 0 fi @@ -52,8 +123,14 @@ 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" + if [[ -n "${WORKER_OUTPUT_FILE:-}" ]]; then + 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" + fi + + if runtime_output_log_enabled; then + emit_runtime_output_log "$runtime_json" || return 1 + fi } diff --git a/src/configs/worker.yaml b/src/configs/worker.yaml index 9358ed77..aabef6ed 100644 --- a/src/configs/worker.yaml +++ b/src/configs/worker.yaml @@ -4,4 +4,5 @@ version: udx.io/worker-v1/config config: env: WORKER_OUTPUT_FILE: "" + WORKER_OUTPUT_LOG: "false" secrets: {} diff --git a/test/modules/25_runtime_output.sh b/test/modules/25_runtime_output.sh new file mode 100755 index 00000000..a92a79f3 --- /dev/null +++ b/test/modules/25_runtime_output.sh @@ -0,0 +1,63 @@ +#!/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" +export WORKER_ENV_FILE="$RUNTIME_ENV_FILE" + +printf 'export PUBLIC_VALUE=%q\n' "visible value" > "$WORKER_ENV_FILE" +printf 'export CONFIG_SECRET=%q\n' "resolved secret" >> "$WORKER_ENV_FILE" +printf 'export CONFIG_REF=%q\n' "resolved reference" >> "$WORKER_ENV_FILE" + +CONFIG_JSON='{ + "config": { + "env": { + "PUBLIC_VALUE": "visible value", + "CONFIG_REF": "gcp/project-id/secret-name" + }, + "secrets": { + "CONFIG_SECRET": "aws/secret-name/us-west-2" + } + } +}' + +RUNTIME_OUTPUT=$(build_runtime_output_json "$CONFIG_JSON") +export WORKER_ENV_FILE="$ORIGINAL_WORKER_ENV_FILE" +rm -f "$RUNTIME_ENV_FILE" + +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' >/dev/null; then + print_error "runtime output leaked a redacted env value" + exit 1 +fi + +if ! echo "$RUNTIME_OUTPUT" | jq -e '.redacted == ["CONFIG_REF", "CONFIG_SECRET"]' >/dev/null; then + print_error "runtime output redacted list is incorrect" + exit 1 +fi + +LOG_LINE=$(WORKER_OUTPUT_LOG=true emit_runtime_output_log "$RUNTIME_OUTPUT") +if [[ "$LOG_LINE" != WORKER_RUNTIME_OUTPUT_JSON=* ]]; then + print_error "runtime output log marker is missing" + exit 1 +fi + +if ! echo "${LOG_LINE#WORKER_RUNTIME_OUTPUT_JSON=}" | jq -e '.env.PUBLIC_VALUE == "visible value"' >/dev/null; then + print_error "runtime output log JSON is invalid" + exit 1 +fi + +print_success "All runtime output tests passed" From 33d9bf2b4d96e74b5eb1009a74fb4c7e942c932d Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Mon, 15 Jun 2026 14:23:42 +0300 Subject: [PATCH 07/16] ci: publish worker runtime output artifact --- .github/workflows/docker-ops.yml | 44 ++++++++++++++++++++++++++++++++ .rabbit/context.yaml | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-ops.yml b/.github/workflows/docker-ops.yml index 7fa23b49..1f9497b7 100644 --- a/.github/workflows/docker-ops.yml +++ b/.github/workflows/docker-ops.yml @@ -35,3 +35,47 @@ 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: docker build --pull --tag "worker-runtime-output:${GITHUB_SHA}" . + + - name: Capture runtime output + run: | + set -euo pipefail + mkdir -p runtime-output + sudo chown 500:500 runtime-output + docker run --rm \ + -e WORKER_OUTPUT_FILE=/tmp/worker-runtime-output/runtime.json \ + -e WORKER_OUTPUT_LOG=true \ + -v "${PWD}/runtime-output:/tmp/worker-runtime-output" \ + "worker-runtime-output:${GITHUB_SHA}" \ + /bin/bash -lc 'test -s "$WORKER_OUTPUT_FILE"' + sudo chown "$(id -u):$(id -g)" 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@v4 + 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 From b13937b0524228d2001609606df538a0d7fe348b Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Wed, 17 Jun 2026 14:14:42 +0300 Subject: [PATCH 08/16] fix: redact deployment env secrets from runtime output --- docs/config.md | 2 +- lib/env_handler.sh | 40 +++++++++++++++++++++++++++++++ lib/runtime_output.sh | 7 ++++++ test/modules/25_runtime_output.sh | 15 +++++++++--- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/docs/config.md b/docs/config.md index d5e3b8dc..ebbd68d3 100644 --- a/docs/config.md +++ b/docs/config.md @@ -83,7 +83,7 @@ docker run --rm \ 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. -Set `WORKER_OUTPUT_FILE` when a deployment or workflow needs runtime config evidence. The worker writes JSON runtime metadata to that path after `worker.yaml`, deployment environment overrides, and secret references have been applied. +Set `WORKER_OUTPUT_FILE` when a deployment or workflow needs redacted runtime config evidence. The worker writes JSON runtime metadata to that path after `worker.yaml`, deployment environment overrides, and secret references have been applied. Set `WORKER_OUTPUT_LOG=true` to also emit the same JSON to container logs as a single minified line prefixed with `WORKER_RUNTIME_OUTPUT_JSON=`. This is useful for Kubernetes or workflow systems where the next step reads container logs instead of a mounted file. diff --git a/lib/env_handler.sh b/lib/env_handler.sh index 1ad6d663..748493b7 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" @@ -41,6 +79,7 @@ upsert_env_value() { fi ensure_env_file || return 1 + reset_env_redactions || return 1 local tmpfile tmpfile=$(mktemp "${WORKER_ENV_FILE}.tmp.XXXXXX") || { @@ -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 c2a90c23..15163924 100644 --- a/lib/runtime_output.sh +++ b/lib/runtime_output.sh @@ -5,6 +5,9 @@ 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 redacted_json @@ -41,6 +44,10 @@ 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)) diff --git a/test/modules/25_runtime_output.sh b/test/modules/25_runtime_output.sh index a92a79f3..58a92894 100755 --- a/test/modules/25_runtime_output.sh +++ b/test/modules/25_runtime_output.sh @@ -12,11 +12,15 @@ 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" > "$WORKER_ENV_FILE" printf 'export CONFIG_SECRET=%q\n' "resolved secret" >> "$WORKER_ENV_FILE" printf 'export CONFIG_REF=%q\n' "resolved reference" >> "$WORKER_ENV_FILE" +printf 'export DEPLOYMENT_SECRET=%q\n' "resolved deployment secret" >> "$WORKER_ENV_FILE" +printf '%s\n' "DEPLOYMENT_SECRET" > "$WORKER_ENV_REDACTION_FILE" CONFIG_JSON='{ "config": { @@ -32,19 +36,24 @@ CONFIG_JSON='{ RUNTIME_OUTPUT=$(build_runtime_output_json "$CONFIG_JSON") export WORKER_ENV_FILE="$ORIGINAL_WORKER_ENV_FILE" -rm -f "$RUNTIME_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' >/dev/null; then +if echo "$RUNTIME_OUTPUT" | jq -e '.env.CONFIG_SECRET or .env.CONFIG_REF or .env.DEPLOYMENT_SECRET' >/dev/null; then print_error "runtime output leaked a redacted env value" exit 1 fi -if ! echo "$RUNTIME_OUTPUT" | jq -e '.redacted == ["CONFIG_REF", "CONFIG_SECRET"]' >/dev/null; then +if ! echo "$RUNTIME_OUTPUT" | jq -e '.redacted == ["CONFIG_REF", "CONFIG_SECRET", "DEPLOYMENT_SECRET"]' >/dev/null; then print_error "runtime output redacted list is incorrect" exit 1 fi From 9d62f88c6c111cca6a8d36447a1fe753d34d13d7 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Wed, 17 Jun 2026 14:16:38 +0300 Subject: [PATCH 09/16] test: satisfy runtime output shellcheck --- test/modules/25_runtime_output.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/modules/25_runtime_output.sh b/test/modules/25_runtime_output.sh index 58a92894..8b525561 100755 --- a/test/modules/25_runtime_output.sh +++ b/test/modules/25_runtime_output.sh @@ -16,10 +16,12 @@ 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" > "$WORKER_ENV_FILE" -printf 'export CONFIG_SECRET=%q\n' "resolved secret" >> "$WORKER_ENV_FILE" -printf 'export CONFIG_REF=%q\n' "resolved reference" >> "$WORKER_ENV_FILE" -printf 'export DEPLOYMENT_SECRET=%q\n' "resolved deployment secret" >> "$WORKER_ENV_FILE" +{ + 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" printf '%s\n' "DEPLOYMENT_SECRET" > "$WORKER_ENV_REDACTION_FILE" CONFIG_JSON='{ From aa9f21a4be1c2ac135dfb780f224f113b9157f67 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Wed, 17 Jun 2026 14:27:25 +0300 Subject: [PATCH 10/16] fix: preserve runtime redaction markers --- lib/env_handler.sh | 2 +- test/modules/25_runtime_output.sh | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/env_handler.sh b/lib/env_handler.sh index 748493b7..833c03f1 100644 --- a/lib/env_handler.sh +++ b/lib/env_handler.sh @@ -79,7 +79,6 @@ upsert_env_value() { fi ensure_env_file || return 1 - reset_env_redactions || return 1 local tmpfile tmpfile=$(mktemp "${WORKER_ENV_FILE}.tmp.XXXXXX") || { @@ -115,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 diff --git a/test/modules/25_runtime_output.sh b/test/modules/25_runtime_output.sh index 8b525561..85019015 100755 --- a/test/modules/25_runtime_output.sh +++ b/test/modules/25_runtime_output.sh @@ -22,7 +22,10 @@ export WORKER_ENV_REDACTION_FILE="${RUNTIME_ENV_FILE}.redacted" printf 'export CONFIG_REF=%q\n' "resolved reference" printf 'export DEPLOYMENT_SECRET=%q\n' "resolved deployment secret" } > "$WORKER_ENV_FILE" -printf '%s\n' "DEPLOYMENT_SECRET" > "$WORKER_ENV_REDACTION_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": { @@ -50,12 +53,12 @@ if ! echo "$RUNTIME_OUTPUT" | jq -e '.env.PUBLIC_VALUE == "visible value"' >/dev exit 1 fi -if echo "$RUNTIME_OUTPUT" | jq -e '.env.CONFIG_SECRET or .env.CONFIG_REF or .env.DEPLOYMENT_SECRET' >/dev/null; then +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_REF", "CONFIG_SECRET", "DEPLOYMENT_SECRET"]' >/dev/null; then +if ! echo "$RUNTIME_OUTPUT" | jq -e '.redacted == ["CONFIG_REF", "CONFIG_SECRET", "DEPLOYMENT_SECRET", "DEPLOYMENT_SECRET_TWO"]' >/dev/null; then print_error "runtime output redacted list is incorrect" exit 1 fi From 20ed30dd2fc0dfe62fbc04bdbf4947f5e3145072 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Wed, 17 Jun 2026 15:29:30 +0300 Subject: [PATCH 11/16] ci: route runtime output validation through make test --- .github/workflows/docker-ops.yml | 20 +++++++------------- Makefile | 18 ++++++++++++++++-- Makefile.help | 5 ++++- Makefile.variables | 7 ++++++- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/.github/workflows/docker-ops.yml b/.github/workflows/docker-ops.yml index 6b0a5c4e..a8cfdf12 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: @@ -47,20 +47,14 @@ jobs: uses: actions/checkout@v6 - name: Build worker image - run: docker build --pull --tag "worker-runtime-output:${GITHUB_SHA}" . + run: make build IMAGE_NAME=worker-runtime-output TAG="${GITHUB_SHA}" - - name: Capture runtime output + - name: Run tests with runtime output run: | - set -euo pipefail - mkdir -p runtime-output - sudo chown 500:500 runtime-output - docker run --rm \ - -e WORKER_OUTPUT_FILE=/tmp/worker-runtime-output/runtime.json \ - -e WORKER_OUTPUT_LOG=true \ - -v "${PWD}/runtime-output:/tmp/worker-runtime-output" \ - "worker-runtime-output:${GITHUB_SHA}" \ - /bin/bash -lc 'test -s "$WORKER_OUTPUT_FILE"' - sudo chown "$(id -u):$(id -g)" runtime-output/runtime.json + make test \ + IMAGE_NAME=worker-runtime-output \ + TAG="${GITHUB_SHA}" \ + TEST_RUNTIME_OUTPUT=true jq -e '.env | type == "object"' runtime-output/runtime.json jq -c . runtime-output/runtime.json diff --git a/Makefile b/Makefile index 9dbb97c7..114d2874 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 \ @@ -101,14 +102,27 @@ clean: test: clean @printf "$(COLOR_BLUE)$(SYM_ARROW) Running tests...$(COLOR_RESET)\n" + @if [ "$(TEST_RUNTIME_OUTPUT)" = "true" ]; then \ + mkdir -p "$(TEST_RUNTIME_OUTPUT_DIR)"; \ + fi @$(MAKE) run \ + RUN_ENV="$(if $(filter true,$(TEST_RUNTIME_OUTPUT)),-e WORKER_OUTPUT_LOG=$(TEST_RUNTIME_OUTPUT_LOG))" \ VOLUMES="$(PWD)/test:/home/udx/test $(PWD)/src/examples/simple-config/.config/worker/worker.yaml:/home/udx/.config/worker/worker.yaml $(PWD)/src/examples/simple-service/.config/worker/services.yaml:/home/udx/.config/worker/services.yaml" \ 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=$$?; \ + @if [ "$(TEST_RUNTIME_OUTPUT)" = "true" ]; then \ + docker logs -f $(CONTAINER_NAME) | tee "$(TEST_RUNTIME_OUTPUT_DIR)/container.log" & LOGS_PID=$$!; \ + else \ + docker logs -f $(CONTAINER_NAME) & LOGS_PID=$$!; \ + fi; \ + EXIT_CODE=$$(docker wait $(CONTAINER_NAME)); \ kill $$LOGS_PID 2>/dev/null || true; \ + wait $$LOGS_PID 2>/dev/null || true; \ exit $$EXIT_CODE + @if [ "$(TEST_RUNTIME_OUTPUT)" = "true" ]; then \ + sed -n 's/^.*WORKER_RUNTIME_OUTPUT_JSON=//p' "$(TEST_RUNTIME_OUTPUT_DIR)/container.log" | tail -n 1 > "$(TEST_RUNTIME_OUTPUT_DIR)/$(TEST_RUNTIME_OUTPUT_FILE)"; \ + test -s "$(TEST_RUNTIME_OUTPUT_DIR)/$(TEST_RUNTIME_OUTPUT_FILE)"; \ + fi @$(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..57f2fd60 100644 --- a/Makefile.help +++ b/Makefile.help @@ -48,4 +48,7 @@ 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))" + @echo " TEST_RUNTIME_OUTPUT (default: $(TEST_RUNTIME_OUTPUT))" + @echo " TEST_RUNTIME_OUTPUT_DIR (default: $(TEST_RUNTIME_OUTPUT_DIR))" diff --git a/Makefile.variables b/Makefile.variables index c4853a5f..466f40dd 100644 --- a/Makefile.variables +++ b/Makefile.variables @@ -14,6 +14,11 @@ DEBUG ?= false COMMAND ?= MULTIPLATFORM ?= false FOLLOW_LOGS ?= false +RUN_ENV ?= +TEST_RUNTIME_OUTPUT ?= false +TEST_RUNTIME_OUTPUT_DIR ?= $(PWD)/runtime-output +TEST_RUNTIME_OUTPUT_FILE ?= runtime.json +TEST_RUNTIME_OUTPUT_LOG ?= true # Colors for better visibility COLOR_RESET=\033[0m @@ -26,4 +31,4 @@ COLOR_YELLOW=\033[33m SYM_ARROW=➜ SYM_SUCCESS=✔ SYM_ERROR=✖ -SYM_WARNING=⚠ \ No newline at end of file +SYM_WARNING=⚠ From 25fffd77f49bcc25d05139c3b03bf515906d7f52 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Wed, 17 Jun 2026 16:15:00 +0300 Subject: [PATCH 12/16] ci: capture runtime output from stdout --- .github/workflows/docker-ops.yml | 11 ++++++----- Makefile | 14 +------------- Makefile.help | 2 -- Makefile.variables | 4 ---- bin/entrypoint.sh | 21 ++++++++++++++++----- docs/config.md | 8 ++++++++ lib/runtime_output.sh | 29 +++++++++++++++++++++++++++-- src/configs/worker.yaml | 1 + test/modules/25_runtime_output.sh | 11 +++++++++++ 9 files changed, 70 insertions(+), 31 deletions(-) diff --git a/.github/workflows/docker-ops.yml b/.github/workflows/docker-ops.yml index a8cfdf12..85f14ac8 100644 --- a/.github/workflows/docker-ops.yml +++ b/.github/workflows/docker-ops.yml @@ -49,12 +49,13 @@ jobs: - name: Build worker image run: make build IMAGE_NAME=worker-runtime-output TAG="${GITHUB_SHA}" - - name: Run tests with runtime output + - name: Capture runtime output run: | - make test \ - IMAGE_NAME=worker-runtime-output \ - TAG="${GITHUB_SHA}" \ - TEST_RUNTIME_OUTPUT=true + mkdir -p runtime-output + docker run --rm \ + -e WORKER_OUTPUT_STDOUT=true \ + "worker-runtime-output:${GITHUB_SHA}" \ + > runtime-output/runtime.json jq -e '.env | type == "object"' runtime-output/runtime.json jq -c . runtime-output/runtime.json diff --git a/Makefile b/Makefile index 114d2874..95d1b06a 100644 --- a/Makefile +++ b/Makefile @@ -102,27 +102,15 @@ clean: test: clean @printf "$(COLOR_BLUE)$(SYM_ARROW) Running tests...$(COLOR_RESET)\n" - @if [ "$(TEST_RUNTIME_OUTPUT)" = "true" ]; then \ - mkdir -p "$(TEST_RUNTIME_OUTPUT_DIR)"; \ - fi @$(MAKE) run \ - RUN_ENV="$(if $(filter true,$(TEST_RUNTIME_OUTPUT)),-e WORKER_OUTPUT_LOG=$(TEST_RUNTIME_OUTPUT_LOG))" \ VOLUMES="$(PWD)/test:/home/udx/test $(PWD)/src/examples/simple-config/.config/worker/worker.yaml:/home/udx/.config/worker/worker.yaml $(PWD)/src/examples/simple-service/.config/worker/services.yaml:/home/udx/.config/worker/services.yaml" \ COMMAND="/home/udx/test/main.sh" @printf "$(COLOR_BLUE)$(SYM_ARROW) Following test output...$(COLOR_RESET)\n" - @if [ "$(TEST_RUNTIME_OUTPUT)" = "true" ]; then \ - docker logs -f $(CONTAINER_NAME) | tee "$(TEST_RUNTIME_OUTPUT_DIR)/container.log" & LOGS_PID=$$!; \ - else \ - docker logs -f $(CONTAINER_NAME) & LOGS_PID=$$!; \ - fi; \ + @docker logs -f $(CONTAINER_NAME) & LOGS_PID=$$!; \ EXIT_CODE=$$(docker wait $(CONTAINER_NAME)); \ kill $$LOGS_PID 2>/dev/null || true; \ wait $$LOGS_PID 2>/dev/null || true; \ exit $$EXIT_CODE - @if [ "$(TEST_RUNTIME_OUTPUT)" = "true" ]; then \ - sed -n 's/^.*WORKER_RUNTIME_OUTPUT_JSON=//p' "$(TEST_RUNTIME_OUTPUT_DIR)/container.log" | tail -n 1 > "$(TEST_RUNTIME_OUTPUT_DIR)/$(TEST_RUNTIME_OUTPUT_FILE)"; \ - test -s "$(TEST_RUNTIME_OUTPUT_DIR)/$(TEST_RUNTIME_OUTPUT_FILE)"; \ - fi @$(MAKE) clean || exit 1 @printf "$(COLOR_GREEN)$(SYM_SUCCESS) Tests completed successfully$(COLOR_RESET)\n" diff --git a/Makefile.help b/Makefile.help index 57f2fd60..228f1a01 100644 --- a/Makefile.help +++ b/Makefile.help @@ -50,5 +50,3 @@ help: @echo " DOCKER_IMAGE (default: $(DOCKER_IMAGE))" @echo " FOLLOW_LOGS (default: $(FOLLOW_LOGS))" @echo " RUN_ENV (default: $(RUN_ENV))" - @echo " TEST_RUNTIME_OUTPUT (default: $(TEST_RUNTIME_OUTPUT))" - @echo " TEST_RUNTIME_OUTPUT_DIR (default: $(TEST_RUNTIME_OUTPUT_DIR))" diff --git a/Makefile.variables b/Makefile.variables index 466f40dd..b0eee4a5 100644 --- a/Makefile.variables +++ b/Makefile.variables @@ -15,10 +15,6 @@ COMMAND ?= MULTIPLATFORM ?= false FOLLOW_LOGS ?= false RUN_ENV ?= -TEST_RUNTIME_OUTPUT ?= false -TEST_RUNTIME_OUTPUT_DIR ?= $(PWD)/runtime-output -TEST_RUNTIME_OUTPUT_FILE ?= runtime.json -TEST_RUNTIME_OUTPUT_LOG ?= true # Colors for better visibility COLOR_RESET=\033[0m diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index 1c4cb7e8..19744db3 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -2,19 +2,30 @@ # 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_stdout_enabled; then + exec 3>&1 + exec 1>&2 + export WORKER_OUTPUT_STDOUT_FD=3 +fi + +log_info "Welcome to UDX Worker Container. Initializing environment..." + +configure_environment || exit 1 + emit_runtime_output || exit 1 +if runtime_output_stdout_enabled; then + exec 3>&- + exit 0 +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 ebbd68d3..7d2419de 100644 --- a/docs/config.md +++ b/docs/config.md @@ -87,6 +87,14 @@ Set `WORKER_OUTPUT_FILE` when a deployment or workflow needs redacted runtime co Set `WORKER_OUTPUT_LOG=true` to also emit the same JSON to container logs as a single minified line prefixed with `WORKER_RUNTIME_OUTPUT_JSON=`. This is useful for Kubernetes or workflow systems where the next step reads container logs instead of a mounted file. +Set `WORKER_OUTPUT_STDOUT=true` when a workflow should capture only the runtime JSON from stdout. In this mode the entrypoint sends setup logs to stderr, writes the redacted runtime JSON to stdout, and exits before starting the process manager: + +```bash +docker run --rm \ + -e WORKER_OUTPUT_STDOUT=true \ + usabilitydynamics/udx-worker:latest > runtime-output/runtime.json +``` + The output contains: - `env`: resolved non-secret environment variables. diff --git a/lib/runtime_output.sh b/lib/runtime_output.sh index 15163924..b41759c6 100644 --- a/lib/runtime_output.sh +++ b/lib/runtime_output.sh @@ -108,6 +108,17 @@ runtime_output_log_enabled() { esac } +runtime_output_stdout_enabled() { + case "${WORKER_OUTPUT_STDOUT:-false}" in + true|TRUE|1|yes|YES|on|ON) + return 0 + ;; + *) + return 1 + ;; + esac +} + emit_runtime_output_log() { local runtime_json="$1" local compact_json @@ -116,11 +127,21 @@ emit_runtime_output_log() { printf 'WORKER_RUNTIME_OUTPUT_JSON=%s\n' "$compact_json" } +emit_runtime_output_stdout() { + local runtime_json="$1" + + if [[ -n "${WORKER_OUTPUT_STDOUT_FD:-}" ]]; then + printf '%s\n' "$runtime_json" >&"${WORKER_OUTPUT_STDOUT_FD}" + else + printf '%s\n' "$runtime_json" + fi +} + emit_runtime_output() { local config_json runtime_json - if [[ -z "${WORKER_OUTPUT_FILE:-}" ]] && ! runtime_output_log_enabled; then - log_info "Runtime output disabled. Set WORKER_OUTPUT_FILE or WORKER_OUTPUT_LOG=true to emit redacted JSON runtime config for workflow/deployment integrations." + if [[ -z "${WORKER_OUTPUT_FILE:-}" ]] && ! runtime_output_log_enabled && ! runtime_output_stdout_enabled; then + log_info "Runtime output disabled. Set WORKER_OUTPUT_FILE, WORKER_OUTPUT_LOG=true, or WORKER_OUTPUT_STDOUT=true to emit redacted JSON runtime config for workflow/deployment integrations." return 0 fi @@ -140,4 +161,8 @@ emit_runtime_output() { if runtime_output_log_enabled; then emit_runtime_output_log "$runtime_json" || return 1 fi + + if runtime_output_stdout_enabled; then + emit_runtime_output_stdout "$runtime_json" || return 1 + fi } diff --git a/src/configs/worker.yaml b/src/configs/worker.yaml index aabef6ed..225de63c 100644 --- a/src/configs/worker.yaml +++ b/src/configs/worker.yaml @@ -5,4 +5,5 @@ config: env: WORKER_OUTPUT_FILE: "" WORKER_OUTPUT_LOG: "false" + WORKER_OUTPUT_STDOUT: "false" secrets: {} diff --git a/test/modules/25_runtime_output.sh b/test/modules/25_runtime_output.sh index 85019015..0b9b89e1 100755 --- a/test/modules/25_runtime_output.sh +++ b/test/modules/25_runtime_output.sh @@ -74,4 +74,15 @@ if ! echo "${LOG_LINE#WORKER_RUNTIME_OUTPUT_JSON=}" | jq -e '.env.PUBLIC_VALUE = exit 1 fi +STDOUT_OUTPUT=$(WORKER_OUTPUT_STDOUT=true emit_runtime_output_stdout "$RUNTIME_OUTPUT") +if [[ "$STDOUT_OUTPUT" == WORKER_RUNTIME_OUTPUT_JSON=* ]]; then + print_error "runtime output stdout mode should emit raw JSON" + exit 1 +fi + +if ! echo "$STDOUT_OUTPUT" | jq -e '.env.PUBLIC_VALUE == "visible value"' >/dev/null; then + print_error "runtime output stdout JSON is invalid" + exit 1 +fi + print_success "All runtime output tests passed" From 8941ed6891f275bf0c695c4aefd65e91fa42b204 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Wed, 17 Jun 2026 16:56:26 +0300 Subject: [PATCH 13/16] fix: simplify runtime output emission --- docs/config.md | 8 +++--- lib/runtime_output.sh | 42 +++++++++++++++++-------------- src/configs/worker.yaml | 1 - test/modules/25_runtime_output.sh | 5 ++-- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/docs/config.md b/docs/config.md index 7d2419de..86643526 100644 --- a/docs/config.md +++ b/docs/config.md @@ -81,11 +81,7 @@ 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. - -Set `WORKER_OUTPUT_FILE` when a deployment or workflow needs redacted runtime config evidence. The worker writes JSON runtime metadata to that path after `worker.yaml`, deployment environment overrides, and secret references have been applied. - -Set `WORKER_OUTPUT_LOG=true` to also emit the same JSON to container logs as a single minified line prefixed with `WORKER_RUNTIME_OUTPUT_JSON=`. This is useful for Kubernetes or workflow systems where the next step reads container logs instead of a mounted file. +By default the worker does not print runtime config details. The entrypoint logs a short hint that output can be enabled. Set `WORKER_OUTPUT_STDOUT=true` when a workflow should capture only the runtime JSON from stdout. In this mode the entrypoint sends setup logs to stderr, writes the redacted runtime JSON to stdout, and exits before starting the process manager: @@ -95,6 +91,8 @@ docker run --rm \ usabilitydynamics/udx-worker:latest > runtime-output/runtime.json ``` +Set `WORKER_OUTPUT_LOG=true` to also emit the same JSON to container logs as a single minified line prefixed with `WORKER_RUNTIME_OUTPUT_JSON=`. This is useful for Kubernetes or workflow systems where the next step reads container logs from a normal-running container. + The output contains: - `env`: resolved non-secret environment variables. diff --git a/lib/runtime_output.sh b/lib/runtime_output.sh index b41759c6..cfd976bc 100644 --- a/lib/runtime_output.sh +++ b/lib/runtime_output.sh @@ -81,18 +81,29 @@ build_runtime_redacted_json() { local config_json="$1" local names name json - if [[ ! -f "$WORKER_ENV_FILE" ]]; then - log_error "Runtime output" "Environment file not found: $WORKER_ENV_FILE" - return 1 + 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 - names=$(grep "^export " "$WORKER_ENV_FILE" | cut -d'=' -f1 | cut -d' ' -f2) - json="[]" - 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" + 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' } @@ -140,8 +151,8 @@ emit_runtime_output_stdout() { emit_runtime_output() { local config_json runtime_json - if [[ -z "${WORKER_OUTPUT_FILE:-}" ]] && ! runtime_output_log_enabled && ! runtime_output_stdout_enabled; then - log_info "Runtime output disabled. Set WORKER_OUTPUT_FILE, WORKER_OUTPUT_LOG=true, or WORKER_OUTPUT_STDOUT=true to emit redacted JSON runtime config for workflow/deployment integrations." + if ! runtime_output_log_enabled && ! runtime_output_stdout_enabled; then + log_info "Runtime output disabled. Set WORKER_OUTPUT_STDOUT=true or WORKER_OUTPUT_LOG=true to emit redacted JSON runtime config for workflow/deployment integrations." return 0 fi @@ -151,13 +162,6 @@ emit_runtime_output() { return 1 fi - if [[ -n "${WORKER_OUTPUT_FILE:-}" ]]; then - 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" - fi - if runtime_output_log_enabled; then emit_runtime_output_log "$runtime_json" || return 1 fi diff --git a/src/configs/worker.yaml b/src/configs/worker.yaml index 225de63c..64350149 100644 --- a/src/configs/worker.yaml +++ b/src/configs/worker.yaml @@ -3,7 +3,6 @@ kind: workerConfig version: udx.io/worker-v1/config config: env: - WORKER_OUTPUT_FILE: "" WORKER_OUTPUT_LOG: "false" WORKER_OUTPUT_STDOUT: "false" secrets: {} diff --git a/test/modules/25_runtime_output.sh b/test/modules/25_runtime_output.sh index 0b9b89e1..7d958c03 100755 --- a/test/modules/25_runtime_output.sh +++ b/test/modules/25_runtime_output.sh @@ -34,7 +34,8 @@ CONFIG_JSON='{ "CONFIG_REF": "gcp/project-id/secret-name" }, "secrets": { - "CONFIG_SECRET": "aws/secret-name/us-west-2" + "CONFIG_SECRET": "aws/secret-name/us-west-2", + "CONFIG_ONLY_SECRET": "aws/config-only/us-west-2" } } }' @@ -58,7 +59,7 @@ if echo "$RUNTIME_OUTPUT" | jq -e '.env.CONFIG_SECRET or .env.CONFIG_REF or .env exit 1 fi -if ! echo "$RUNTIME_OUTPUT" | jq -e '.redacted == ["CONFIG_REF", "CONFIG_SECRET", "DEPLOYMENT_SECRET", "DEPLOYMENT_SECRET_TWO"]' >/dev/null; then +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 From 254336fb02212b636c742be6510a4f26c1429310 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Wed, 17 Jun 2026 17:33:01 +0300 Subject: [PATCH 14/16] fix: emit runtime contract without stopping execution --- .github/workflows/docker-ops.yml | 3 ++- bin/entrypoint.sh | 7 +++--- docs/config.md | 15 ++++++++---- lib/runtime_output.sh | 39 ++++++------------------------- src/configs/worker.yaml | 3 +-- test/modules/25_runtime_output.sh | 20 ++-------------- 6 files changed, 25 insertions(+), 62 deletions(-) diff --git a/.github/workflows/docker-ops.yml b/.github/workflows/docker-ops.yml index 85f14ac8..13d05825 100644 --- a/.github/workflows/docker-ops.yml +++ b/.github/workflows/docker-ops.yml @@ -53,8 +53,9 @@ jobs: run: | mkdir -p runtime-output docker run --rm \ - -e WORKER_OUTPUT_STDOUT=true \ + -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 diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index 19744db3..374f41aa 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -9,10 +9,10 @@ source "${WORKER_LIB_DIR}/secrets.sh" # shellcheck disable=SC1091 source "${WORKER_LIB_DIR}/runtime_output.sh" -if runtime_output_stdout_enabled; then +if runtime_output_enabled; then exec 3>&1 exec 1>&2 - export WORKER_OUTPUT_STDOUT_FD=3 + export WORKER_RUNTIME_OUTPUT_FD=3 fi log_info "Welcome to UDX Worker Container. Initializing environment..." @@ -21,9 +21,8 @@ configure_environment || exit 1 emit_runtime_output || exit 1 -if runtime_output_stdout_enabled; then +if runtime_output_enabled; then exec 3>&- - exit 0 fi # Start the process manager diff --git a/docs/config.md b/docs/config.md index 86643526..d7484c19 100644 --- a/docs/config.md +++ b/docs/config.md @@ -83,15 +83,20 @@ docker run --rm \ By default the worker does not print runtime config details. The entrypoint logs a short hint that output can be enabled. -Set `WORKER_OUTPUT_STDOUT=true` when a workflow should capture only the runtime JSON from stdout. In this mode the entrypoint sends setup logs to stderr, writes the redacted runtime JSON to stdout, and exits before starting the process manager: +Set `WORKER_RUNTIME_OUTPUT=true` when a workflow or deployment should receive the runtime contract JSON. In this mode the entrypoint sends setup and process logs to stderr, writes the redacted runtime JSON to stdout, and continues normal runtime execution: ```bash -docker run --rm \ - -e WORKER_OUTPUT_STDOUT=true \ +# Runtime contract plus normal container processes. +docker run \ + -e WORKER_RUNTIME_OUTPUT=true \ usabilitydynamics/udx-worker:latest > runtime-output/runtime.json -``` -Set `WORKER_OUTPUT_LOG=true` to also emit the same JSON to container logs as a single minified line prefixed with `WORKER_RUNTIME_OUTPUT_JSON=`. This is useful for Kubernetes or workflow systems where the next step reads container logs from a normal-running container. +# Runtime contract only, with a command that exits. +docker run --rm \ + -e WORKER_RUNTIME_OUTPUT=true \ + usabilitydynamics/udx-worker:latest \ + true > runtime-output/runtime.json +``` The output contains: diff --git a/lib/runtime_output.sh b/lib/runtime_output.sh index cfd976bc..d3c8ac7c 100644 --- a/lib/runtime_output.sh +++ b/lib/runtime_output.sh @@ -108,8 +108,8 @@ build_runtime_redacted_json() { echo "$json" | jq -S 'unique' } -runtime_output_log_enabled() { - case "${WORKER_OUTPUT_LOG:-false}" in +runtime_output_enabled() { + case "${WORKER_RUNTIME_OUTPUT:-false}" in true|TRUE|1|yes|YES|on|ON) return 0 ;; @@ -119,30 +119,11 @@ runtime_output_log_enabled() { esac } -runtime_output_stdout_enabled() { - case "${WORKER_OUTPUT_STDOUT:-false}" in - true|TRUE|1|yes|YES|on|ON) - return 0 - ;; - *) - return 1 - ;; - esac -} - -emit_runtime_output_log() { - local runtime_json="$1" - local compact_json - - compact_json=$(echo "$runtime_json" | jq -c .) || return 1 - printf 'WORKER_RUNTIME_OUTPUT_JSON=%s\n' "$compact_json" -} - emit_runtime_output_stdout() { local runtime_json="$1" - if [[ -n "${WORKER_OUTPUT_STDOUT_FD:-}" ]]; then - printf '%s\n' "$runtime_json" >&"${WORKER_OUTPUT_STDOUT_FD}" + if [[ -n "${WORKER_RUNTIME_OUTPUT_FD:-}" ]]; then + printf '%s\n' "$runtime_json" >&"${WORKER_RUNTIME_OUTPUT_FD}" else printf '%s\n' "$runtime_json" fi @@ -151,8 +132,8 @@ emit_runtime_output_stdout() { emit_runtime_output() { local config_json runtime_json - if ! runtime_output_log_enabled && ! runtime_output_stdout_enabled; then - log_info "Runtime output disabled. Set WORKER_OUTPUT_STDOUT=true or WORKER_OUTPUT_LOG=true to emit 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 @@ -162,11 +143,5 @@ emit_runtime_output() { return 1 fi - if runtime_output_log_enabled; then - emit_runtime_output_log "$runtime_json" || return 1 - fi - - if runtime_output_stdout_enabled; then - emit_runtime_output_stdout "$runtime_json" || return 1 - fi + emit_runtime_output_stdout "$runtime_json" || return 1 } diff --git a/src/configs/worker.yaml b/src/configs/worker.yaml index 64350149..b44440a0 100644 --- a/src/configs/worker.yaml +++ b/src/configs/worker.yaml @@ -3,6 +3,5 @@ kind: workerConfig version: udx.io/worker-v1/config config: env: - WORKER_OUTPUT_LOG: "false" - WORKER_OUTPUT_STDOUT: "false" + WORKER_RUNTIME_OUTPUT: "false" secrets: {} diff --git a/test/modules/25_runtime_output.sh b/test/modules/25_runtime_output.sh index 7d958c03..ee33108b 100755 --- a/test/modules/25_runtime_output.sh +++ b/test/modules/25_runtime_output.sh @@ -64,25 +64,9 @@ if ! echo "$RUNTIME_OUTPUT" | jq -e '.redacted == ["CONFIG_ONLY_SECRET", "CONFIG exit 1 fi -LOG_LINE=$(WORKER_OUTPUT_LOG=true emit_runtime_output_log "$RUNTIME_OUTPUT") -if [[ "$LOG_LINE" != WORKER_RUNTIME_OUTPUT_JSON=* ]]; then - print_error "runtime output log marker is missing" - exit 1 -fi - -if ! echo "${LOG_LINE#WORKER_RUNTIME_OUTPUT_JSON=}" | jq -e '.env.PUBLIC_VALUE == "visible value"' >/dev/null; then - print_error "runtime output log JSON is invalid" - exit 1 -fi - -STDOUT_OUTPUT=$(WORKER_OUTPUT_STDOUT=true emit_runtime_output_stdout "$RUNTIME_OUTPUT") -if [[ "$STDOUT_OUTPUT" == WORKER_RUNTIME_OUTPUT_JSON=* ]]; then - print_error "runtime output stdout mode should emit raw JSON" - 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 stdout JSON is invalid" + print_error "runtime output JSON is invalid" exit 1 fi From 3378872c0c3a188575de7f8ef64df42916554831 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Thu, 18 Jun 2026 12:35:57 +0300 Subject: [PATCH 15/16] docs: split runtime output contract --- README.md | 1 + docs/config.md | 26 ++--------------- docs/deployment.md | 1 + docs/runtime-output.md | 65 ++++++++++++++++++++++++++++++++++++++++++ docs/secrets.md | 1 + 5 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 docs/runtime-output.md 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/docs/config.md b/docs/config.md index d7484c19..67dbbb60 100644 --- a/docs/config.md +++ b/docs/config.md @@ -81,33 +81,13 @@ docker run --rm \ ## Runtime Output -By default the worker does not print runtime config details. 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_RUNTIME_OUTPUT=true` when a workflow or deployment should receive the runtime contract JSON. In this mode the entrypoint sends setup and process logs to stderr, writes the redacted runtime JSON to stdout, and continues normal runtime execution: - -```bash -# Runtime contract plus normal container processes. -docker run \ - -e WORKER_RUNTIME_OUTPUT=true \ - usabilitydynamics/udx-worker:latest > runtime-output/runtime.json - -# Runtime contract only, with a command that exits. -docker run --rm \ - -e WORKER_RUNTIME_OUTPUT=true \ - usabilitydynamics/udx-worker:latest \ - true > runtime-output/runtime.json -``` - -The output contains: - -- `env`: resolved non-secret environment variables. -- `redacted`: environment variable names that were intentionally omitted because they are configured as secrets or secret references. -- `paths`: the config and environment files used to build the output. - -Workflow-specific outputs such as `$GITHUB_OUTPUT`, `$GITHUB_STEP_SUMMARY`, uploaded artifacts, or platform annotations should be generated by the workflow from the JSON file or the structured log line. +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` From 5d53ac5201e7217d5547b08405c1f5770e125da5 Mon Sep 17 00:00:00 2001 From: Dmytro Smirnov Date: Thu, 18 Jun 2026 12:46:35 +0300 Subject: [PATCH 16/16] ci: label build release job --- .github/workflows/docker-ops.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-ops.yml b/.github/workflows/docker-ops.yml index 13d05825..c6a800ec 100644 --- a/.github/workflows/docker-ops.yml +++ b/.github/workflows/docker-ops.yml @@ -21,6 +21,7 @@ name: Build/Release jobs: docker-ops: + name: Docker Ops permissions: contents: write security-events: write