Skip to main content

xbp_deploy/
context.rs

1use serde::{Deserialize, Serialize};
2
3use crate::types::{DeployTarget, ProjectConfig};
4
5/// Exactly one deploy mode (CLI must enforce exclusivity).
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum DeployMode {
9    Plan,
10    Run,
11    Verify,
12    Promote,
13    Status,
14    History,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct DeployFlags {
19    pub dry_run: bool,
20    pub skip_oci: bool,
21    /// Skip docker build/push even when `services[].oci.dockerfile` is set.
22    #[serde(default)]
23    pub skip_build: bool,
24    /// Build images into the local Docker engine only (no registry push).
25    /// Preferred for docker-desktop / kind / minikube / k3d local clusters.
26    #[serde(default)]
27    pub local_image: bool,
28    /// When true, Cloudflare container verification fails if the image digest/tag
29    /// did not change after deploy. Default is false (config-only deploys allowed).
30    #[serde(default)]
31    pub require_new_container_image: bool,
32    /// When true (default), if a k8s service has a workload but no manifest paths,
33    /// generate and apply a minimal Deployment/Service (first-time bootstrap).
34    /// Disable with CLI `--no-bootstrap-workload`.
35    #[serde(default = "default_true")]
36    pub bootstrap_workload: bool,
37    /// Run full preflight doctor before apply (cluster, namespaces, CF builds, OCI).
38    #[serde(default)]
39    pub doctor: bool,
40    /// Skip deploy preflight doctor even when `doctor` would run automatically.
41    #[serde(default)]
42    pub skip_doctor: bool,
43    pub skip_apply: bool,
44    pub skip_rollout: bool,
45    /// Skip post-rollout port-forward / hosts DNS expose phase.
46    #[serde(default)]
47    pub skip_expose: bool,
48    pub skip_health: bool,
49    pub yes: bool,
50    pub json: bool,
51    pub namespace: Option<String>,
52    pub context: Option<String>,
53    pub promote_tag: Option<String>,
54    pub history_limit: usize,
55    pub output: Option<std::path::PathBuf>,
56    /// Selected deploy destinations from CLI (`--to cloudflare,kubernetes`).
57    /// Empty means: use each service's default `deploy.provider` (or its default destination).
58    #[serde(default)]
59    pub destinations: Vec<String>,
60    /// Keep only these service names after planning (`--only`). Empty = no only-filter.
61    #[serde(default)]
62    pub only_services: Vec<String>,
63    /// Drop these service names after planning (`--exclude`).
64    #[serde(default)]
65    pub exclude_services: Vec<String>,
66}
67
68/// Everything the engine needs; built by CLI, consumed by planner/runner/verifier.
69#[derive(Debug, Clone)]
70pub struct DeployContext {
71    pub env: String,
72    pub target: DeployTarget,
73    pub config: ProjectConfig,
74    pub flags: DeployFlags,
75    pub mode: DeployMode,
76}
77
78fn default_true() -> bool {
79    true
80}
81
82impl Default for DeployFlags {
83    fn default() -> Self {
84        Self {
85            dry_run: false,
86            skip_oci: false,
87            skip_build: false,
88            local_image: false,
89            require_new_container_image: false,
90            bootstrap_workload: true,
91            doctor: false,
92            skip_doctor: false,
93            skip_apply: false,
94            skip_rollout: false,
95            skip_expose: false,
96            skip_health: false,
97            yes: false,
98            json: false,
99            namespace: None,
100            context: None,
101            promote_tag: None,
102            history_limit: 20,
103            output: None,
104            destinations: Vec::new(),
105            only_services: Vec::new(),
106            exclude_services: Vec::new(),
107        }
108    }
109}
110
111impl DeployContext {
112    pub fn require_confirmation(&self) -> bool {
113        if self.flags.yes || self.flags.dry_run {
114            return false;
115        }
116        self.config
117            .kubernetes
118            .as_ref()
119            .map(|k| k.require_context_confirmation_for_apply)
120            .unwrap_or(true)
121    }
122
123    /// Preflight doctor: explicit `--doctor`, or auto on Run when not skipped.
124    pub fn should_run_doctor(&self) -> bool {
125        if self.flags.skip_doctor || self.flags.dry_run {
126            return false;
127        }
128        self.flags.doctor || matches!(self.mode, DeployMode::Run)
129    }
130}