1use chrono::{DateTime, Utc};
4use kube::CustomResource;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use tatara_lisp::DeriveTataraDomain;
8
9use crate::attestation::ProcessAttestation;
10use crate::boundary::Boundary;
11use crate::classification::Classification;
12use crate::compliance::ComplianceSpec;
13use crate::encapsulates::EncapsulatesSpec;
14use crate::identity::Identity;
15use crate::intent::Intent;
16use crate::lifetime::Lifetime;
17use crate::phase::ProcessPhase;
18use crate::routing::RoutingSpec;
19use crate::signal::ProcessSignal;
20use crate::spec::{DependsOn, IdentitySpec, SignalPolicy};
21use crate::status::{BoundaryStatus, ComplianceStatus, FluxResourceRef, ProcessCondition};
22
23#[derive(CustomResource, DeriveTataraDomain, Clone, Debug, Deserialize, Serialize, JsonSchema)]
51#[kube(
52 group = "tatara.pleme.io",
53 version = "v1alpha1",
54 kind = "Process",
55 plural = "processes",
56 shortname = "proc",
57 namespaced,
58 status = "ProcessStatus",
59 printcolumn = r#"{"name":"PID","type":"string","jsonPath":".status.pid"}"#,
60 printcolumn = r#"{"name":"Phase","type":"string","jsonPath":".status.phase"}"#,
61 printcolumn = r#"{"name":"Type","type":"string","jsonPath":".spec.classification.pointType"}"#,
62 printcolumn = r#"{"name":"Substrate","type":"string","jsonPath":".spec.classification.substrate"}"#,
63 printcolumn = r#"{"name":"Gen","type":"integer","jsonPath":".status.attestation.generation"}"#,
64 printcolumn = r#"{"name":"Age","type":"date","jsonPath":".metadata.creationTimestamp"}"#
65)]
66#[serde(rename_all = "camelCase")]
67#[tatara(keyword = "defpoint")]
68pub struct ProcessSpec {
69 #[serde(default)]
71 pub identity: IdentitySpec,
72
73 pub classification: Classification,
75
76 pub intent: Intent,
78
79 #[serde(default)]
81 pub boundary: Boundary,
82
83 #[serde(default)]
85 pub compliance: ComplianceSpec,
86
87 #[serde(default)]
89 pub depends_on: Vec<DependsOn>,
90
91 #[serde(default)]
93 pub signals: SignalPolicy,
94
95 #[serde(default, skip_serializing_if = "Lifetime::is_default")]
98 pub lifetime: Lifetime,
99
100 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub routing: Option<RoutingSpec>,
105
106 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub encapsulates: Option<EncapsulatesSpec>,
112
113 #[serde(default)]
116 pub suspended: bool,
117}
118
119#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)]
121#[serde(rename_all = "camelCase")]
122pub struct ProcessStatus {
123 #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub pid: Option<String>,
126
127 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub parent: Option<String>,
130
131 #[serde(default)]
133 pub children: Vec<String>,
134
135 #[serde(default, skip_serializing_if = "Option::is_none")]
137 pub identity: Option<Identity>,
138
139 #[serde(default)]
141 pub phase: ProcessPhase,
142
143 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub phase_since: Option<DateTime<Utc>>,
146
147 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub attestation: Option<ProcessAttestation>,
150
151 #[serde(default)]
153 pub flux_resources: Vec<FluxResourceRef>,
154
155 #[serde(default)]
157 pub boundary: BoundaryStatus,
158
159 #[serde(default)]
161 pub compliance: ComplianceStatus,
162
163 #[serde(default)]
165 pub signal_queue: Vec<ProcessSignal>,
166
167 #[serde(default)]
169 pub conditions: Vec<ProcessCondition>,
170
171 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub message: Option<String>,
174
175 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub exit_code: Option<i32>,
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::classification::{ConvergencePointType, SubstrateType};
184 use crate::intent::NixIntent;
185
186 #[test]
187 fn minimal_spec_serializes() {
188 let spec = ProcessSpec {
189 identity: IdentitySpec::default(),
190 classification: Classification {
191 point_type: ConvergencePointType::Gate,
192 substrate: SubstrateType::Observability,
193 horizon: Default::default(),
194 calm: Default::default(),
195 data_classification: Default::default(),
196 },
197 intent: Intent {
198 nix: Some(NixIntent {
199 flake_ref: "github:pleme-io/k8s".into(),
200 attribute: "obs".into(),
201 system: None,
202 attic_cache: None,
203 extra_args: vec![],
204 delegate_to_nix_build: false,
205 }),
206 ..Intent::default()
207 },
208 boundary: Default::default(),
209 compliance: Default::default(),
210 depends_on: vec![],
211 signals: Default::default(),
212 lifetime: Default::default(),
213 routing: None,
214 encapsulates: None,
215 suspended: false,
216 };
217 let yaml = serde_yaml::to_string(&spec).unwrap();
218 assert!(yaml.contains("pointType: Gate"));
219 assert!(yaml.contains("substrate: Observability"));
220 assert!(yaml.contains("flakeRef: github:pleme-io/k8s"));
221 }
222}