diff --git a/.github/actions/test-aggressive-cleanup/action.yml b/.github/actions/test-aggressive-cleanup/action.yml new file mode 100644 index 00000000..b499b095 --- /dev/null +++ b/.github/actions/test-aggressive-cleanup/action.yml @@ -0,0 +1,29 @@ +--- + +name: 'Test aggressive-cleanup action' +description: 'Runs validation against the aggressive-cleanup workflow' + +runs: + using: 'composite' + steps: + - name: 'Setup brew' + uses: Homebrew/actions/setup-homebrew@master + + - name: 'Install BATS' + shell: bash + run: brew install bats-core + + - name: 'Set up Docker Buildx' + uses: docker/setup-buildx-action@v3 + + - name: 'Checkout' + uses: actions/checkout@v4 + + - name: 'Run the aggressive-cleanup action' + uses: ./actions/aggressive-cleanup + with: + skip: 'chromium google' + + - name: 'Validate' + shell: bash + run: bats --verbose-run -r ${{ github.action_path }}/aggressive-cleanup.bats diff --git a/.github/actions/test-aggressive-cleanup/aggressive-cleanup.bats b/.github/actions/test-aggressive-cleanup/aggressive-cleanup.bats new file mode 100644 index 00000000..0c712340 --- /dev/null +++ b/.github/actions/test-aggressive-cleanup/aggressive-cleanup.bats @@ -0,0 +1,56 @@ +#!/usr/bin/env bats + +@test "it should have removed /usr/lib/jvm" { + run [ -f /usr/lib/jvm ] + [ "$status" -ne 0 ] +} + +@test "it should have removed /usr/share/dotnet" { + run [ -f /usr/share/dotnet ] + [ "$status" -ne 0 ] +} + +@test "it should have removed /usr/share/swift" { + run [ -f /usr/share/swift ] + [ "$status" -ne 0 ] +} + +@test "it should have removed /usr/local/.ghcup" { + run [ -f /usr/local/.ghcup ] + [ "$status" -ne 0 ] +} + +@test "it should have removed /usr/local/julia*" { + run ls /usr/local/julia* + [ "$status" -ne 0 ] +} + +@test "it should have removed /usr/local/lib/android" { + run [ -f /usr/local/lib/android ] + [ "$status" -ne 0 ] +} + +@test "it should not have removed /usr/local/share/chromium" { + run [ -f /usr/local/share/chromium ] + [ "$status" -eq 0 ] +} + +@test "it should have removed /opt/microsoft" { + run [ -f /opt/microsoft ] + [ "$status" -ne 0 ] +} + +@test "it should not have removed /opt/google" { + run [ -f /opt/google ] + [ "$status" -eq 0 ] +} + +@test "it should have removed /opt/az" { + run [ -f /opt/az ] + [ "$status" -ne 0 ] +} + +@test "it should have removed /usr/local/share/powershell" { + run [ -f /usr/local/share/powershell ] + [ "$status" -ne 0 ] +} diff --git a/.github/workflows/test-aggressive-cleanup.yml b/.github/workflows/test-aggressive-cleanup.yml new file mode 100644 index 00000000..f21f3dd8 --- /dev/null +++ b/.github/workflows/test-aggressive-cleanup.yml @@ -0,0 +1,25 @@ +--- + +name: 'Run aggressive-cleanup action' + +on: + pull_request: + paths: + - actions/aggressive-cleanup/* + - .github/actions/test-aggressive-cleanup/* + - .github/workflows/test-aggressive-cleanup.yml + +defaults: + run: + shell: bash + +jobs: + test-aggressive-cleanup-action: + name: 'Uses the aggressive-cleanup action' + runs-on: ubuntu-latest + steps: + - name: 'Checkout actions' + uses: actions/checkout@v4 + + - name: 'Test aggressive-cleanup action' + uses: ./.github/actions/test-aggressive-cleanup diff --git a/actions/aggressive-cleanup/action.yml b/actions/aggressive-cleanup/action.yml new file mode 100644 index 00000000..2ca766fe --- /dev/null +++ b/actions/aggressive-cleanup/action.yml @@ -0,0 +1,28 @@ +--- +name: 'Aggressive Cleanup' +description: |- + Removes pre-installed software packages that will be unused. This action helps to free up disk + space on the GitHub Actions runner by removing unnecessary files and directories making more + room for your jobs. + +inputs: + skip: + description: 'List of packages to skip from removal. Options are java, dotnet, swift, ghcup, julia, android, chromium, microsoft, google, azure, powershell. Separate multiple values with spaces.' + required: false + default: '' + +outputs: + removed: + description: 'List of packages that were removed.' + value: ${{ steps.cleanup.outputs.removed }} + skipped: + description: 'List of packages that were skipped from removal.' + value: ${{ steps.cleanup.outputs.skipped }} + +runs: + using: 'composite' + steps: + - name: 'Aggressive Cleanup' + id: cleanup + shell: bash + run: ${{ github.action_path }}/cleanup.sh ${{ inputs.skip }} diff --git a/actions/aggressive-cleanup/cleanup.bats b/actions/aggressive-cleanup/cleanup.bats new file mode 100644 index 00000000..d76724c2 --- /dev/null +++ b/actions/aggressive-cleanup/cleanup.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load cleanup.sh + +function sudo() { + echo "sudo $*" >> "$SUDO_CMD_FILE" +} + +function setup() { + export SUDO_CMD_FILE="$BATS_TEST_DIRNAME/sudo.cmd" + export -f sudo +} + +function teardown() { + rm -f "$SUDO_CMD_FILE" +} + +@test "it should remove all directories when no arguments are given" { + run cleanup + + [ "$status" -eq 0 ] + [ -f "$SUDO_CMD_FILE" ] + local expected_commands=( + "sudo rm -rf /usr/lib/jvm" + "sudo rm -rf /usr/share/dotnet" + "sudo rm -rf /usr/share/swift" + "sudo rm -rf /usr/local/.ghcup" + "sudo rm -rf /usr/local/julia*" + "sudo rm -rf /usr/local/lib/android" + "sudo rm -rf /usr/local/share/chromium" + "sudo rm -rf /opt/microsoft" + "sudo rm -rf /opt/google" + "sudo rm -rf /opt/az" + "sudo rm -rf /usr/local/share/powershell" + ) + for cmd in "${expected_commands[@]}"; do + grep -q "$cmd" "$SUDO_CMD_FILE" + done +} + +@test "it should skip specified directories" { + run cleanup java dotnet + + [ "$status" -eq 0 ] + [ -f "$SUDO_CMD_FILE" ] + grep -qv "sudo rm -rf /usr/lib/jvm" "$SUDO_CMD_FILE" + grep -qv "sudo rm -rf /usr/share/dotnet" "$SUDO_CMD_FILE" +} diff --git a/actions/aggressive-cleanup/cleanup.sh b/actions/aggressive-cleanup/cleanup.sh new file mode 100755 index 00000000..ec2125ee --- /dev/null +++ b/actions/aggressive-cleanup/cleanup.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# @see https://mathio28.medium.com/squeezing-disk-space-from-github-actions-runners-an-engineers-guide-d5fbe4443692 + +DIRECTORIES=( + # Java (JDKs) + java="/usr/lib/jvm" + # .NET SDKs + dotnet="/usr/share/dotnet" + # Swift toolchain + swift="/usr/share/swift" + # Haskell (GHC) + ghcup="/usr/local/.ghcup" + # Julia + julia="/usr/local/julia*" + # Android SDKs + android="/usr/local/lib/android" + # Chromium + chromium="/usr/local/share/chromium" + # Microsoft/Edge + microsoft="/opt/microsoft" + # Google Chrome + google="/opt/google" + # Azure CLI + azure="/opt/az" + # PowerShell + powershell="/usr/local/share/powershell" +) + +function cleanup() { + local skip=("$@") + + for dir in "${DIRECTORIES[@]}"; do + local key="${dir%%=*}" + dir="${dir##*=}" + local skip_dir=false + for s in "${skip[@]}"; do + if [ "$key" = "$s" ]; then + skip_dir=true + break + fi + done + if [ "$skip_dir" = false ]; then + echo "Removing $dir..." + sudo rm -rf "$dir" + else + echo "Skipping $dir..." + fi + done +} + +if [ "$0" = "${BASH_SOURCE[0]}" ]; then + set -eo pipefail + cleanup "${@:-}" + exit $? +fi