use std::path::Path;
#[derive(Debug)]
pub struct ApplyOutcome {
pub provisioning_state: String,
pub raw: serde_json::Value,
}
#[derive(Debug, thiserror::Error)]
pub enum ApplyError {
#[error("az command failed (exit {code}): {stderr}")]
AzFailed { code: i32, stderr: String },
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("json: {0}")]
Json(#[from] serde_json::Error),
}
pub fn run(resource_group: &str, bicep_path: &Path) -> Result<ApplyOutcome, ApplyError> {
let output = std::process::Command::new("az")
.args([
"deployment",
"group",
"create",
"--resource-group",
resource_group,
"--template-file",
bicep_path.to_str().unwrap_or(""),
"--no-prompt",
"true",
"--output",
"json",
])
.output()?;
if !output.status.success() {
return Err(ApplyError::AzFailed {
code: output.status.code().unwrap_or(-1),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
});
}
let result: serde_json::Value = serde_json::from_slice(&output.stdout)?;
let provisioning_state = result
.pointer("/properties/provisioningState")
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string();
Ok(ApplyOutcome {
provisioning_state,
raw: result,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn apply_error_formats_code_and_stderr() {
let err = ApplyError::AzFailed {
code: 1,
stderr: "some error".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("exit 1"), "error must mention exit code");
assert!(msg.contains("some error"), "error must include stderr");
}
}