Use the direct provider¶
Not every download source is a forge. A tool distributed from a plain web server,
an internal artefact store, or a CDN has releases but no API to ask about them.
The direct provider covers that case, and ships inside this module because it
is the one provider that will never need a vendor SDK.
It is also the reference implementation: it takes the same credential path, uses the same config layout, and runs the same conformance harness a third-party provider should.
Moved: these settings used to be ReleaseSource.Params
A direct source's own settings now live in its configuration subtree
rather than in a Params map carried on the address. The old keys are no
longer read. There is no compatibility window, and a source that has not
migrated fails at construction with a hint naming the key it now expects.
| Was | Is now |
|---|---|
release_source.params.url_template |
direct.url_template |
release_source.params.checksum_url_template |
direct.checksum_url_template |
release_source.params.signature_url_template |
direct.signature_url_template |
release_source.params.version_url |
direct.version_url |
release_source.params.version_format |
direct.version_format |
release_source.params.version_key |
direct.version_key |
release_source.params.pinned_version |
direct.pinned_version |
release_source.repo (as {tool}) |
direct.tool_name |
The reason is that two direct sources could not previously be told apart: the
provider never reads Host, so every source shared one address and the second
silently got the first one's template. Each source now reads its own
subtree: direct.<name> for a named source, bare direct for the default.
Compose the download URL¶
There is no API, so the asset URL is built from a template. Only
url_template is required:
Placeholders:
| Placeholder | Expands to | Example |
|---|---|---|
{version} |
The version as given | v1.2.3 |
{version_bare} |
Version without a leading v |
1.2.3 |
{os} |
runtime.GOOS, title-cased |
Linux |
{arch} |
runtime.GOARCH, with amd64 → x86_64 |
x86_64 |
{tool} |
The tool name, from the tool_name key; tool when unset |
mytool |
{ext} |
Archive extension, always tar.gz |
tar.gz |
The amd64 → x86_64 substitution matches what GoReleaser and most release
pipelines actually publish, rather than what Go calls the architecture.
{ext} does not vary
It expands to tar.gz unconditionally. There is no .zip branch. A source
publishing Windows archives as .zip cannot be expressed through {ext};
write the extension into the template literally, or publish .tar.gz for
every platform.
Tell it how to find the latest version¶
With no API, "what is the latest release?" needs an answer from somewhere. Two options, and configuring neither is an error, because silently assuming a version would be worse:
Pin it. Useful for a fixed internal distribution:
Point at a version endpoint. The format is auto-detected from the
response's Content-Type; version_format is an override for a server that
sends the wrong one, not the only way to select a parser:
direct:
version_url: "https://dl.example.com/mytool/latest.json"
version_format: "json" # OPTIONAL override: json | yaml | xml
version_key: "tag_name" # which field holds the version
Content-Type |
Parsed as | Version taken from |
|---|---|---|
text/plain (or anything unrecognised) |
Plain text | The whole body, trimmed |
application/json |
JSON | The version_key field |
application/yaml |
YAML | The version_key field |
text/xml, application/xml |
XML | The version_key element |
When version_key is unset (or names a field the document lacks) the parsers
fall back to tag_name, then version, in that order.
Responses each format accepts:
Without either, version checks return ErrVersionUnknown, a sentinel that lives
in the direct package rather than the shared contract, because it names config
keys no forge-backed provider has.
The version endpoint response is capped at 1 MiB.
Checksums and signatures¶
Because there is no asset listing to search, direct composes those URLs too,
opting into both optional capability interfaces:
direct:
checksum_url_template: "https://dl.example.com/{tool}/{version}/checksums.txt"
signature_url_template: "https://dl.example.com/{tool}/{version}/checksums.txt.sig"
Leave either unset and the provider returns ErrNotSupported for it, so the
caller falls back or applies its own policy. Both reads are bounded by the
caller-supplied maxBytes.
Recipes¶
A static site or object store¶
direct:
tool_name: mytool
url_template: "https://dl.example.com/mytool/{version}/mytool_{os}_{arch}.{ext}"
version_url: "https://dl.example.com/mytool/latest.txt"
The version endpoint is a one-line text file the publish step overwrites. Works for S3 static hosting, a CDN, Nexus, Artifactory, or any plain web server.
GitHub releases, without a GitHub provider¶
Useful when you want GitHub's asset hosting but not its API client in your dependency graph. The download URLs are predictable, and the version comes from the public API as plain JSON:
direct:
tool_name: my-tool
url_template: "https://github.com/myorg/my-tool/releases/download/{version}/{tool}_{os}_{arch}.{ext}"
version_url: "https://api.github.com/repos/myorg/my-tool/releases/latest"
version_key: "tag_name"
Unauthenticated GitHub API calls are rate-limited per IP; set DIRECT_TOKEN, or
compose a credential of your own, if that bites.
A pinned internal distribution¶
direct:
tool_name: mytool
url_template: "https://artifacts.corp.internal/mytool/{version}/mytool_{os}_{arch}.{ext}"
pinned_version: "v1.4.2"
No version endpoint, so the tool only ever installs what the pin names. Useful where rollout is controlled by changing configuration rather than by publishing.
Credentials¶
The provider composes three sources, in this order: the standard key, the legacy scalar, then the well-known environment variable.
| Source | Key |
|---|---|
| Standard credential key | direct.auth.value |
| Legacy scalar | direct.token |
| Well-known variable | DIRECT_TOKEN |
direct.auth.value is read from whatever layer supplies it: an env layer, a
keychain layer, a file. That is where the precedence belongs. Or take over the
composition entirely:
The legacy direct.token scalar still works, composed below auth.value, so
existing configuration keeps running.
direct.auth.env is no longer read
A key naming an environment variable was a rung of the resolution ladder
this module removed. Configuration still carrying auth.env or
auth.keychain reports forge.ErrStaleAuthKeys rather than resolving,
but only when nothing else supplied a credential, so a stale key beside a
working DIRECT_TOKEN stays quiet and the token is never read.
Name the variable in your config stack's env layer, or pass
forge.EnvCredential("MY_ARTEFACT_TOKEN"). See
authenticate.
The token travels as an Authorization: Bearer header and, unlike the
forge-backed providers, it is attached to whatever URL the template
produced, with no HostTrusted pin.
It does not survive a redirect leaving that origin. Authorization is
registered as a sensitive header, so it is deleted on any hop where the scheme,
host or port changes. That is stricter than the standard library, which strips
Authorization cross-host but compares the domain and ignores the port. That is defensible here because the
template is operator-supplied rather than release-author-supplied, but it does
mean a template pointing at a third party sends your token there. Point it only
at hosts you control.
What it does not do¶
ListReleases returns ErrNotSupported, because a URL template cannot be
enumerated.
GetReleaseByTag synthesises a release for any tag you ask for, because with no
API there is nothing to check against; a wrong tag surfaces as a 404 at download
time rather than at lookup.
The synthesised release exposes exactly one asset, and its name is
<tag>.tar.gz, so v1.2.3.tar.gz, whatever filename url_template produces.
Code that finds an asset by matching a filename convention will not find this
one; take the single asset the release gives you.
There is no Repositories, Contents, Sites, Issues or IssueFiler
support, and there never will be. A web server is not a forge.
Related¶
- Authenticate to a forge
- Verify checksums and signatures
- Configuration: every key with its default
- What forge does not do