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
44 changes: 21 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ Installs the [mago](https://github.com/carthage-software/mago) PHP toolchain in
- run: mago lint
```

Pin by commit SHA, which is what GitHub recommends for third-party actions. A tag is a ref this
repository can repoint at any commit, so anyone who takes over the repository reaches every
workflow that follows one; a SHA names the code you reviewed. No moving major tag is published
here, for that reason.
> [!NOTE]
> No moving major tag is published here, so pin by commit SHA, as GitHub recommends for
> third-party actions.

## Inputs

Expand All @@ -22,18 +21,24 @@ here, for that reason.
| `version` | detected | Version to install, without a leading `v` (e.g. `1.29.0`), or `latest`. Unset means: read the project's composer files. |
| `working-directory` | workspace | Where the composer files are read from when `version` is unset. |
| `sha256` | — | Expected sha256 of the release archive. Verified before install when set, and can be stated in `composer.json` instead. |
| `token` | — | Authenticates the git request that lists tags, and nothing else. |
| `token` | — | Authenticates the `git ls-remote` that lists mago's tags. |

## Outputs

| Output | Description |
|---|---|
| `version` | The version installed, with `latest` and any range resolved. |
| `sha256` | The checksum the archive was verified against, empty when none was stated. |

## Version and checksum

With no `version`, the version comes from the project itself, in this order: `extra.mago-version`
in `composer.json`, then the `carthage-software/mago` entry in `composer.lock`, then the one in
`composer.json`, and the latest release if the project states none. A requirement may be a range
(`^1.29`, `~1.29.0`, `>=1.0 <2.0`, `1.29.*`, `a || b`), and then the newest release inside it is
installed.
`composer.json`, and the latest release if the project states none.

A requirement may be a range — `^1.29`, `~1.29.0`, `1.29.*`, `>=1.0 <2.0`, `a || b` — and then the
newest release inside it is installed. `!=` and hyphen ranges are not read; a constraint using one
resolves to the latest release with a warning.

`extra` comes first because a project that installs mago from its release archive rather than
through composer has nowhere else to put the version — and it can put the checksum beside it:
Expand Down Expand Up @@ -65,20 +70,6 @@ installs on more than one states it per target triple:
A stated checksum is only ever used for the version stated next to it, so a workflow that pins a
different `version` does not get it applied to the wrong archive. The `sha256` input wins over it.

None of that spends REST API quota. `latest` is read from where the releases page redirects to,
which names the tag, and a range is matched against `git ls-remote --tags`, which is the git
protocol. The REST API would answer the same questions out of the 1,000/hour budget every workflow
run in the repository shares.

That leaves anonymous requests, which GitHub counts against the runner's shared IP. `token` is
there for the one request where authenticating helps — the tag listing — and is unnecessary
otherwise: the release download and the `latest` redirect are not API calls and take no
credentials. A token GitHub rejects costs a warning and an anonymous retry, not the install.

Constraints are read as ranges: `^`, `~`, `x.y.*`, `>=`/`>`/`<=`/`<`/`=`, several of those side by
side, and `||` between alternatives. `!=` and hyphen ranges are not read — a constraint using them
resolves to the latest release with a warning.

Pinning the checksum is worth it where the version is pinned anyway:

```yaml
Expand All @@ -101,7 +92,14 @@ workflow run in a repository, so a burst of pull requests can exhaust it and fai

This action constructs the asset URL directly and fetches it from the release CDN, which is not the
REST API, so it uses no quota. The binary is cached by target triple and version, so a hit skips
the download too.
the download too. Working out which version to install costs nothing either: `latest` is read from
where the releases page redirects to, and a range is matched against `git ls-remote --tags`, which
is the git protocol.

Those requests are anonymous, and GitHub counts anonymous traffic against the runner's shared IP.
`token` is there for the tag listing, the one request where authenticating helps; the release
download and the `latest` redirect are not API calls and take no credentials. A token GitHub
rejects costs a warning and an anonymous retry, not the install.

It is a composite action on purpose: the work is `curl`, `tar`, a checksum and some shell to read a
version out of the composer files, none of which needs a JavaScript runtime, a bundled
Expand Down
19 changes: 7 additions & 12 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ inputs:
default: ""
token:
description: >-
Used for one thing: authenticating the git request that lists mago's tags, which happens
only when a version range has to be resolved. Optional -- without it that request is
anonymous, which is fine until the runner's shared IP is throttled. Nothing here goes
through the GitHub REST API, so nothing else needs authenticating.
Authenticates the git request that lists mago's tags, which happens only when a version
range has to be resolved. Optional -- without it that request is anonymous, which is fine
until the runner's shared IP is throttled.
required: false
default: ""
sha256:
Expand Down Expand Up @@ -108,18 +107,14 @@ runs:
if [ -n "$sha256" ]; then echo "composer.json states a checksum for this archive"; fi
fi

# Each pin gets its own directory, so two installs in one job never share a path. Sharing
# one would let actions/cache save whatever an earlier install left there under this
# install's key, and a later run restoring that key would skip the download -- and with it
# the checksum -- for a binary this pin never verified.
# Each pin gets its own directory and, below, its own key, so a run never gets handed back
# a binary that was verified against a different checksum, or against none.
dir="$HOME/.cache/setup-mago/$triple/$version/${sha256:-unpinned}"
native=$dir
if [ "$OS" = Windows ]; then native=$(cygpath -w "$dir"); fi

# The checksum is part of the cache key, so changing a pin re-downloads and re-verifies
# rather than handing back what an earlier, differently-pinned run cached. The leading 2
# is the layout, not the tool: entries written under the old shared path are not
# restorable into this one and must not be reachable by key.
# The leading 2 is the layout, not the tool: entries written under the old shared path are
# not restorable into this one and must not be reachable by key.
{
echo "cache-key=mago-2-$triple-$version-$sha256"
echo "triple=$triple"
Expand Down
Loading