feat(secret): added get_or_prompt functionality and debuggable ipxe chainloading boot file and some misc stuff
Some checks failed
Run Check Script / check (pull_request) Failing after 29s
Some checks failed
Run Check Script / check (pull_request) Failing after 29s
This commit is contained in:
parent
160939de21
commit
b765e9b7dc
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@ private_repos/
|
|||||||
|
|
||||||
### Harmony ###
|
### Harmony ###
|
||||||
harmony.log
|
harmony.log
|
||||||
|
data/okd/installation_files*
|
||||||
|
|
||||||
### Helm ###
|
### Helm ###
|
||||||
# Chart dependencies
|
# Chart dependencies
|
||||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2427,6 +2427,7 @@ dependencies = [
|
|||||||
"harmony_secret_derive",
|
"harmony_secret_derive",
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"infisical",
|
"infisical",
|
||||||
|
"inquire",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
@ -22,7 +22,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = SecretManager::get::<OPNSenseFirewallConfig>().await;
|
let config = SecretManager::get_or_prompt::<OPNSenseFirewallConfig>().await;
|
||||||
let config = config.unwrap();
|
let config = config.unwrap();
|
||||||
|
|
||||||
let opnsense = Arc::new(
|
let opnsense = Arc::new(
|
||||||
|
@ -16,7 +16,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = SecretManager::get::<OPNSenseFirewallCredentials>().await;
|
let config = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>().await;
|
||||||
let config = config.unwrap();
|
let config = config.unwrap();
|
||||||
|
|
||||||
let opnsense = Arc::new(
|
let opnsense = Arc::new(
|
||||||
|
@ -425,7 +425,8 @@ impl OKDSetup02BootstrapInterpret {
|
|||||||
topology: &HAClusterTopology,
|
topology: &HAClusterTopology,
|
||||||
) -> Result<(), InterpretError> {
|
) -> Result<(), InterpretError> {
|
||||||
let okd_bin_path = PathBuf::from("./data/okd/bin");
|
let okd_bin_path = PathBuf::from("./data/okd/bin");
|
||||||
let okd_installation_path_str = "./data/okd/installation_files";
|
let okd_installation_path_str =
|
||||||
|
format!("./data/okd/installation_files_{}", inventory.location.name);
|
||||||
let okd_images_path = &PathBuf::from("./data/okd/installer_image/");
|
let okd_images_path = &PathBuf::from("./data/okd/installer_image/");
|
||||||
let okd_installation_path = &PathBuf::from(okd_installation_path_str);
|
let okd_installation_path = &PathBuf::from(okd_installation_path_str);
|
||||||
|
|
||||||
@ -450,8 +451,8 @@ impl OKDSetup02BootstrapInterpret {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let redhat_secret = SecretManager::get::<RedhatSecret>().await?;
|
let redhat_secret = SecretManager::get_or_prompt::<RedhatSecret>().await?;
|
||||||
let ssh_key = SecretManager::get::<SshKeyPair>().await?;
|
let ssh_key = SecretManager::get_or_prompt::<SshKeyPair>().await?;
|
||||||
|
|
||||||
let install_config_yaml = InstallConfigYaml {
|
let install_config_yaml = InstallConfigYaml {
|
||||||
cluster_name: &topology.get_cluster_name(),
|
cluster_name: &topology.get_cluster_name(),
|
||||||
@ -562,38 +563,14 @@ impl OKDSetup02BootstrapInterpret {
|
|||||||
.interpret(inventory, topology)
|
.interpret(inventory, topology)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let run_command =
|
|
||||||
async |cmd: &str, args: Vec<&str>| -> Result<std::process::Output, InterpretError> {
|
|
||||||
let output = Command::new(cmd).args(&args).output().await.map_err(|e| {
|
|
||||||
InterpretError::new(format!("Failed to launch command {cmd} : {e}"))
|
|
||||||
})?;
|
|
||||||
let stdout = String::from_utf8(output.stdout.clone()).unwrap();
|
|
||||||
info!("{cmd} stdout :\n\n{}", stdout);
|
|
||||||
let stderr = String::from_utf8(output.stderr.clone()).unwrap();
|
|
||||||
info!("{cmd} stderr :\n\n{}", stderr);
|
|
||||||
info!("{cmd} exit status : {}", output.status);
|
|
||||||
if !output.status.success() {
|
|
||||||
return Err(InterpretError::new(format!(
|
|
||||||
"Command execution failed, exit code {} : {} {}",
|
|
||||||
output.status,
|
|
||||||
cmd,
|
|
||||||
args.join(" ")
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
Ok(output)
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("Successfully prepared ignition files for OKD installation");
|
info!("Successfully prepared ignition files for OKD installation");
|
||||||
// ignition_files_http_path // = PathBuf::from("okd_ignition_files");
|
// ignition_files_http_path // = PathBuf::from("okd_ignition_files");
|
||||||
info!(
|
info!(
|
||||||
r#"Uploading images, they can be refreshed with a command similar to this one: openshift-install coreos print-stream-json | grep -Eo '"https.*(kernel.|initramfs.|rootfs.)\w+(\.img)?"' | grep x86_64 | xargs -n 1 curl -LO"#
|
r#"Uploading images, they can be refreshed with a command similar to this one: openshift-install coreos print-stream-json | grep -Eo '"https.*(kernel.|initramfs.|rootfs.)\w+(\.img)?"' | grep x86_64 | xargs -n 1 curl -LO"#
|
||||||
);
|
);
|
||||||
|
|
||||||
warn!(
|
|
||||||
"TODO push installer image files with `scp -r data/okd/installer_image/* root@192.168.1.1:/usr/local/http/scos/` until performance issue is resolved"
|
|
||||||
);
|
|
||||||
inquire::Confirm::new(
|
inquire::Confirm::new(
|
||||||
"push installer image files with `scp -r data/okd/installer_image/* root@192.168.1.1:/usr/local/http/scos/` until performance issue is resolved").prompt().expect("Prompt error");
|
&format!("push installer image files with `scp -r {}/* root@{}:/usr/local/http/scos/` until performance issue is resolved", okd_images_path.to_string_lossy(), topology.http_server.get_ip())).prompt().expect("Prompt error");
|
||||||
|
|
||||||
// let scos_http_path = PathBuf::from("scos");
|
// let scos_http_path = PathBuf::from("scos");
|
||||||
// StaticFilesHttpScore {
|
// StaticFilesHttpScore {
|
||||||
@ -636,10 +613,12 @@ impl OKDSetup02BootstrapInterpret {
|
|||||||
) -> Result<(), InterpretError> {
|
) -> Result<(), InterpretError> {
|
||||||
let content = BootstrapIpxeTpl {
|
let content = BootstrapIpxeTpl {
|
||||||
http_ip: &topology.http_server.get_ip().to_string(),
|
http_ip: &topology.http_server.get_ip().to_string(),
|
||||||
scos_path: "scos", // TODO use some constant
|
scos_path: "scos", // TODO use some constant
|
||||||
installation_device: "/dev/sda", // TODO do something smart based on the host drives
|
ignition_http_path: "okd_ignition_files", // TODO use proper variable
|
||||||
// topology. Something like use the smallest device
|
installation_device: "/dev/sda",
|
||||||
// above 200G that is an ssd
|
// TODO do something smart based on the host drives
|
||||||
|
// topology. Something like use the smallest device
|
||||||
|
// above 200G that is an ssd
|
||||||
}
|
}
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
@ -735,7 +714,7 @@ impl Interpret<HAClusterTopology> for OKDSetup02BootstrapInterpret {
|
|||||||
self.prepare_ignition_files(inventory, topology).await?;
|
self.prepare_ignition_files(inventory, topology).await?;
|
||||||
self.render_per_mac_pxe(inventory, topology).await?;
|
self.render_per_mac_pxe(inventory, topology).await?;
|
||||||
self.setup_bootstrap_load_balancer(inventory, topology)
|
self.setup_bootstrap_load_balancer(inventory, topology)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// TODO https://docs.okd.io/latest/installing/installing_bare_metal/upi/installing-bare-metal.html#installation-user-provisioned-validating-dns_installing-bare-metal
|
// TODO https://docs.okd.io/latest/installing/installing_bare_metal/upi/installing-bare-metal.html#installation-user-provisioned-validating-dns_installing-bare-metal
|
||||||
// self.validate_dns_config(inventory, topology).await?;
|
// self.validate_dns_config(inventory, topology).await?;
|
||||||
|
@ -15,4 +15,5 @@ pub struct BootstrapIpxeTpl<'a> {
|
|||||||
pub http_ip: &'a str,
|
pub http_ip: &'a str,
|
||||||
pub scos_path: &'a str,
|
pub scos_path: &'a str,
|
||||||
pub installation_device: &'a str,
|
pub installation_device: &'a str,
|
||||||
|
pub ignition_http_path: &'a str,
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,122 @@
|
|||||||
#!ipxe
|
#!ipxe
|
||||||
|
# Default chainloader with optional debug mode.
|
||||||
|
# - Press any key within 3 seconds at start to enable debug mode.
|
||||||
|
# - In debug mode: confirmations and extra sleeps are enabled.
|
||||||
|
# - In production (no key pressed): continues without prompts.
|
||||||
|
|
||||||
|
# Config
|
||||||
set base-url http://{{ gateway_ip }}:8080
|
set base-url http://{{ gateway_ip }}:8080
|
||||||
set hostfile ${base-url}/byMAC/01-${mac:hexhyp}.ipxe
|
set macfile 01-${mac:hexhyp}
|
||||||
|
set hostfile ${base-url}/byMAC/${macfile}.ipxe
|
||||||
|
set fallback ${base-url}/fallback.ipxe
|
||||||
|
|
||||||
chain ${hostfile} || chain ${base-url}/fallback.ipxe
|
# Verbosity (1..4)
|
||||||
|
set debug 2
|
||||||
|
|
||||||
|
# State
|
||||||
|
set debugmode 0
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo === iPXE chainload stage (default) ===
|
||||||
|
echo MAC: ${mac}
|
||||||
|
echo Base URL: ${base-url}
|
||||||
|
echo Host file: ${hostfile}
|
||||||
|
echo Fallback : ${fallback}
|
||||||
|
echo ======================================
|
||||||
|
echo
|
||||||
|
echo Press any key within 3 seconds to enter DEBUG MODE...
|
||||||
|
prompt --timeout 3 Entering debug mode... && set debugmode 1 || set debugmode 0
|
||||||
|
|
||||||
|
iseq ${debugmode} 1 && goto :debug_enabled || goto :debug_disabled
|
||||||
|
|
||||||
|
:debug_enabled
|
||||||
|
echo DEBUG MODE: ON (confirmations and extra sleeps enabled)
|
||||||
|
sleep 1
|
||||||
|
goto :start
|
||||||
|
|
||||||
|
:debug_disabled
|
||||||
|
echo DEBUG MODE: OFF (no confirmations; production behavior)
|
||||||
|
sleep 1
|
||||||
|
goto :start
|
||||||
|
|
||||||
|
:start
|
||||||
|
# Show network status briefly in both modes
|
||||||
|
ifstat
|
||||||
|
iseq ${debugmode} 1 && sleep 2 || sleep 0
|
||||||
|
|
||||||
|
# Probe host-specific script via HTTP HEAD
|
||||||
|
echo
|
||||||
|
echo Probing host-specific script: ${hostfile}
|
||||||
|
http --head ${hostfile}
|
||||||
|
iseq ${rc} 0 && goto :has_hostfile || goto :no_hostfile
|
||||||
|
|
||||||
|
:has_hostfile
|
||||||
|
echo Found host-specific script: ${hostfile}
|
||||||
|
iseq ${debugmode} 1 && goto :confirm_host || goto :chain_host
|
||||||
|
|
||||||
|
:confirm_host
|
||||||
|
prompt --timeout 8 Press Enter to chain host script, Esc to abort... && goto :chain_host || goto :abort
|
||||||
|
|
||||||
|
:chain_host
|
||||||
|
echo Chaining ${hostfile} ...
|
||||||
|
iseq ${debugmode} 1 && sleep 2 || sleep 0
|
||||||
|
chain ${hostfile} || goto :host_chain_fail
|
||||||
|
# On success, control does not return.
|
||||||
|
|
||||||
|
:host_chain_fail
|
||||||
|
echo ERROR: chain to ${hostfile} failed (rc=${rc})
|
||||||
|
iseq ${debugmode} 1 && sleep 5 || sleep 1
|
||||||
|
goto :try_fallback
|
||||||
|
|
||||||
|
:no_hostfile
|
||||||
|
echo NOT FOUND or unreachable: ${hostfile} (rc=${rc})
|
||||||
|
iseq ${debugmode} 1 && sleep 2 || sleep 0
|
||||||
|
|
||||||
|
:try_fallback
|
||||||
|
echo
|
||||||
|
echo Probing fallback script: ${fallback}
|
||||||
|
http --head ${fallback}
|
||||||
|
iseq ${rc} 0 && goto :has_fallback || goto :fallback_missing
|
||||||
|
|
||||||
|
:has_fallback
|
||||||
|
iseq ${debugmode} 1 && goto :confirm_fallback || goto :chain_fallback
|
||||||
|
|
||||||
|
:confirm_fallback
|
||||||
|
prompt --timeout 8 Press Enter to chain fallback, Esc to shell... && goto :chain_fallback || goto :shell
|
||||||
|
|
||||||
|
:chain_fallback
|
||||||
|
echo Chaining ${fallback} ...
|
||||||
|
iseq ${debugmode} 1 && sleep 2 || sleep 0
|
||||||
|
chain ${fallback} || goto :fallback_chain_fail
|
||||||
|
# On success, control does not return.
|
||||||
|
|
||||||
|
:fallback_chain_fail
|
||||||
|
echo ERROR: chain to fallback failed (rc=${rc})
|
||||||
|
iseq ${debugmode} 1 && sleep 5 || sleep 1
|
||||||
|
goto :shell
|
||||||
|
|
||||||
|
:fallback_missing
|
||||||
|
echo ERROR: Fallback script not reachable: ${fallback} (rc=${rc})
|
||||||
|
iseq ${debugmode} 1 && sleep 5 || sleep 1
|
||||||
|
goto :shell
|
||||||
|
|
||||||
|
:abort
|
||||||
|
echo Aborted by user.
|
||||||
|
iseq ${debugmode} 1 && sleep 2 || sleep 1
|
||||||
|
goto :shell
|
||||||
|
|
||||||
|
:shell
|
||||||
|
echo
|
||||||
|
echo === iPXE debug shell ===
|
||||||
|
echo Try:
|
||||||
|
echo dhcp
|
||||||
|
echo ifstat
|
||||||
|
echo ping {{ gateway_ip }}
|
||||||
|
echo http ${hostfile}
|
||||||
|
echo http ${fallback}
|
||||||
|
echo chain ${hostfile}
|
||||||
|
echo chain ${fallback}
|
||||||
|
sleep 1
|
||||||
|
shell
|
||||||
|
|
||||||
|
exit
|
||||||
|
@ -2,6 +2,6 @@ set base-url http://{{ http_ip }}:8080
|
|||||||
set scos-base-url = ${base-url}/{{ scos_path }}
|
set scos-base-url = ${base-url}/{{ scos_path }}
|
||||||
set installation-device = {{ installation_device }}
|
set installation-device = {{ installation_device }}
|
||||||
|
|
||||||
kernel ${scos-base-url}/scos-live-kernel.x86_64 initrd=main coreos.live.rootfs_url=${scos-base-url}/scos-live-rootfs.x86_64.img coreos.inst.install_dev=${installation-device} coreos.inst.ignition_url=${base-url}/bootstrap.ign
|
kernel ${scos-base-url}/scos-live-kernel.x86_64 initrd=main coreos.live.rootfs_url=${scos-base-url}/scos-live-rootfs.x86_64.img coreos.inst.install_dev=${installation-device} coreos.inst.ignition_url=${base-url}/{{ ignition_http_path }}/bootstrap.ign
|
||||||
initrd --name main ${scos-base-url}/scos-live-initramfs.x86_64.img
|
initrd --name main ${scos-base-url}/scos-live-initramfs.x86_64.img
|
||||||
boot
|
boot
|
||||||
|
@ -18,6 +18,7 @@ infisical = { git = "https://github.com/jggc/rust-sdk.git", branch = "patch-1" }
|
|||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
http.workspace = true
|
http.workspace = true
|
||||||
|
inquire.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
|
@ -110,6 +110,42 @@ impl SecretManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_or_prompt<T: Secret>() -> Result<T, SecretStoreError> {
|
||||||
|
let secret = Self::get::<T>().await;
|
||||||
|
let manager = get_secret_manager().await;
|
||||||
|
let prompted = secret.is_err();
|
||||||
|
|
||||||
|
let secret = secret.or_else(|e| -> Result<T, SecretStoreError> {
|
||||||
|
debug!("Could not get secret : {e}");
|
||||||
|
|
||||||
|
let ns = &manager.namespace;
|
||||||
|
let key = T::KEY;
|
||||||
|
let secret_json = inquire::Text::new(&format!(
|
||||||
|
"Secret not found for {} {}, paste the JSON here :",
|
||||||
|
ns, key
|
||||||
|
))
|
||||||
|
.prompt()
|
||||||
|
.map_err(|e| {
|
||||||
|
SecretStoreError::Store(format!("Failed to prompt secret {ns} {key} : {e}").into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let secret: T = serde_json::from_str(&secret_json).map_err(|e| {
|
||||||
|
SecretStoreError::Deserialization {
|
||||||
|
key: T::KEY.to_string(),
|
||||||
|
source: e,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(secret)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if prompted {
|
||||||
|
Self::set(&secret).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(secret)
|
||||||
|
}
|
||||||
|
|
||||||
/// Serializes and stores a secret.
|
/// Serializes and stores a secret.
|
||||||
pub async fn set<T: Secret>(secret: &T) -> Result<(), SecretStoreError> {
|
pub async fn set<T: Secret>(secret: &T) -> Result<(), SecretStoreError> {
|
||||||
let manager = get_secret_manager().await;
|
let manager = get_secret_manager().await;
|
||||||
|
Loading…
Reference in New Issue
Block a user