Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 116 additions & 3 deletions crates/api-core/src/cfg/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1614,7 +1614,15 @@ pub struct DpfResolvedMandatoryServicesConfig {
}

/// Configuration for a single Helm-based DPF service.
///
/// All string fields default to the empty string when absent from the config
/// file so that a site-config override can supply only the fields that differ
/// (e.g. only `helm_version`) without triggering a deserialization error for
/// the fields it omits. The caller is responsible for filling any empty fields
/// from the service's compiled-in defaults after deserialization; see
/// [`DpfServiceConfig::fill_from_defaults`].
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(default)]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
pub struct DpfServiceConfig {
/// Name of the Helm service.
pub name: String,
Expand All @@ -1631,10 +1639,40 @@ pub struct DpfServiceConfig {
/// Secret to use to pull the docker images. `None` when the service pulls from
/// a public registry (`dts` and `doca_hbn` default to this); when set, an
/// `imagePullSecrets` entry is emitted in the service's Helm values.
#[serde(default)]
pub docker_image_pull_secret: Option<String>,
}

impl DpfServiceConfig {
/// Fill every empty field in `self` from the corresponding field in
/// `defaults`. This is used after Figment extraction to ensure that a
/// site-config partial override (e.g. only `helm_version`) still produces
/// a fully-populated config: the compiled-in service defaults supply every
/// field that the operator did not explicitly override.
pub fn fill_from_defaults(&mut self, defaults: &DpfServiceConfig) {
if self.name.is_empty() {
self.name = defaults.name.clone();
}
if self.helm_repo_url.is_empty() {
self.helm_repo_url = defaults.helm_repo_url.clone();
}
if self.helm_chart.is_empty() {
self.helm_chart = defaults.helm_chart.clone();
}
if self.helm_version.is_empty() {
self.helm_version = defaults.helm_version.clone();
}
if self.docker_repo_url.is_empty() {
self.docker_repo_url = defaults.docker_repo_url.clone();
}
if self.docker_image_tag.is_empty() {
self.docker_image_tag = defaults.docker_image_tag.clone();
}
if self.docker_image_pull_secret.is_none() {
self.docker_image_pull_secret = defaults.docker_image_pull_secret.clone();
}
}
}

/// Per-deployment DPF configuration for named entries under `[dpf.deployments]`.
///
/// `flavor_name`, `deployment_name`, and `node_label_key` are required when a
Expand Down Expand Up @@ -1774,8 +1812,15 @@ impl DpfDeploymentsConfig {
.copied()
.map(|service| (service, service.default_config()))
.collect();
// A configured entry replaces only that service's built-in definition.
deployment.extra_services.extend(configured);
// A configured entry overrides the built-in definition for that service.
// `DpfServiceConfig` now allows partial TOML entries (see `#[serde(default)]`),
// so a configured entry may have empty string fields. Fill those from the
// service's built-in default before inserting so a partial override does not
// silently leave `name`, `helm_repo_url`, etc. blank.
for (service, mut partial) in configured {
partial.fill_from_defaults(&service.default_config());
deployment.extra_services.insert(service, partial);
}
}

/// Returns all active deployment configs as `(name, config)` pairs.
Expand Down Expand Up @@ -6732,6 +6777,74 @@ node_label_key = "carbide.nvidia.com/astra"
);
}

// -------------------------------------------------------------------------
// DpfServiceConfig::fill_from_defaults
// -------------------------------------------------------------------------

#[test]
fn fill_from_defaults_preserves_non_empty_fields() {
let default = DpfServiceConfig {
name: "carbide-dhcp-server".to_string(),
helm_repo_url: "https://default-repo.example.com".to_string(),
helm_chart: "carbide-dhcp-server".to_string(),
helm_version: "1.0.0".to_string(),
docker_repo_url: "nvcr.io/default/dhcp".to_string(),
docker_image_tag: "1.0.0".to_string(),
docker_image_pull_secret: Some("default-pull-secret".to_string()),
};

// Simulate a partial site-config entry: only helm_version is set,
// everything else is the empty-string default that #[serde(default)]
// produces when the field is absent from the TOML.
let mut partial = DpfServiceConfig {
helm_version: "2.1.0-pr-385".to_string(),
..DpfServiceConfig::default()
};

partial.fill_from_defaults(&default);

// The overridden field must be preserved.
assert_eq!(partial.helm_version, "2.1.0-pr-385");
// All other fields must come from the default.
assert_eq!(partial.name, "carbide-dhcp-server");
assert_eq!(partial.helm_repo_url, "https://default-repo.example.com");
assert_eq!(partial.helm_chart, "carbide-dhcp-server");
assert_eq!(partial.docker_repo_url, "nvcr.io/default/dhcp");
assert_eq!(partial.docker_image_tag, "1.0.0");
assert_eq!(
partial.docker_image_pull_secret,
Some("default-pull-secret".to_string())
);
}

#[test]
fn fill_from_defaults_full_config_unchanged() {
// When all fields are already populated, fill_from_defaults must not overwrite any of them.
let default = DpfServiceConfig {
name: "default-name".to_string(),
helm_repo_url: "https://default.example.com".to_string(),
helm_chart: "default-chart".to_string(),
helm_version: "1.0.0".to_string(),
docker_repo_url: "nvcr.io/default/image".to_string(),
docker_image_tag: "1.0.0".to_string(),
docker_image_pull_secret: Some("default-secret".to_string()),
};
let mut full = DpfServiceConfig {
name: "custom-name".to_string(),
helm_repo_url: "https://custom.example.com".to_string(),
helm_chart: "custom-chart".to_string(),
helm_version: "2.0.0".to_string(),
docker_repo_url: "nvcr.io/custom/image".to_string(),
docker_image_tag: "2.0.0".to_string(),
docker_image_pull_secret: Some("custom-secret".to_string()),
};
full.fill_from_defaults(&default);

assert_eq!(full.name, "custom-name");
assert_eq!(full.helm_version, "2.0.0");
assert_eq!(full.docker_image_pull_secret, Some("custom-secret".to_string()));
}

#[test]
fn validate_provisioning_sources_requires_exactly_one_psid() {
// Exactly one PSID entry is accepted.
Expand Down
40 changes: 40 additions & 0 deletions crates/api-core/src/cfg/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ pub fn parse_carbide_config(
.extract()
.wrap_err("failed to load configuration files")?;

// Fill any empty DpfServiceConfig fields from the compiled-in service defaults.
// A site-config partial override (e.g. only `helm_version`) leaves the other
// fields as empty strings after Figment extraction because the compiled-in
// defaults live in Rust code, not in any TOML file that Figment can deep-merge.
// `fill_from_defaults` restores those fields so the rest of the API sees a
// fully-populated config regardless of how many fields were overridden.
{
use crate::dpf_services::{
default_dhcp_server_service, default_doca_hbn_service, default_dpu_agent_service,
default_fmds_service, default_otelcol_service, default_dts_service,
};
let s = &mut config.dpf.services;
s.dts.fill_from_defaults(&default_dts_service());
s.doca_hbn.fill_from_defaults(&default_doca_hbn_service());
s.dpu_agent.fill_from_defaults(&default_dpu_agent_service());
s.dhcp_server.fill_from_defaults(&default_dhcp_server_service());
s.fmds.fill_from_defaults(&default_fmds_service());
s.otel.fill_from_defaults(&default_otelcol_service());

// Apply the same fill to per-deployment service overrides (bf3, bf4_generic, bf4_astra).
// These use the same DpfMandatoryServicesConfig type as the top-level services block.
let fill_deployment = |dep: &mut crate::cfg::file::DpfDeploymentConfig| {
if let Some(ref mut dep_services) = dep.services {
dep_services.dts.fill_from_defaults(&default_dts_service());
dep_services.doca_hbn.fill_from_defaults(&default_doca_hbn_service());
dep_services.dpu_agent.fill_from_defaults(&default_dpu_agent_service());
dep_services.dhcp_server.fill_from_defaults(&default_dhcp_server_service());
dep_services.fmds.fill_from_defaults(&default_fmds_service());
dep_services.otel.fill_from_defaults(&default_otelcol_service());
}
};
fill_deployment(&mut config.dpf.deployments.bf3);
if let Some(ref mut dep) = config.dpf.deployments.bf4_generic {
fill_deployment(dep);
}
if let Some(ref mut dep) = config.dpf.deployments.bf4_astra {
fill_deployment(dep);
}
}

config.config_ctx = Some(merged_config);

for (label, _) in config
Expand Down
Loading