Skip to content

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.

import _ "gitlab.com/phpboyscout/go/forge/direct"

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:

direct:
  url_template: "https://dl.example.com/{tool}/{version}/{tool}_{os}_{arch}.{ext}"

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 amd64x86_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 amd64x86_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:

direct:
  pinned_version: "v1.2.3"

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:

# text/plain
v1.2.3
{"tag_name": "v1.2.3", "prerelease": false}
version: v1.2.3
<release><version>v1.2.3</version></release>

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:

settings.Credential = forge.StaticCredential(tokenFromMyOwnLoginFlow)

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.