mirror of
https://codeberg.org/Toasterson/solstice-ci.git
synced 2026-04-10 21:30:41 +00:00
Add public runner URL configuration and enhance log streaming support
- Introduce options for specifying public runner base URLs (`SOLSTICE_RUNNER_BASE_URL`) and orchestrator contact addresses (`ORCH_CONTACT_ADDR`). - Update `.env.sample` and `compose.yml` with new configuration fields for external log streaming and runner binary serving. - Refactor runner URL handling and generation logic for improved flexibility. - Enhance `cloud-init` templates with updated runner URL environment variables (`RUNNER_SINGLE` and `RUNNER_URLS`). - Add unit tests for runner URL generation to verify various input cases. Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
parent
1e48b1de66
commit
930efe547f
4 changed files with 84 additions and 19 deletions
|
|
@ -49,6 +49,16 @@ struct Opts {
|
||||||
#[arg(long, env = "GRPC_ADDR", default_value = "0.0.0.0:50051")]
|
#[arg(long, env = "GRPC_ADDR", default_value = "0.0.0.0:50051")]
|
||||||
grpc_addr: String,
|
grpc_addr: String,
|
||||||
|
|
||||||
|
/// Public contact address for runners to stream logs to (host:port). Overrides detection.
|
||||||
|
#[arg(long = "orch-contact-addr", env = "ORCH_CONTACT_ADDR")]
|
||||||
|
orch_contact_addr: Option<String>,
|
||||||
|
|
||||||
|
/// Public base URL where runner binaries are served (preferred). Example: https://runner.svc.domain
|
||||||
|
/// The orchestrator will append /runners/{filename} to construct full URLs.
|
||||||
|
#[arg(long = "runner-base-url", env = "SOLSTICE_RUNNER_BASE_URL")]
|
||||||
|
runner_base_url: Option<String>,
|
||||||
|
|
||||||
|
|
||||||
/// Postgres connection string (if empty, persistence is disabled)
|
/// Postgres connection string (if empty, persistence is disabled)
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
|
|
@ -152,9 +162,9 @@ async fn main() -> Result<()> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Orchestrator contact address for runner to dial back (auto-detect if not provided)
|
// Orchestrator contact address for runner to dial back (auto-detect if not provided)
|
||||||
let orch_contact = match std::env::var("ORCH_CONTACT_ADDR") {
|
let orch_contact = match &opts.orch_contact_addr {
|
||||||
Ok(v) => v,
|
Some(v) if !v.trim().is_empty() => v.clone(),
|
||||||
Err(_) => detect_contact_addr(&opts),
|
_ => detect_contact_addr(&opts),
|
||||||
};
|
};
|
||||||
info!(contact = %orch_contact, "orchestrator contact address determined");
|
info!(contact = %orch_contact, "orchestrator contact address determined");
|
||||||
|
|
||||||
|
|
@ -167,12 +177,14 @@ async fn main() -> Result<()> {
|
||||||
.rsplit(':')
|
.rsplit(':')
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or("8081");
|
.unwrap_or("8081");
|
||||||
let base = format!("http://{}:{}/runners", http_host, http_port);
|
let base = format!("http://{}:{}", http_host, http_port);
|
||||||
let linux_url = format!("{}/{}", base, "solstice-runner-linux");
|
let (single_url, multi_urls) = build_runner_urls(&base);
|
||||||
let illumos_url = format!("{}/{}", base, "solstice-runner-illumos");
|
// Log concrete OS URLs for local serving
|
||||||
let single_url = format!("{}/{}", base, "solstice-runner");
|
let mut parts = multi_urls.split_whitespace();
|
||||||
|
let linux_url = parts.next().unwrap_or("");
|
||||||
|
let illumos_url = parts.next().unwrap_or("");
|
||||||
info!(linux = %linux_url, illumos = %illumos_url, "serving runner binaries via orchestrator HTTP");
|
info!(linux = %linux_url, illumos = %illumos_url, "serving runner binaries via orchestrator HTTP");
|
||||||
(single_url, format!("{} {}", linux_url, illumos_url))
|
(single_url, multi_urls)
|
||||||
} else {
|
} else {
|
||||||
(String::new(), String::new())
|
(String::new(), String::new())
|
||||||
};
|
};
|
||||||
|
|
@ -195,9 +207,20 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Determine runner URLs to inject into cloud-init
|
// Determine runner URLs to inject into cloud-init (prefer base URL)
|
||||||
let runner_url_env = std::env::var("SOLSTICE_RUNNER_URL").unwrap_or_else(|_| runner_url_default.clone());
|
let (runner_url_env, runner_urls_env) = if let Some(base) = opts
|
||||||
let runner_urls_env = std::env::var("SOLSTICE_RUNNER_URLS").unwrap_or_else(|_| runner_urls_default.clone());
|
.runner_base_url
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.trim())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
{
|
||||||
|
let (u1, u2) = build_runner_urls(base);
|
||||||
|
info!(base = %base, url = %u1, urls = %u2, "using public runner base URL");
|
||||||
|
(u1, u2)
|
||||||
|
} else {
|
||||||
|
// Fall back to URLs served by this orchestrator's HTTP (when RUNNER_DIR configured)
|
||||||
|
(runner_url_default.clone(), runner_urls_default.clone())
|
||||||
|
};
|
||||||
|
|
||||||
// Consumer: enqueue and ack-on-accept
|
// Consumer: enqueue and ack-on-accept
|
||||||
let cfg_clone = cfg.clone();
|
let cfg_clone = cfg.clone();
|
||||||
|
|
@ -435,6 +458,20 @@ fn extract_attr_value<'a>(tag: &'a str, key: &'a str) -> Option<&'a str> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_runner_urls(base: &str) -> (String, String) {
|
||||||
|
// Normalize base and ensure it ends with /runners
|
||||||
|
let trimmed = base.trim_end_matches('/');
|
||||||
|
let base = if trimmed.ends_with("/runners") {
|
||||||
|
trimmed.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}/runners", trimmed)
|
||||||
|
};
|
||||||
|
let single = format!("{}/{}", base, "solstice-runner");
|
||||||
|
let linux = format!("{}/{}", base, "solstice-runner-linux");
|
||||||
|
let illumos = format!("{}/{}", base, "solstice-runner-illumos");
|
||||||
|
(single, format!("{} {}", linux, illumos))
|
||||||
|
}
|
||||||
|
|
||||||
fn make_cloud_init_userdata(
|
fn make_cloud_init_userdata(
|
||||||
repo_url: &str,
|
repo_url: &str,
|
||||||
commit_sha: &str,
|
commit_sha: &str,
|
||||||
|
|
@ -461,8 +498,8 @@ write_files:
|
||||||
echo "Solstice: bootstrapping workflow runner for {sha}" | tee /dev/console
|
echo "Solstice: bootstrapping workflow runner for {sha}" | tee /dev/console
|
||||||
RUNNER="/usr/local/bin/solstice-runner"
|
RUNNER="/usr/local/bin/solstice-runner"
|
||||||
# Runner URL(s) provided by orchestrator (local dev) if set
|
# Runner URL(s) provided by orchestrator (local dev) if set
|
||||||
export SOLSTICE_RUNNER_URL='{runner_url}'
|
RUNNER_SINGLE='{runner_url}'
|
||||||
export SOLSTICE_RUNNER_URLS='{runner_urls}'
|
RUNNER_URLS='{runner_urls}'
|
||||||
if [ ! -x "$RUNNER" ]; then
|
if [ ! -x "$RUNNER" ]; then
|
||||||
mkdir -p /usr/local/bin
|
mkdir -p /usr/local/bin
|
||||||
# Helper to download from a URL to $RUNNER
|
# Helper to download from a URL to $RUNNER
|
||||||
|
|
@ -481,12 +518,12 @@ write_files:
|
||||||
}}
|
}}
|
||||||
OS=$(uname -s 2>/dev/null || echo unknown)
|
OS=$(uname -s 2>/dev/null || echo unknown)
|
||||||
# Prefer single URL if provided
|
# Prefer single URL if provided
|
||||||
if [ -n "$SOLSTICE_RUNNER_URL" ]; then
|
if [ -n "$RUNNER_SINGLE" ]; then
|
||||||
fetch_runner "$SOLSTICE_RUNNER_URL" || true
|
fetch_runner "$RUNNER_SINGLE" || true
|
||||||
fi
|
fi
|
||||||
# If still missing, iterate URLs with a basic OS-based preference
|
# If still missing, iterate URLs with a basic OS-based preference
|
||||||
if [ ! -x "$RUNNER" ] && [ -n "$SOLSTICE_RUNNER_URLS" ]; then
|
if [ ! -x "$RUNNER" ] && [ -n "$RUNNER_URLS" ]; then
|
||||||
for U in $SOLSTICE_RUNNER_URLS; do
|
for U in $RUNNER_URLS; do
|
||||||
case "$OS" in
|
case "$OS" in
|
||||||
Linux)
|
Linux)
|
||||||
echo "$U" | grep -qi linux || continue ;;
|
echo "$U" | grep -qi linux || continue ;;
|
||||||
|
|
@ -498,8 +535,8 @@ write_files:
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
# As a final fallback, try all URLs regardless of OS tag
|
# As a final fallback, try all URLs regardless of OS tag
|
||||||
if [ ! -x "$RUNNER" ] && [ -n "$SOLSTICE_RUNNER_URLS" ]; then
|
if [ ! -x "$RUNNER" ] && [ -n "$RUNNER_URLS" ]; then
|
||||||
for U in $SOLSTICE_RUNNER_URLS; do
|
for U in $RUNNER_URLS; do
|
||||||
fetch_runner "$U" && break || true
|
fetch_runner "$U" && break || true
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
@ -549,6 +586,19 @@ mod tests {
|
||||||
assert!(m.get("other").is_none());
|
assert!(m.get("other").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_runner_urls_variants() {
|
||||||
|
let (s, m) = super::build_runner_urls("https://runner.svc.example");
|
||||||
|
assert_eq!(s, "https://runner.svc.example/runners/solstice-runner");
|
||||||
|
assert_eq!(m, "https://runner.svc.example/runners/solstice-runner-linux https://runner.svc.example/runners/solstice-runner-illumos");
|
||||||
|
let (s2, m2) = super::build_runner_urls("https://runner.svc.example/");
|
||||||
|
assert_eq!(s2, s);
|
||||||
|
assert_eq!(m2, m);
|
||||||
|
let (s3, m3) = super::build_runner_urls("https://runner.svc.example/runners");
|
||||||
|
assert_eq!(s3, s);
|
||||||
|
assert_eq!(m3, m);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_make_cloud_init_userdata_includes_fields() {
|
fn test_make_cloud_init_userdata_includes_fields() {
|
||||||
let req_id = uuid::Uuid::new_v4();
|
let req_id = uuid::Uuid::new_v4();
|
||||||
|
|
|
||||||
|
|
@ -418,6 +418,7 @@ mod tests {
|
||||||
.send(SchedItem {
|
.send(SchedItem {
|
||||||
spec: make_spec("b"),
|
spec: make_spec("b"),
|
||||||
ctx: make_ctx(),
|
ctx: make_ctx(),
|
||||||
|
original: None,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,16 @@ ORCH_WORK_DIR=/var/lib/solstice-ci
|
||||||
# Default points to the workspace target/runners where mise tasks may place built artifacts.
|
# Default points to the workspace target/runners where mise tasks may place built artifacts.
|
||||||
RUNNER_DIR_HOST=../../target/runners
|
RUNNER_DIR_HOST=../../target/runners
|
||||||
|
|
||||||
|
# When orchestrator runs behind NAT or in containers, set the public contact address
|
||||||
|
# that VMs can reach for gRPC log streaming (host:port). This overrides autodetection.
|
||||||
|
# Example: grpc.${ENV}.${DOMAIN}:443 (when terminated by Traefik) or a public IP:port
|
||||||
|
ORCH_CONTACT_ADDR=
|
||||||
|
|
||||||
|
# Preferred: Provide a public base URL for runner binaries; the orchestrator will construct
|
||||||
|
# full URLs like ${SOLSTICE_RUNNER_BASE_URL}/runners/solstice-runner(-linux|-illumos)
|
||||||
|
# Example: https://runner.svc.${DOMAIN}
|
||||||
|
SOLSTICE_RUNNER_BASE_URL=
|
||||||
|
|
||||||
# Forge Integration secrets (set per deployment)
|
# Forge Integration secrets (set per deployment)
|
||||||
# Shared secret used to validate Forgejo/Gitea webhooks (X-Gitea-Signature HMAC-SHA256)
|
# Shared secret used to validate Forgejo/Gitea webhooks (X-Gitea-Signature HMAC-SHA256)
|
||||||
WEBHOOK_SECRET=
|
WEBHOOK_SECRET=
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,10 @@ services:
|
||||||
# Libvirt configuration for Linux/KVM
|
# Libvirt configuration for Linux/KVM
|
||||||
LIBVIRT_URI: ${LIBVIRT_URI:-qemu:///system}
|
LIBVIRT_URI: ${LIBVIRT_URI:-qemu:///system}
|
||||||
LIBVIRT_NETWORK: ${LIBVIRT_NETWORK:-default}
|
LIBVIRT_NETWORK: ${LIBVIRT_NETWORK:-default}
|
||||||
|
# Public contact address for runners to stream logs to (host:port); overrides autodetection
|
||||||
|
ORCH_CONTACT_ADDR: ${ORCH_CONTACT_ADDR}
|
||||||
|
# Preferred: public base URL for runner binaries
|
||||||
|
SOLSTICE_RUNNER_BASE_URL: ${SOLSTICE_RUNNER_BASE_URL}
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue