Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/docker-dependency-updater.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 }}
Expand Down
43 changes: 42 additions & 1 deletion .github/workflows/docker-ops.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Docker Ops
name: Build/Release

"on":
push:
Expand All @@ -21,6 +21,7 @@ name: Docker Ops

jobs:
docker-ops:
name: Docker Ops
permissions:
contents: write
security-events: write
Expand All @@ -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
2 changes: 1 addition & 1 deletion .rabbit/context.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand All @@ -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
Expand All @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion Makefile.help
Original file line number Diff line number Diff line change
Expand Up @@ -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))"
@echo " FOLLOW_LOGS (default: $(FOLLOW_LOGS))"
@echo " RUN_ENV (default: $(RUN_ENV))"
3 changes: 2 additions & 1 deletion Makefile.variables
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ DEBUG ?= false
COMMAND ?=
MULTIPLATFORM ?= false
FOLLOW_LOGS ?= false
RUN_ENV ?=

# Colors for better visibility
COLOR_RESET=\033[0m
Expand All @@ -26,4 +27,4 @@ COLOR_YELLOW=\033[33m
SYM_ARROW=➜
SYM_SUCCESS=✔
SYM_ERROR=✖
SYM_WARNING=⚠
SYM_WARNING=⚠
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 15 additions & 5 deletions bin/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" &
Expand Down
7 changes: 3 additions & 4 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
1 change: 1 addition & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
65 changes: 65 additions & 0 deletions docs/runtime-output.md
Original file line number Diff line number Diff line change
@@ -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`
1 change: 1 addition & 0 deletions docs/secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
40 changes: 40 additions & 0 deletions lib/env_handler.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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[]')
Expand Down
Loading
Loading