Skip to content

Point a tool at a release source

Embed the config type

ReleaseSourceConfig is Deserialize and Serialize, so drop it into your own config struct:

#[derive(serde::Deserialize)]
struct ToolConfig {
    release: rtb_forge::ReleaseSourceConfig,
}

A user's YAML then looks like this:

release:
  source_type: github
  owner: phpboyscout
  repo: rust-tool-base

The source_type key selects the variant, and the rest of the keys belong to that variant's parameter struct. A typo in a required key name fails at deserialise time with a serde error naming the field (missing field `owner`). A typo in an optional key does not — unknown keys are accepted and ignored, so a mistyped timeout_seconds silently leaves the default in place. Every key, default and validation rule is in Release source configuration.

Resolve it to a provider

Two steps: find the factory, then call it.

use rtb_forge::{lookup, registered_types, ReleaseProvider};
use secrecy::SecretString;
use std::sync::Arc;

fn build_provider(
    cfg: &rtb_forge::ReleaseSourceConfig,
    token: Option<SecretString>,
) -> anyhow::Result<Arc<dyn ReleaseProvider>> {
    let factory = lookup(cfg.source_type()).ok_or_else(|| {
        anyhow::anyhow!(
            "no release backend for `{}`; this build supports: {}",
            cfg.source_type(),
            registered_types().join(", ")
        )
    })?;
    Ok(factory(cfg, token)?)
}

Always render registered_types() in that error. The set of available backends depends on the Cargo features this binary was built with, so a hard-coded list in the message will be wrong on a trimmed build.

cfg.source_type() returns the right discriminator for every variant, including Custom — where it returns the nested type value rather than the literal string custom.

Supply a token

The second argument is Option<SecretString>. Pass None for public repositories. For private ones, resolve the secret however your tool already does — rtb-credentials if you use the toolkit:

use rtb_credentials::{CredentialRef, Resolver};

let cref = CredentialRef {
    env: Some("GITHUB_TOKEN".into()),
    ..Default::default()
};
let token = Resolver::with_platform_default().resolve(&cref).await?;
let provider = build_provider(&cfg.release, Some(token))?;

Note that the private: true key in config does not do this for you, and does not do anything else either — see What rtb-forge does not do.

Reach a self-hosted instance

GitLab and Gitea take a bare host; the API path is appended for you.

release:
  source_type: gitea
  host: git.example.com          # required — Gitea has no default host
  owner: platform
  repo: deploy-tool

GitHub Enterprise takes the instance host and gets /api/v3 appended:

release:
  source_type: github
  host: github.example.com       # becomes github.example.com/api/v3
  owner: platform
  repo: deploy-tool

If your instance serves its API somewhere else, write the full path — a host containing /api/ is passed through untouched.

All hosts must be HTTPS. An http:// prefix is a configuration error, and there is no config-file switch to relax it.

Reach Codeberg, or another Gitea instance

Codeberg has its own source_type and takes no host:

release:
  source_type: codeberg
  owner: platform
  repo: deploy-tool

The host is fixed at codeberg.org. For any other Gitea instance use source_type: gitea and set host — it is the same backend underneath.

Serve releases from a bucket or mirror

When there is no forge API at all, use the direct backend. It needs a URL that returns the current version, and a template for the asset URL:

release:
  source_type: direct
  version_url: https://downloads.example.com/deploy-tool/stable.txt
  asset_url_template: https://downloads.example.com/deploy-tool/{version}/deploy-tool-{target}{ext}

version_url may return the version as plain text, or as JSON with a version string at the root. The template must contain {version}; {target}, {os}, {arch} and {ext} are also substituted.

{target} only resolves for x86_64 and aarch64 on Linux, macOS and Windows. On any other host it becomes the empty string, producing a URL that looks fine and 404s. If you ship for musl, 32-bit ARM or a BSD, build the filename from {os} and {arch} instead:

  asset_url_template: https://downloads.example.com/deploy-tool/{version}/deploy-tool-{os}-{arch}{ext}

Pin to a known version

Set pinned_version on a direct source and the version URL is never fetched:

release:
  source_type: direct
  version_url: https://downloads.example.com/deploy-tool/stable.txt
  asset_url_template: https://downloads.example.com/deploy-tool/{version}/tool{ext}
  pinned_version: "1.4.2"

latest_release() then returns 1.4.2 without any network call, and release_by_tag() returns NotFound for anything else. This is the only backend with a pinning mechanism.

Set a timeout

Every backend accepts timeout_seconds, defaulting to 30:

release:
  source_type: github
  owner: phpboyscout
  repo: rust-tool-base
  timeout_seconds: 10

0 does not mean "immediate" — it removes the timeout entirely, so a request to an unresponsive host can hang indefinitely. If you want a short timeout, write a short timeout.