diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b72ade..0c7bbeb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -120,6 +120,82 @@ jobs: exit 1 fi + output-contract: + runs-on: ubuntu-latest + strategy: + matrix: + scenario: + - name: custom + cleanup_profile: custom + remove_codeql: "true" + skip_components: "" + expected_component: codeql + - name: max + cleanup_profile: max + remove_codeql: "false" + skip_components: docker-engine + expected_component: dotnet + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Run action and capture outputs + id: cleanup + uses: ./ + with: + cleanup-profile: ${{ matrix.scenario.cleanup_profile }} + remove-codeql: ${{ matrix.scenario.remove_codeql }} + skip-components: ${{ matrix.scenario.skip_components }} + + - name: Verify structured outputs + shell: bash + env: + BEFORE_AVAILABLE_BYTES: ${{ steps.cleanup.outputs.before-available-bytes }} + AFTER_AVAILABLE_BYTES: ${{ steps.cleanup.outputs.after-available-bytes }} + RECLAIMED_BYTES: ${{ steps.cleanup.outputs.reclaimed-bytes }} + CLEANUP_PROFILE_USED: ${{ steps.cleanup.outputs.cleanup-profile }} + EXECUTED_COMPONENTS: ${{ steps.cleanup.outputs.executed-components }} + EXPECTED_PROFILE: ${{ matrix.scenario.cleanup_profile }} + EXPECTED_COMPONENT: ${{ matrix.scenario.expected_component }} + run: | + set -euo pipefail + + if ! [[ "$BEFORE_AVAILABLE_BYTES" =~ ^[0-9]+$ ]]; then + echo "before-available-bytes must be numeric, got '$BEFORE_AVAILABLE_BYTES'" + exit 1 + fi + + if ! [[ "$AFTER_AVAILABLE_BYTES" =~ ^[0-9]+$ ]]; then + echo "after-available-bytes must be numeric, got '$AFTER_AVAILABLE_BYTES'" + exit 1 + fi + + if ! [[ "$RECLAIMED_BYTES" =~ ^-?[0-9]+$ ]]; then + echo "reclaimed-bytes must be numeric, got '$RECLAIMED_BYTES'" + exit 1 + fi + + if [ "$CLEANUP_PROFILE_USED" != "$EXPECTED_PROFILE" ]; then + echo "Expected cleanup-profile '$EXPECTED_PROFILE', got '$CLEANUP_PROFILE_USED'" + exit 1 + fi + + if [ -z "$EXECUTED_COMPONENTS" ]; then + echo "executed-components must not be empty" + exit 1 + fi + + if ! [[ ",$EXECUTED_COMPONENTS," == *",$EXPECTED_COMPONENT,"* ]]; then + echo "Expected executed-components to include '$EXPECTED_COMPONENT', got '$EXECUTED_COMPONENTS'" + exit 1 + fi + + expected_reclaimed=$((AFTER_AVAILABLE_BYTES - BEFORE_AVAILABLE_BYTES)) + if [ "$RECLAIMED_BYTES" -ne "$expected_reclaimed" ]; then + echo "Expected reclaimed-bytes to be '$expected_reclaimed', got '$RECLAIMED_BYTES'" + exit 1 + fi + invalid-input-validation: runs-on: ubuntu-latest strategy: diff --git a/AGENTS.md b/AGENTS.md index 26d297e..0beea46 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,11 +5,12 @@ - Composite GitHub Action that frees disk space on `ubuntu-latest` runners by removing optional preinstalled software and caches. - Main implementation lives in `action.yml` and is intended to run early in a workflow. - Supports `cleanup-profile` modes (`custom` and `max`), `skip-components` to keep specific toolchains, and an opt-in `swapfile-size` input to manage `/mnt/swapfile` without changing it by default. +- Exposes structured outputs for `/` disk availability before/after cleanup, reclaimed bytes, normalized cleanup profile, and the scheduled component list. ## Repository Map - `action.yml`: Action metadata, inputs, and all cleanup logic. -- `.github/workflows/test.yml`: CI matrix tests; each run enables exactly one removal input and verifies expected state. +- `.github/workflows/test.yml`: CI matrix tests; each run enables exactly one removal input and verifies expected state, plus output-contract checks for `custom` and `max`. - `README.md`: User-facing usage and caveats. ## Scripts / Execution Surfaces diff --git a/README.md b/README.md index c33d2c6..b765ad6 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This action removes optional preinstalled SDKs, toolchains, and caches so your w - [Quick start](#quick-start) - [Cleanup Profiles](#cleanup-profiles) - [Inputs reference](#inputs-reference) +- [Outputs reference](#outputs-reference) - [Compatibility](#compatibility) - [Contributing](#contributing) - [Migration Guide](#migration-guide) @@ -182,6 +183,36 @@ All `remove-*` inputs are optional toggles. In `cleanup-profile: max`, every com | `skip-components` | N/A | Comma-separated components to keep when `cleanup-profile=max`. | | `swapfile-size` | empty | Optional swapfile size (`0`, `1.5GiB`, `512MiB`, or a plain GiB value like `2`). Leaves swap untouched when omitted. | +## Outputs reference + +The action now emits machine-readable outputs so later workflow steps can branch on actual cleanup results. + +| Output | Description | +| ------------------------ | --------------------------------------------------------------- | +| `before-available-bytes` | Available bytes on `/` before cleanup starts. | +| `after-available-bytes` | Available bytes on `/` after cleanup completes. | +| `reclaimed-bytes` | Net bytes reclaimed on `/` (`after-available-bytes - before-available-bytes`). | +| `cleanup-profile` | Normalized cleanup profile actually used (`max` or `custom`). | +| `executed-components` | Comma-separated component names scheduled for execution. | + +Example: write reclaimed space to the job summary. + +```yaml +- name: Free Runner Space + id: free-space + uses: justinthelaw/maximize-github-runner-space@latest + with: + cleanup-profile: custom + remove-codeql: "true" + +- name: Publish cleanup summary + shell: bash + run: | + set -euo pipefail + echo "Reclaimed bytes: ${{ steps.free-space.outputs.reclaimed-bytes }}" >> "$GITHUB_STEP_SUMMARY" + echo "Executed components: ${{ steps.free-space.outputs.executed-components }}" >> "$GITHUB_STEP_SUMMARY" +``` + ### Component toggles Each row lists the `remove-*` input, its `skip-components` name, and the primary removal targets. diff --git a/action.yml b/action.yml index a61ae28..202eda7 100644 --- a/action.yml +++ b/action.yml @@ -236,18 +236,39 @@ inputs: required: false default: "false" +outputs: + before-available-bytes: + description: "Available bytes on / before cleanup starts." + value: ${{ steps.structured-outputs.outputs.before_available_bytes }} + after-available-bytes: + description: "Available bytes on / after cleanup completes." + value: ${{ steps.structured-outputs.outputs.after_available_bytes }} + reclaimed-bytes: + description: "Net bytes reclaimed on / (after - before)." + value: ${{ steps.structured-outputs.outputs.reclaimed_bytes }} + cleanup-profile: + description: "Normalized cleanup profile used for the run (max or custom)." + value: ${{ steps.structured-outputs.outputs.cleanup_profile }} + executed-components: + description: "Comma-separated cleanup components scheduled for execution." + value: ${{ steps.structured-outputs.outputs.executed_components }} + runs: using: "composite" steps: - name: Disk Space Report (BEFORE) + id: disk-before shell: bash run: | set -euo pipefail + before_available_bytes=$(df --output=avail -B1 / | tail -n1 | tr -d '[:space:]') echo "Available storage:" sudo df -h echo + echo "available_bytes=${before_available_bytes}" >> "$GITHUB_OUTPUT" - name: Initialize Cleanup Plan + id: init-plan shell: bash env: CLEANUP_PROFILE: ${{ inputs.cleanup-profile }} @@ -701,6 +722,62 @@ runs: echo echo + scheduled_components=() + [[ "$COMPONENT_DOTNET" == "true" ]] && scheduled_components+=("dotnet") + [[ "$COMPONENT_ANDROID" == "true" ]] && scheduled_components+=("android") + [[ "$COMPONENT_HASKELL" == "true" ]] && scheduled_components+=("haskell") + [[ "$COMPONENT_CODEQL" == "true" ]] && scheduled_components+=("codeql") + [[ "$COMPONENT_CACHED_TOOLS" == "true" ]] && scheduled_components+=("cached-tools") + [[ "$COMPONENT_SWAPFILE" == "true" ]] && scheduled_components+=("swapfile") + [[ "$COMPONENT_SWIFT" == "true" ]] && scheduled_components+=("swift") + [[ "$COMPONENT_JULIA" == "true" ]] && scheduled_components+=("julia") + [[ "$COMPONENT_JAVA" == "true" ]] && scheduled_components+=("java") + [[ "$COMPONENT_BROWSERS" == "true" ]] && scheduled_components+=("browsers") + [[ "$COMPONENT_POWERSHELL" == "true" ]] && scheduled_components+=("powershell") + [[ "$COMPONENT_MINICONDA" == "true" ]] && scheduled_components+=("miniconda") + [[ "$COMPONENT_HOMEBREW" == "true" ]] && scheduled_components+=("homebrew") + [[ "$COMPONENT_VCPKG" == "true" ]] && scheduled_components+=("vcpkg") + [[ "$COMPONENT_CACHED_GO" == "true" ]] && scheduled_components+=("cached-go") + [[ "$COMPONENT_CACHED_NODE" == "true" ]] && scheduled_components+=("cached-node") + [[ "$COMPONENT_CACHED_PYTHON" == "true" ]] && scheduled_components+=("cached-python") + [[ "$COMPONENT_CACHED_PYPY" == "true" ]] && scheduled_components+=("cached-pypy") + [[ "$COMPONENT_CACHED_RUBY" == "true" ]] && scheduled_components+=("cached-ruby") + [[ "$COMPONENT_CHROME" == "true" ]] && scheduled_components+=("chrome") + [[ "$COMPONENT_CHROMIUM" == "true" ]] && scheduled_components+=("chromium") + [[ "$COMPONENT_EDGE" == "true" ]] && scheduled_components+=("edge") + [[ "$COMPONENT_FIREFOX" == "true" ]] && scheduled_components+=("firefox") + [[ "$COMPONENT_WEBDRIVERS" == "true" ]] && scheduled_components+=("webdrivers") + [[ "$COMPONENT_SELENIUM" == "true" ]] && scheduled_components+=("selenium") + [[ "$COMPONENT_AWS_CLI" == "true" ]] && scheduled_components+=("aws-cli") + [[ "$COMPONENT_AWS_SAM_CLI" == "true" ]] && scheduled_components+=("aws-sam-cli") + [[ "$COMPONENT_AZURE_CLI" == "true" ]] && scheduled_components+=("azure-cli") + [[ "$COMPONENT_GH_CLI" == "true" ]] && scheduled_components+=("gh-cli") + [[ "$COMPONENT_GCLOUD_CLI" == "true" ]] && scheduled_components+=("gcloud-cli") + [[ "$COMPONENT_AZCOPY" == "true" ]] && scheduled_components+=("azcopy") + [[ "$COMPONENT_KUBECTL" == "true" ]] && scheduled_components+=("kubectl") + [[ "$COMPONENT_HELM" == "true" ]] && scheduled_components+=("helm") + [[ "$COMPONENT_KIND" == "true" ]] && scheduled_components+=("kind") + [[ "$COMPONENT_MINIKUBE" == "true" ]] && scheduled_components+=("minikube") + [[ "$COMPONENT_KUSTOMIZE" == "true" ]] && scheduled_components+=("kustomize") + [[ "$COMPONENT_DOCKER_ENGINE" == "true" ]] && scheduled_components+=("docker-engine") + [[ "$COMPONENT_BUILDAH" == "true" ]] && scheduled_components+=("buildah") + [[ "$COMPONENT_PODMAN" == "true" ]] && scheduled_components+=("podman") + [[ "$COMPONENT_MAVEN" == "true" ]] && scheduled_components+=("maven") + [[ "$COMPONENT_GRADLE" == "true" ]] && scheduled_components+=("gradle") + [[ "$COMPONENT_ANT" == "true" ]] && scheduled_components+=("ant") + [[ "$COMPONENT_PHP" == "true" ]] && scheduled_components+=("php") + [[ "$COMPONENT_RUST" == "true" ]] && scheduled_components+=("rust") + [[ "$COMPONENT_POSTGRESQL" == "true" ]] && scheduled_components+=("postgresql") + [[ "$COMPONENT_MYSQL" == "true" ]] && scheduled_components+=("mysql") + [[ "$COMPONENT_APACHE" == "true" ]] && scheduled_components+=("apache") + [[ "$COMPONENT_NGINX" == "true" ]] && scheduled_components+=("nginx") + [[ "$COMPONENT_DOCKER_IMAGES" == "true" ]] && scheduled_components+=("docker-images") + [[ "$COMPONENT_LARGE_PACKAGES" == "true" ]] && scheduled_components+=("large-packages") + + scheduled_components_csv=$(IFS=,; echo "${scheduled_components[*]}") + echo "cleanup_profile=${CLEANUP_PROFILE}" >> "$GITHUB_OUTPUT" + echo "executed_components=${scheduled_components_csv}" >> "$GITHUB_OUTPUT" + - name: Parallel File System Cleanup shell: bash run: | @@ -1397,9 +1474,29 @@ runs: rm -f /tmp/total_ops /tmp/completed_ops /tmp/completed_ops.lock - name: Disk Space Report (AFTER) + id: disk-after shell: bash run: | set -euo pipefail + after_available_bytes=$(df --output=avail -B1 / | tail -n1 | tr -d '[:space:]') echo "Available storage:" sudo df -h echo + echo "available_bytes=${after_available_bytes}" >> "$GITHUB_OUTPUT" + + - name: Emit Structured Outputs + id: structured-outputs + shell: bash + env: + BEFORE_AVAILABLE_BYTES: ${{ steps.disk-before.outputs.available_bytes }} + AFTER_AVAILABLE_BYTES: ${{ steps.disk-after.outputs.available_bytes }} + CLEANUP_PROFILE_USED: ${{ steps.init-plan.outputs.cleanup_profile }} + EXECUTED_COMPONENTS: ${{ steps.init-plan.outputs.executed_components }} + run: | + set -euo pipefail + reclaimed_bytes=$((AFTER_AVAILABLE_BYTES - BEFORE_AVAILABLE_BYTES)) + echo "before_available_bytes=${BEFORE_AVAILABLE_BYTES}" >> "$GITHUB_OUTPUT" + echo "after_available_bytes=${AFTER_AVAILABLE_BYTES}" >> "$GITHUB_OUTPUT" + echo "reclaimed_bytes=${reclaimed_bytes}" >> "$GITHUB_OUTPUT" + echo "cleanup_profile=${CLEANUP_PROFILE_USED}" >> "$GITHUB_OUTPUT" + echo "executed_components=${EXECUTED_COMPONENTS}" >> "$GITHUB_OUTPUT" diff --git a/docs/MIGRATIONS.md b/docs/MIGRATIONS.md index 9d595d3..3b78f37 100644 --- a/docs/MIGRATIONS.md +++ b/docs/MIGRATIONS.md @@ -4,8 +4,25 @@ This document captures breaking changes and notable behavior shifts between rele ## Table of Contents +- [0.7.x -> 0.8.0](#07x---080) - [0.6.x -> 0.7.0](#06x---070) +## 0.7.x -> 0.8.0 + +**Summary** + +`maximize-github-runner-space` now emits stable machine-readable outputs for disk-space reporting and cleanup planning. + +**Behavior changes** + +- Added composite action outputs: + - `before-available-bytes` + - `after-available-bytes` + - `reclaimed-bytes` + - `cleanup-profile` + - `executed-components` +- Output behavior is observational only and does not change cleanup/removal semantics. + ## 0.6.x -> 0.7.0 **Summary**