use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::strategies::XbpConfig;
use crate::utils::resolve_cargo_package_version;
use xbp_deploy::{
DeployGroupView, KubernetesDefaults, ProjectConfig, ServiceConfigView, ServiceDeployEnvView,
ServiceDeployView, ServiceK8sView, ServiceOciView,
};
pub fn map_project_config(project_root: &Path, config: &XbpConfig) -> ProjectConfig {
let history_dir = config
.deploy
.as_ref()
.and_then(|d| d.history_dir.as_deref())
.filter(|s| !s.is_empty())
.unwrap_or(".xbp/deployments");
let lock_file = config
.deploy
.as_ref()
.and_then(|d| d.lock_file.as_deref())
.filter(|s| !s.is_empty())
.unwrap_or(".xbp/deploy-lock.json");
let mut groups = HashMap::new();
if let Some(deploy) = &config.deploy {
for (name, g) in &deploy.groups {
groups.insert(
name.clone(),
DeployGroupView {
description: g.description.clone(),
services: g.services.clone(),
order: g.order.clone(),
},
);
}
}
let services = config
.services
.as_ref()
.map(|list| {
list.iter()
.map(|s| {
let version = resolve_service_version(project_root, config, s);
ServiceConfigView {
name: s.name.clone(),
root_directory: s.root_directory.clone(),
version,
depends_on: s.depends_on.clone().unwrap_or_default(),
oci: s.oci.as_ref().and_then(|o| {
let image = o.image.as_ref()?.trim();
if image.is_empty() {
return None;
}
Some(ServiceOciView {
image: image.to_string(),
tag: o.tag.clone(),
tag_from: o.tag_from.clone(),
dockerfile: o.dockerfile.clone(),
platforms: o.platforms.clone(),
})
}),
deploy: s.deploy.as_ref().map(|d| ServiceDeployView {
provider: d
.provider
.clone()
.unwrap_or_else(|| "kubernetes".into()),
envs: d
.envs
.iter()
.map(|(k, v)| {
(
k.clone(),
ServiceDeployEnvView {
namespace: v.namespace.clone(),
replicas: v.replicas,
health: v.health.clone(),
kubernetes: v.kubernetes.as_ref().map(|k8s| {
ServiceK8sView {
manifests_base: k8s
.manifests
.as_ref()
.and_then(|m| m.base.clone()),
manifests_overlay: k8s
.manifests
.as_ref()
.and_then(|m| m.overlay.clone()),
workload: k8s.workload.clone(),
service: k8s.service.clone(),
crds_path: k8s.crds_path.clone(),
install_path: k8s.install_path.clone(),
selector: k8s.selector.clone(),
}
}),
},
)
})
.collect(),
}),
}
})
.collect()
})
.unwrap_or_default();
ProjectConfig {
project_name: config.project_name.clone(),
version: config.version.clone(),
project_root: project_root.to_path_buf(),
services,
groups,
default_env: config
.deploy
.as_ref()
.and_then(|d| d.default_env.clone()),
kubernetes: config.kubernetes.as_ref().map(|k| KubernetesDefaults {
default_context: k.default_context.clone(),
default_namespace: k.default_namespace.clone(),
require_context_confirmation_for_apply: k
.require_context_confirmation_for_apply
.unwrap_or(true),
kubeconfig: k.kubeconfig.clone(),
}),
history_dir: project_root.join(history_dir),
lock_file: project_root.join(lock_file),
git_sha: resolve_git_sha(project_root),
}
}
fn resolve_service_version(
project_root: &Path,
config: &XbpConfig,
service: &crate::strategies::ServiceConfig,
) -> Option<String> {
if let Some(targets) = service.version_targets.as_ref() {
for target in targets {
let path = project_root.join(target);
if !path.exists() {
continue;
}
let path_str = path.to_string_lossy();
if path_str.ends_with("Cargo.toml") {
if let Ok(Some(v)) = resolve_cargo_package_version(&path) {
return Some(v);
}
} else if path_str.ends_with("package.json") {
if let Ok(raw) = std::fs::read_to_string(&path) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&raw) {
if let Some(v) = val.get("version").and_then(|v| v.as_str()) {
return Some(v.to_string());
}
}
}
}
}
}
Some(config.version.clone())
}
fn resolve_git_sha(project_root: &Path) -> Option<String> {
let output = Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.current_dir(project_root)
.output()
.ok()?;
if !output.status.success() {
return None;
}
let sha = String::from_utf8_lossy(&output.stdout).trim().to_string();
if sha.is_empty() {
None
} else {
Some(sha)
}
}
#[allow(dead_code)]
pub fn default_lock_path(project_root: &Path) -> PathBuf {
project_root.join(".xbp/deploy-lock.json")
}