Skip to main content

hyperi_rustlib/deployment/
registry.rs

1// Project:   hyperi-rustlib
2// File:      src/deployment/registry.rs
3// Purpose:   Config-cascade-driven container registry resolution
4// Language:  Rust
5//
6// License:   BUSL-1.1
7// Copyright: (c) 2026 HYPERI PTY LIMITED
8
9//! Container registry resolution for deployment contracts.
10//!
11//! The publish-target registry (where the built image is pushed) and the
12//! base image (the `FROM` line) are org-wide decisions, not per-app. This
13//! module reads them from the config cascade so they live in YAML config
14//! rather than being hardcoded in each app's contract source.
15//!
16//! # Cascade keys
17//!
18//! ```yaml
19//! deployment:
20//!   image_registry: ghcr.io/hyperi-io        # default: ghcr.io/hyperi-io
21//!   base_image: ubuntu:24.04                 # default: ubuntu:24.04
22//! ```
23//!
24//! # Defaults
25//!
26//! - [`DEFAULT_IMAGE_REGISTRY`] = `ghcr.io/hyperi-io` -- where built images go
27//! - [`DEFAULT_BASE_IMAGE`] = `ubuntu:24.04` -- what the runtime stage builds on
28//!
29//! When (eventually) a curated GHCR base image lands at
30//! `ghcr.io/hyperi-io/dfe-base:ubuntu-24.04`, ops can override
31//! `deployment.base_image` in the cascade without rebuilding the apps.
32
33/// Default publish-target registry for HyperI org.
34///
35/// Combined with the contract's `app_name` to produce
36/// `<DEFAULT_IMAGE_REGISTRY>/<app_name>:<version>`.
37pub const DEFAULT_IMAGE_REGISTRY: &str = "ghcr.io/hyperi-io";
38
39/// Default base image for the runtime stage.
40///
41/// Pulled from Docker Hub (no registry prefix). To use a curated GHCR base,
42/// set `deployment.base_image` in the YAML cascade to e.g.
43/// `ghcr.io/hyperi-io/dfe-base:ubuntu-24.04` once that image exists.
44pub const DEFAULT_BASE_IMAGE: &str = "ubuntu:24.04";
45
46/// Read the publish-target image registry from the config cascade.
47///
48/// Reads `deployment.image_registry` from the YAML cascade. Falls back to
49/// [`DEFAULT_IMAGE_REGISTRY`] when not set, when config isn't loaded, or
50/// when the `config` feature is disabled.
51///
52/// # Example
53///
54/// ```rust,no_run
55/// use hyperi_rustlib::deployment::{DeploymentContract, image_registry_from_cascade};
56/// # fn dummy() -> DeploymentContract { unimplemented!() }
57/// let mut contract = dummy();
58/// contract.image_registry = image_registry_from_cascade();
59/// ```
60#[must_use]
61pub fn image_registry_from_cascade() -> String {
62    #[cfg(feature = "config")]
63    {
64        if let Some(cfg) = crate::config::try_get()
65            && let Some(s) = cfg.get_string("deployment.image_registry")
66            && !s.is_empty()
67        {
68            return s;
69        }
70    }
71    DEFAULT_IMAGE_REGISTRY.to_string()
72}
73
74/// Read the runtime base image from the config cascade.
75///
76/// Reads `deployment.base_image` from the YAML cascade. Falls back to
77/// [`DEFAULT_BASE_IMAGE`] when not set.
78#[must_use]
79pub fn base_image_from_cascade() -> String {
80    #[cfg(feature = "config")]
81    {
82        if let Some(cfg) = crate::config::try_get()
83            && let Some(s) = cfg.get_string("deployment.base_image")
84            && !s.is_empty()
85        {
86            return s;
87        }
88    }
89    DEFAULT_BASE_IMAGE.to_string()
90}
91
92/// Read the git repo URL for ArgoCD generation from the config cascade.
93///
94/// Reads `deployment.argocd.repo_url` from the YAML cascade. Falls back to
95/// `https://github.com/hyperi-io/{app_name}` if not set -- matches the org
96/// convention.
97#[must_use]
98pub fn argocd_repo_url_from_cascade(app_name: &str) -> String {
99    #[cfg(feature = "config")]
100    {
101        if let Some(cfg) = crate::config::try_get()
102            && let Some(s) = cfg.get_string("deployment.argocd.repo_url")
103            && !s.is_empty()
104        {
105            return s;
106        }
107    }
108    format!("https://github.com/hyperi-io/{app_name}")
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn defaults_are_ghcr_friendly() {
117        assert_eq!(DEFAULT_IMAGE_REGISTRY, "ghcr.io/hyperi-io");
118        assert_eq!(DEFAULT_BASE_IMAGE, "ubuntu:24.04");
119    }
120
121    #[test]
122    fn cascade_falls_back_to_defaults_when_no_config() {
123        // No config setup → returns defaults.
124        assert_eq!(image_registry_from_cascade(), DEFAULT_IMAGE_REGISTRY);
125        assert_eq!(base_image_from_cascade(), DEFAULT_BASE_IMAGE);
126    }
127}