diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 26722ce..ec51cb0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -14,6 +14,11 @@ on: - 'handle_caches.jl' - '.github/**' +# needed to allow julia-actions/cache to delete old caches that it has created +permissions: + actions: write + contents: read + jobs: generate-key: runs-on: ubuntu-latest diff --git a/README.md b/README.md index db35f01..00f346d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,11 @@ name: CI on: [push, pull_request] +# needed to allow julia-actions/cache to delete old caches that it has created +permissions: + actions: write + contents: read + jobs: test: runs-on: ubuntu-latest @@ -34,7 +39,7 @@ However note that caching the registries may mean that the registry will not be ### Optional Inputs -- `cache-name` - The cache key prefix. Defaults to `julia-cache`. The key body automatically includes matrix vars and the OS. Include any other parameters/details in this prefix to ensure one unique cache key per concurrent job type. +- `cache-name` - The cache key prefix. Defaults to `julia-cache-${{ github.workflow }}-${{ github.job }}`. The key body automatically includes matrix vars and the OS. Include any other parameters/details in this prefix to ensure one unique cache key per concurrent job type. - `include-matrix` - Whether to include the matrix values when constructing the cache key. Defaults to `true`. - `cache-artifacts` - Whether to cache `~/.julia/artifacts/`. Defaults to `true`. - `cache-packages` - Whether to cache `~/.julia/packages/`. Defaults to `true`. @@ -43,6 +48,7 @@ However note that caching the registries may mean that the registry will not be - `cache-scratchspaces` - Whether to cache `~/.julia/scratchspaces/`. Defaults to `true`. - `cache-log` - Whether to cache `~/.julia/logs/`. Defaults to `true`. Helps auto-`Pkg.gc()` keep the cache small. - `delete-old-caches` - Whether to delete old caches for the given key. Defaults to `true` +- `token` - A github PAT. Defaults to `github.token`. Requires `repo` scope to enable the deletion of old caches. ### Outputs @@ -79,6 +85,18 @@ This action automatically deletes old caches that match the first 4 fields of th Which means your caches files will not grow needlessly. Github also deletes cache files after [90 days which can be increased in private repos to up to 400 days](https://docs.github.com/en/organizations/managing-organization-settings/configuring-the-retention-period-for-github-actions-artifacts-and-logs-in-your-organization) +> [!NOTE] +> To allow deletion of caches you will likely need to grant the following to the default +> `GITHUB_TOKEN` by adding this to your yml: +> ``` +> permissions: +> actions: write +> contents: read +> ``` +> (Note this won't work for fork PRs but should once merged) +> Or provide a token with `repo` scope via the `token` input option. +> See https://cli.github.com/manual/gh_cache_delete + To disable deletion set input `delete-old-caches: 'false'`. ### Cache Garbage Collection diff --git a/action.yml b/action.yml index 42fbe03..79dc982 100644 --- a/action.yml +++ b/action.yml @@ -9,7 +9,7 @@ branding: inputs: cache-name: description: 'The cache key prefix. Unless disabled the key body automatically includes matrix vars, and the OS. Include any other parameters/details in this prefix to ensure one unique cache key per concurrent job type.' - default: 'julia-cache' + default: 'julia-cache-${{ github.workflow }}-${{ github.job }}' include-matrix: description: 'Whether to include the matrix values when constructing the cache key' default: 'true' @@ -34,6 +34,9 @@ inputs: delete-old-caches: description: 'Whether to delete old caches for the given key' default: 'true' + token: + description: 'A github PAT. Requires `repo` scope to enable the deletion of old caches' + default: '${{ github.token }}' outputs: cache-hit: @@ -65,7 +68,7 @@ runs: # with a matrix. - id: keys run: | - MATRIX_STRING="${{ join(matrix.*, '-') }}" + [ "${{ inputs.include-matrix }}" == "true" ] && MATRIX_STRING="${{ join(matrix.*, '-') }}" [ -n "$MATRIX_STRING" ] && MATRIX_STRING="-${MATRIX_STRING}" RESTORE_KEY="${{ inputs.cache-name }}-${{ runner.os }}${MATRIX_STRING}_" echo "restore-key=${RESTORE_KEY}" >> $GITHUB_OUTPUT @@ -87,6 +90,10 @@ runs: restore-keys: ${{ steps.keys.outputs.restore-key }} enableCrossOsArchive: false + - name: list restored depot directory sizes + run: du -shc ~/.julia/* || true + shell: bash + # github and actions/cache doesn't provide a way to update a cache at a given key, so we delete any # that match the restore key just before saving the new cache - uses: pyTooling/Actions/with-post-step@adef08d3bdef092282614f3b683897cefae82ee3 @@ -94,10 +101,10 @@ runs: with: # seems like there has to be a `main` step in this action. Could list caches for info if we wanted # main: julia ${{ github.action_path }}/handle_caches.jl "${{ github.repository }}" "list" - main: du -shc ~/.julia/* || true + main: echo "" post: julia ${{ github.action_path }}/handle_caches.jl "${{ github.repository }}" "rm" "${{ steps.keys.outputs.restore-key }}" env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ inputs.token }} - id: hit run: echo "cache-hit=$CACHE_HIT" >> $GITHUB_OUTPUT diff --git a/handle_caches.jl b/handle_caches.jl index 9066712..bdfe32c 100644 --- a/handle_caches.jl +++ b/handle_caches.jl @@ -9,6 +9,7 @@ function handle_caches() run(`gh cache list --limit 100 --repo $repo`) elseif func == "rm" caches = String[] + failed = String[] for _ in 1:5 # limit to avoid accidental rate limiting hits = split(strip(read(`gh cache list --limit 100 --repo $repo`, String)), keepempty=false) search_again = length(hits) == 100 @@ -18,18 +19,37 @@ function handle_caches() for c in hits try run(`gh cache delete $(split(c)[1]) --repo $repo`) + push!(caches, c) catch e @error e + push!(failed, c) end end - append!(caches, hits) search_again || break end - if isempty(caches) + if isempty(failed) && isempty(caches) println("No existing caches found for restore key `$restore_key`") else - println("$(length(caches)) existing caches deleted that match restore key `$restore_key`:") - println.(caches) + if !isempty(failed) + println("Failed to delete $(length(failed)) existing caches for restore key `$restore_key`") + println.(failed) + @info """ + To delete caches you need to grant the following to the default `GITHUB_TOKEN` by adding + this to your yml: + ``` + permissions: + actions: write + contents: read + ``` + (Note this won't work for fork PRs but should once merged) + Or provide a token with `repo` scope via the `token` input option. + See https://cli.github.com/manual/gh_cache_delete + """ + end + if !isempty(caches) + println("$(length(caches)) existing caches deleted that match restore key `$restore_key`:") + println.(caches) + end end else throw(ArgumentError("Unexpected second argument: $func"))