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 the forge-integration task use fnox secrets
|
||||
|
|
|
|||
|
|
@ -145,9 +145,12 @@ async fn main() -> Result<()> {
|
|||
.await;
|
||||
});
|
||||
|
||||
// Orchestrator contact address for runner to dial back (can override via ORCH_CONTACT_ADDR)
|
||||
let orch_contact =
|
||||
std::env::var("ORCH_CONTACT_ADDR").unwrap_or_else(|_| opts.grpc_addr.clone());
|
||||
// Orchestrator contact address for runner to dial back (auto-detect if not provided)
|
||||
let orch_contact = match std::env::var("ORCH_CONTACT_ADDR") {
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
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(
|
||||
repo_url: &str,
|
||||
commit_sha: &str,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue