Skip to main content

Crate scaleset

Crate scaleset 

Source
Expand description

A client for the GitHub Actions Runner Scale Set APIs.

Scale sets let you autoscale self-hosted runners on any infrastructure, without Kubernetes or actions-runner-controller. This crate covers the whole API: scale sets, just-in-time (JIT) runner configs, and the message sessions that tell you how many runners are needed. It follows the protocol of GitHub’s Go client, actions/scaleset.

§Overview

  1. Build a Client with a GitHub App (Credentials::github_app, preferred) or a personal access token.
  2. Create a scale set with Client::create_runner_scale_set. Its name is the label workflows target with runs-on.
  3. Run a Listener with your Scaler. It opens the message session, acquires jobs, acknowledges messages and recovers from failures.
  4. In the scaler, keep total_assigned_jobs runners available: start each with a config from Client::generate_jit_config.
use std::collections::HashSet;

use scaleset::listener::{Event, Listener, Scaler};
use scaleset::{
    BoxError, Client, Credentials, ExposeSecret, JitRunnerSetting, RunnerScaleSetSpec,
    ScaleSetId,
};

/// Keeps one runner for every job assigned to the scale set.
struct Autoscaler {
    client: Client,
    scale_set_id: ScaleSetId,
    /// Runners started and not finished yet.
    runners: HashSet<String>,
}

impl Scaler for Autoscaler {
    type Error = BoxError;

    async fn scale(&mut self, event: Event<'_>) -> Result<(), BoxError> {
        // Runners are ephemeral: each one leaves when its job completes.
        if let Some(message) = event.message() {
            for job in message.jobs_completed() {
                if let Some(name) = &job.runner_name {
                    self.runners.remove(name);
                }
            }
        }
        let wanted = usize::try_from(event.statistics().total_assigned_jobs)?;
        while self.runners.len() < wanted {
            let setting = JitRunnerSetting::unique("runner");
            let jit = self.client.generate_jit_config(self.scale_set_id, &setting).await?;
            // Start a runner (process, container, VM) with
            // ACTIONS_RUNNER_INPUT_JITCONFIG set to this value.
            let _config = jit.encoded_jit_config.expose_secret();
            self.runners.insert(setting.name);
        }
        Ok(())
    }
}

let pem = std::fs::read_to_string("app.pem")?;
let client = Client::builder(
    "https://github.com/my-org",
    Credentials::github_app("Iv1.abc123", 12345, &pem)?,
)
.build()?;

let scale_set = client
    .create_runner_scale_set(&RunnerScaleSetSpec::new("my-scale-set"))
    .await?;

let mut scaler = Autoscaler { client: client.clone(), scale_set_id: scale_set.id, runners: HashSet::new() };
Listener::new(client.clone(), scale_set.id, 10)
    .run(&mut scaler, async { let _ = tokio::signal::ctrl_c().await; })
    .await?;

client.delete_runner_scale_set(scale_set.id).await?;

§Layers

LayerUse it to
Listener + Scalerrun an autoscaler; the usual choice
Subscriptiondrive the poll / acknowledge loop yourself
MessageSessioncall the message queue endpoints directly
Clientmanage scale sets, runners and JIT configs

§Transport and retries

The HTTP client is yours (ClientBuilder::http_client, with defaults from http_client_builder); retries belong to the SDK (RetryPolicy). Transport errors, 5xx other than 501, and 429 are retried four times with jittered exponential backoff by default. Rate-limited responses come back as a RateLimitError (see Error::rate_limit) and are retried according to RateLimitPolicy. Client::with_retry_policy changes the policy for some calls only.

§Testing

With the testing feature, testing::Emulator runs a stateful emulator of GitHub and the Actions service in your test, so an autoscaler can be tested end to end without network access.

§Logging

The crate logs through tracing; each HTTP request runs in a scaleset.request span. Install a subscriber to see token refreshes, polls and retries.

§Features

  • rustls (default): TLS through rustls.
  • native-tls: TLS through the platform’s library.
  • testing: testing::Emulator and the scaleset-emulator binary.

Enable rustls or native-tls; without TLS the client cannot connect.

Re-exports§

pub use reqwest;
pub use secrecy;

Modules§

listener
Turning a scale set’s message queue into scaling decisions.
testingtesting
A stateful emulator of the scale set APIs, for tests.

Structs§

ActionsException
An exception returned by the Actions service.
ApiError
An unsuccessful response from GitHub or the Actions service.
Client
A client for the GitHub Actions Runner Scale Set APIs.
ClientBuilder
Builds a Client. See Client::builder.
Credentials
Credentials for the scale set APIs.
GitHubConfigUrl
A parsed GitHub configuration URL: an enterprise, organization or repository on github.com, GHE.com or GitHub Enterprise Server.
JitRunnerConfig
A just-in-time runner configuration.
JitRunnerSetting
Settings for a just-in-time runner.
JobAssigned
A job was assigned to the scale set.
JobAvailable
A job is available to the scale set.
JobCompleted
A job finished, or its assignment was canceled before a runner took it.
JobInfo
Details shared by all job messages.
JobRequestId
Identifies a job request (runnerRequestId), as passed to MessageSession::acquire_jobs.
JobStarted
A runner started a job.
Label
A label workflows use in runs-on to target a scale set.
MessageId
Identifies a message in a session’s message queue.
MessageSession
A message session on a scale set’s message queue.
RateLimitError
A rate-limited response: any 429, or a 403 with Retry-After, X-RateLimit-Remaining: 0, or a secondary-rate-limit message.
RateLimitRule
Limits the retries for one kind of rate limit. See RateLimitPolicy::rules.
RetryPolicy
How the SDK retries a request.
RunnerGroup
A runner group.
RunnerGroupId
Identifies a runner group.
RunnerId
Identifies a registered runner.
RunnerReference
A registered runner.
RunnerScaleSet
A runner scale set as returned by the service.
RunnerScaleSetSpec
The desired state of a scale set, for Client::create_runner_scale_set and Client::update_runner_scale_set.
RunnerSetting
Runner settings of a scale set.
ScaleSetId
Identifies a runner scale set.
ScaleSetMessage
A batch of job messages from the message queue.
ScaleSetStatistics
Job and runner counts of a scale set.
SessionId
Identifies a message session.
SessionInfo
A message session, as created or refreshed by the service.
SignerJwtProvider
Builds GitHub App JWTs and has an Rs256Signer sign them, so the private key can stay in a KMS or HSM.
SystemInfo
Describes the system using the client. It is sent, with the crate version, as a JSON User-Agent.

Enums§

ApiErrorKind
What an ApiError means, beyond its HTTP status.
Error
Errors returned by this crate.
JobMessage
One job message in a ScaleSetMessage.
RateLimitKind
Which rate limit rejected a request.
RateLimitPolicy
How rate-limited responses are retried.
Scope
The level at which a scale set is registered.

Constants§

DEFAULT_RUNNER_GROUP
Name of the default runner group.
FORCE_GHES_ENV
Environment variable that forces GitHub Enterprise Server URL handling, even for hosts that look like github.com.
LABEL_TYPE_SYSTEM
Label type the service assigns to labels created through this API.
MESSAGE_POLL_TIMEOUT
How long one message poll attempt may take. The service holds an idle poll for about 50 seconds; this leaves room above that. Each retry gets a new window.

Traits§

ExposeSecret
Re-exported for SecretString fields such as JitRunnerConfig::encoded_jit_config. Expose a reference to an inner secret
JwtProvider
Creates short-lived JWTs for GitHub App authentication.
Rs256Signer
Signs data with RSASSA-PKCS1-v1_5 and SHA-256 (the JWT RS256 algorithm), for keys that never leave a KMS or HSM.

Functions§

http_client_builder
Returns a reqwest::ClientBuilder with the SDK’s defaults: a 5-minute timeout per attempt, a 30-second connect timeout, a 90-second idle pool timeout, TCP keepalive, TLS 1.2 or later, and no redirects (the scale set APIs never redirect, so one is treated as an error rather than followed with credentials attached).

Type Aliases§

BoxError
A boxed error, as returned by user-supplied hooks such as a JwtProvider.
Result
Result with this crate’s Error.
SecretString
Re-exported for SecretString fields such as JitRunnerConfig::encoded_jit_config. Secret string type.