Skip to content

Providers

A provider teaches forge to talk to one release backend. Each ships as its own module — except direct, which is not a forge — so a tool links only the clients it actually uses.

Enable one with a blank import; the registry does the rest:

import (
    "gitlab.com/phpboyscout/go/forge"

    _ "gitlab.com/phpboyscout/go/forge-gitlab"
)

factory, err := forge.Lookup("gitlab")

Your code never names a provider type. See backend agnosticism.

Capability matrix

Provider Source types Module Vendor SDK
GitHub github go/forge-github go-github/v88
GitLab gitlab go/forge-gitlab gitlab-org/api/client-go/v2
Gitea / Codeberg gitea, codeberg go/forge-gitea gitea.dev/sdk
Bitbucket bitbucket go/forge-bitbucket none
Direct direct ships in go/forge none
Provider GetReleaseByTag ListReleases ChecksumProvider SignatureProvider Can return a redirect
GitHub yes (CDN)
GitLab no
Gitea / Codeberg no
Bitbucket ErrNotSupported ErrNotSupported no
Direct (synthesised) ErrNotSupported (opt-in) (opt-in) no

The redirect column matters because callers are told to refuse a non-empty redirect from DownloadReleaseAsset rather than follow it. Only GitHub ever populates it, when its API redirects an asset request to a CDN.

What the contract flattens

A single interface over five platforms cannot preserve every distinction. Two worth knowing, because code branching on them misbehaves silently rather than failing:

Accessor Caveat
GetDraft() Always false on GitLab — the platform has no draft-release concept, so there is nothing to report. GitHub and Gitea return the real value.
GetLatestRelease() GitHub, GitLab and Gitea have a dedicated endpoint. Bitbucket and Direct synthesise a release, so "latest" means newest by the rule in each section below.

See also the honest limits in backend agnosticism.

A dash means the provider does not implement the optional interface, so callers fall back to locating the file by name among the release assets — which is how most releases publish checksums. That fallback is the normal path, not a degraded one. See optional capabilities.


GitLab

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

Registers the source type gitlab. Works against gitlab.com and any self-hosted instance.

Configuration

Key Purpose
gitlab.auth.env Name of an environment variable holding the token
gitlab.auth.keychain OS keychain reference, service/account
gitlab.auth.value Literal token — weakest tier, avoid in committed config
gitlab.url.api Override the API endpoint outright
GITLAB_TOKEN Well-known fallback, consulted last

The endpoint is derived from ReleaseSource.Host:

Host API base
(empty) https://gitlab.com/api/v4
git.example.com https://git.example.com/api/v4

gitlab.url.api overrides both. See authenticate for the resolution order.

Credentials are optional

A public project resolves unauthenticated. A token raises rate limits and is required for private projects.

Security

The token travels as a PRIVATE-TOKEN header, and is attached only to asset downloads on the configured instance — host, port and scheme must all match. Asset URLs come from release metadata that a release author controls, so an unpinned credential is an exfiltration primitive. See credential pinning.


GitHub

import _ "gitlab.com/phpboyscout/go/forge-github"

Registers the source type github. Works against github.com and GitHub Enterprise.

Configuration

Key Purpose
github.auth.env Name of an environment variable holding the token
github.auth.keychain OS keychain reference, service/account
github.auth.value Literal token — weakest tier
github.url.api Override the API endpoint
github.url.upload Override the asset-upload endpoint
GITHUB_TOKEN Well-known fallback

For Enterprise, set ReleaseSource.Host and both endpoints are derived (/api/v3/ and /api/uploads/). Setting url.api alone still derives a matching upload endpoint, so assets never target the wrong host.

Credentials are optional; public repositories resolve unauthenticated.

Releases only

GitHub's wider API — pull requests, repository creation, SSH keys, file contents, device login — exists in go-tool-base and was deliberately not extracted. Shipping it for one forge would give GitHub capabilities the others lack, which is the disparity this module's design exists to prevent. Restoring those across every provider in lockstep is specified separately.


Bitbucket

import _ "gitlab.com/phpboyscout/go/forge-bitbucket"

Registers the source type bitbucket, over the Downloads API — a file bucket rather than a release system. It is a deliberately partial provider: there are no tags and no release list, so GetReleaseByTag and ListReleases return ErrNotSupported and callers fall back.

It is the only provider implementing both optional interfaces, locating checksums.txt and checksums.txt.sig by exact filename — because without a release object there is nothing else to hang a manifest on.

Configuration

Bitbucket needs two credentials, not a single token:

Key Purpose
bitbucket.username.env / bitbucket.app_password.env Names of environment variables holding each field
bitbucket.keychain OS keychain entry holding a JSON blob with both fields
bitbucket.username / bitbucket.app_password Literal values — weakest tier
BITBUCKET_USERNAME / BITBUCKET_APP_PASSWORD Well-known fallbacks

One keychain entry rather than two, because a username and app password are useless apart. Version detection reads the version from asset filenames; override with the filename_pattern param.

Credentials travel as basic auth, which makes the scheme check in credential pinning matter more here than elsewhere.


Gitea and Codeberg

import _ "gitlab.com/phpboyscout/go/forge-gitea"

Registers two source types from one implementation. Codeberg is a Gitea instance, so they differ only in a pre-seeded host and which variable supplies the token:

Source type Host Token fallback
gitea from ReleaseSource.Host GITEA_TOKEN
codeberg pre-seeded https://codeberg.org CODEBERG_TOKEN

Splitting them into separate modules would mean duplicating the provider to change two constants.

Configuration

Key Purpose
gitea.auth.env Name of an environment variable holding the token
gitea.auth.keychain OS keychain reference, service/account
gitea.auth.value Literal token — weakest tier
gitea.url.api Override the API endpoint
GITEA_TOKEN / CODEBERG_TOKEN Well-known fallbacks

ReleaseSource.Host selects the instance. Credentials are optional; public repositories resolve unauthenticated.

api_version is inert

An earlier hand-rolled client built the API path itself, so the api_version param could pin an instance to a non-v1 API. The provider now uses the official SDK, which targets v1 — the only version Gitea has ever shipped — so the param is ignored rather than rejected.

Security

The token travels as an Authorization header, attached only to asset downloads on the configured instance. See credential pinning.


Direct

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

Registers the source type direct. Not a forge: a plain download source — a web server, an artefact store, a CDN — with releases but no API to ask about them. It ships inside the core module because it will never need a vendor SDK, and doubles as the contract's reference implementation.

Because there is no API, the URLs are composed from templates and the latest version has to be told to it. Full setup is in use the direct provider; the keys are:

Params key Required Purpose
url_template yes Asset URL, with {version} {version_bare} {os} {arch} {tool} {ext}
pinned_version one of A fixed version
version_url one of Endpoint naming the latest version
version_format json, yaml, xml; plain text when omitted
version_key Field holding the version, for structured formats
checksum_url_template Opts into ChecksumProvider
signature_url_template Opts into SignatureProvider
Config key Purpose
direct.auth.env / .keychain / .value Standard credential chain
direct.token Legacy literal — still honoured, superseded by auth.*
DIRECT_TOKEN Well-known fallback

Neither pinned_version nor version_url set means a version check returns ErrVersionUnknown — assuming a version silently would be worse. The token travels as a Bearer header.


ReleaseSource.Private

Setting Private: true makes a missing credential a fast failure rather than an unauthenticated attempt. Leave it false and a provider with no resolvable token proceeds anonymously, which is correct for a public repository and gives a clearer error than a late 404 when the repository is private.

Registry API

forge.RegisteredTypes()      // sorted []string of every registered source type
forge.Registered("gitlab")   // is this type registered?
forge.Unregister("gitlab")   // deliberately replace a built-in

RegisteredTypes is what Lookup's error message lists when a type is unknown — usually diagnosing a missing blank import. Constants exist for the first-party set (forge.SourceTypeGitHub and friends), but the registry accepts any string, so they are a convenience rather than a constraint.

Adding your own

All first-party providers ship: GitHub, GitLab, Gitea, Codeberg, Bitbucket and plain download sources.

Your own is a first-class peer, not a workaround — the registry takes any string key, and nothing needs contributing here. See author a provider.