mirror of
https://codeberg.org/Toasterson/solstice-ci.git
synced 2026-04-10 21:30:41 +00:00
Auto-detect orchestrator contact address and enhance platform-specific configurations
This commit introduces: - Automatic detection of the orchestrator contact address when not explicitly provided. - Platform-specific logic for determining reachable IPs, including libvirt network parsing (Linux) and external IP detection. - Updates to GRPC address processing to handle both specific and unspecified hosts. - Additional utility functions for parsing and detecting IPs in libvirt configurations.
This commit is contained in:
parent
97599eb48d
commit
0dabdf2bb2
2 changed files with 123 additions and 6 deletions
3
TODO.txt
3
TODO.txt
|
|
@ -1,6 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- Make orchestrator detect the address it will be reachable by checking the libvirt config or on illumos use it's external IP
|
|
||||||
- Make VM reachable IP of the orchestrator configurable in case the setup on illumos gets more complicated (via config file)
|
- Make VM reachable IP of the orchestrator configurable in case the setup on illumos gets more complicated (via config file)
|
||||||
- Make the forge-integration task use fnox secrets
|
- Make the forge-integration task use fnox secrets
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,12 @@ async fn main() -> Result<()> {
|
||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Orchestrator contact address for runner to dial back (can override via ORCH_CONTACT_ADDR)
|
// Orchestrator contact address for runner to dial back (auto-detect if not provided)
|
||||||
let orch_contact =
|
let orch_contact = match std::env::var("ORCH_CONTACT_ADDR") {
|
||||||
std::env::var("ORCH_CONTACT_ADDR").unwrap_or_else(|_| opts.grpc_addr.clone());
|
Ok(v) => v,
|
||||||
|
Err(_) => detect_contact_addr(&opts),
|
||||||
|
};
|
||||||
|
info!(contact = %orch_contact, "orchestrator contact address determined");
|
||||||
|
|
||||||
// Compose default runner URLs served by this orchestrator (if runner_dir configured)
|
// Compose default runner URLs served by this orchestrator (if runner_dir configured)
|
||||||
let (runner_url_default, runner_urls_default) = if opts.runner_dir.is_some() {
|
let (runner_url_default, runner_urls_default) = if opts.runner_dir.is_some() {
|
||||||
|
|
@ -306,6 +309,123 @@ fn parse_capacity_map(s: Option<&str>) -> HashMap<String, usize> {
|
||||||
m
|
m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn detect_contact_addr(opts: &Opts) -> String {
|
||||||
|
// Extract host and port from GRPC_ADDR (format host:port).
|
||||||
|
let (host_part, port_part) = match opts.grpc_addr.rsplit_once(':') {
|
||||||
|
Some((h, p)) => (h.to_string(), p.to_string()),
|
||||||
|
None => (opts.grpc_addr.clone(), String::from("")),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If host is already a specific address (not any/unspecified), keep it.
|
||||||
|
let host_trim = host_part.trim();
|
||||||
|
let is_unspecified = host_trim == "0.0.0.0" || host_trim == "::" || host_trim == "[::]" || host_trim.is_empty();
|
||||||
|
if !is_unspecified {
|
||||||
|
return opts.grpc_addr.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try platform-specific detection
|
||||||
|
#[cfg(all(target_os = "linux"))]
|
||||||
|
{
|
||||||
|
// Attempt to read libvirt network XML to obtain the NAT gateway IP (reachable from guests).
|
||||||
|
if let Some(ip) = detect_libvirt_network_ip(&opts.libvirt_network) {
|
||||||
|
let port = if port_part.is_empty() { String::from("50051") } else { port_part.clone() };
|
||||||
|
return format!("{}:{}", ip, port);
|
||||||
|
}
|
||||||
|
// Fallback to external IP detection
|
||||||
|
if let Some(ip) = detect_external_ip() {
|
||||||
|
let port = if port_part.is_empty() { String::from("50051") } else { port_part.clone() };
|
||||||
|
return format!("{}:{}", ip, port);
|
||||||
|
}
|
||||||
|
// Last resort
|
||||||
|
return format!("127.0.0.1:{}", if port_part.is_empty() { "50051" } else { &port_part });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "illumos")]
|
||||||
|
{
|
||||||
|
if let Some(ip) = detect_external_ip() {
|
||||||
|
let port = if port_part.is_empty() { String::from("50051") } else { port_part.clone() };
|
||||||
|
return format!("{}:{}", ip, port);
|
||||||
|
}
|
||||||
|
return format!("127.0.0.1:{}", if port_part.is_empty() { "50051" } else { &port_part });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other platforms: best-effort external IP
|
||||||
|
if let Some(ip) = detect_external_ip() {
|
||||||
|
let port = if port_part.is_empty() { String::from("50051") } else { port_part };
|
||||||
|
return format!("{}:{}", ip, port);
|
||||||
|
}
|
||||||
|
opts.grpc_addr.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "illumos"))]
|
||||||
|
fn detect_external_ip() -> Option<String> {
|
||||||
|
use std::net::{SocketAddr, UdpSocket};
|
||||||
|
// UDP connect trick: no packets are actually sent, but OS picks a route and local addr.
|
||||||
|
let target: SocketAddr = "1.1.1.1:80".parse().ok()?;
|
||||||
|
let sock = UdpSocket::bind("0.0.0.0:0").ok()?;
|
||||||
|
sock.connect(target).ok()?;
|
||||||
|
let local = sock.local_addr().ok()?;
|
||||||
|
Some(local.ip().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn detect_libvirt_network_ip(name: &str) -> Option<String> {
|
||||||
|
use std::fs;
|
||||||
|
let candidates = vec![
|
||||||
|
format!("/etc/libvirt/qemu/networks/{}.xml", name),
|
||||||
|
format!("/etc/libvirt/qemu/networks/autostart/{}.xml", name),
|
||||||
|
format!("/var/lib/libvirt/network/{}.xml", name),
|
||||||
|
format!("/var/lib/libvirt/qemu/networks/{}.xml", name),
|
||||||
|
];
|
||||||
|
for p in candidates {
|
||||||
|
if let Ok(xml) = fs::read_to_string(&p) {
|
||||||
|
// Look for <ip address='x.x.x.x' ...> or with double quotes
|
||||||
|
if let Some(ip) = extract_ip_from_libvirt_network_xml(&xml) {
|
||||||
|
return Some(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn extract_ip_from_libvirt_network_xml(xml: &str) -> Option<String> {
|
||||||
|
// Very small string-based parser to avoid extra dependencies
|
||||||
|
// Find "<ip" then search for "address='...'" or "address=\"...\""
|
||||||
|
let mut idx = 0;
|
||||||
|
while let Some(start) = xml[idx..].find("<ip") {
|
||||||
|
let s = idx + start;
|
||||||
|
let end = xml[s..].find('>').map(|e| s + e).unwrap_or(xml.len());
|
||||||
|
let segment = &xml[s..end];
|
||||||
|
if let Some(val) = extract_attr_value(segment, "address") {
|
||||||
|
return Some(val.to_string());
|
||||||
|
}
|
||||||
|
idx = end + 1;
|
||||||
|
if idx >= xml.len() { break; }
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn extract_attr_value<'a>(tag: &'a str, key: &'a str) -> Option<&'a str> {
|
||||||
|
// Search for key='value' or key="value"
|
||||||
|
if let Some(pos) = tag.find(key) {
|
||||||
|
let rest = &tag[pos + key.len()..];
|
||||||
|
let rest = rest.trim_start();
|
||||||
|
if rest.starts_with('=') {
|
||||||
|
let rest = &rest[1..].trim_start();
|
||||||
|
let quote = rest.chars().next()?;
|
||||||
|
if quote == '\'' || quote == '"' {
|
||||||
|
let rest2 = &rest[1..];
|
||||||
|
if let Some(end) = rest2.find(quote) {
|
||||||
|
return Some(&rest2[..end]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn make_cloud_init_userdata(
|
fn make_cloud_init_userdata(
|
||||||
repo_url: &str,
|
repo_url: &str,
|
||||||
commit_sha: &str,
|
commit_sha: &str,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue