1use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use semver::Version;
8
9use crate::pack::extensions::component_sources::{
10 ComponentSourcesError, ComponentSourcesV1, EXT_COMPONENT_SOURCES_V1,
11};
12use crate::{
13 ComponentManifest, Flow, FlowId, FlowKind, PROVIDER_EXTENSION_ID, PackId,
14 ProviderExtensionInline, SecretRequirement, SemverReq, Signature,
15};
16
17#[cfg(feature = "schemars")]
18use schemars::JsonSchema;
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21
22#[cfg(feature = "schemars")]
23fn empty_secret_requirements() -> Vec<SecretRequirement> {
24 Vec::new()
25}
26
27pub(crate) fn extensions_is_empty(value: &Option<BTreeMap<String, ExtensionRef>>) -> bool {
28 value.as_ref().is_none_or(BTreeMap::is_empty)
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
35#[cfg_attr(feature = "schemars", derive(JsonSchema))]
36pub enum PackKind {
37 Application,
39 Provider,
41 Infrastructure,
43 Library,
45}
46
47#[derive(Clone, Debug, PartialEq)]
49#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50#[cfg_attr(
51 feature = "schemars",
52 derive(JsonSchema),
53 schemars(
54 title = "Greentic PackManifest v1",
55 description = "Canonical pack manifest embedding flows, components, dependencies and signatures.",
56 rename = "greentic.pack-manifest.v1"
57 )
58)]
59pub struct PackManifest {
60 pub schema_version: String,
62 pub pack_id: PackId,
64 #[cfg_attr(
66 feature = "schemars",
67 schemars(default, description = "Optional pack name")
68 )]
69 #[cfg_attr(
70 feature = "serde",
71 serde(default, skip_serializing_if = "Option::is_none")
72 )]
73 pub name: Option<String>,
74 #[cfg_attr(
76 feature = "schemars",
77 schemars(with = "String", description = "SemVer version")
78 )]
79 pub version: Version,
80 pub kind: PackKind,
82 pub publisher: String,
84 #[cfg_attr(feature = "serde", serde(default))]
86 pub components: Vec<ComponentManifest>,
87 #[cfg_attr(feature = "serde", serde(default))]
89 pub flows: Vec<PackFlowEntry>,
90 #[cfg_attr(feature = "serde", serde(default))]
92 pub dependencies: Vec<PackDependency>,
93 #[cfg_attr(feature = "serde", serde(default))]
95 pub capabilities: Vec<ComponentCapability>,
96 #[cfg_attr(
98 feature = "serde",
99 serde(default, skip_serializing_if = "Vec::is_empty")
100 )]
101 #[cfg_attr(feature = "schemars", schemars(default = "empty_secret_requirements"))]
102 pub secret_requirements: Vec<SecretRequirement>,
103 #[cfg_attr(feature = "serde", serde(default))]
105 pub signatures: PackSignatures,
106 #[cfg_attr(
108 feature = "serde",
109 serde(default, skip_serializing_if = "Option::is_none")
110 )]
111 pub bootstrap: Option<BootstrapSpec>,
112 #[cfg_attr(
114 feature = "serde",
115 serde(default, skip_serializing_if = "extensions_is_empty")
116 )]
117 pub extensions: Option<BTreeMap<String, ExtensionRef>>,
118}
119
120#[derive(Clone, Debug, PartialEq)]
122#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
123#[cfg_attr(feature = "schemars", derive(JsonSchema))]
124pub struct PackFlowEntry {
125 pub id: FlowId,
127 pub kind: FlowKind,
129 pub flow: Flow,
131 #[cfg_attr(feature = "serde", serde(default))]
133 pub tags: Vec<String>,
134 #[cfg_attr(feature = "serde", serde(default))]
136 pub entrypoints: Vec<String>,
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
141#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
142#[cfg_attr(feature = "schemars", derive(JsonSchema))]
143pub struct PackDependency {
144 pub alias: String,
146 pub pack_id: PackId,
148 pub version_req: SemverReq,
150 #[cfg_attr(feature = "serde", serde(default))]
152 pub required_capabilities: Vec<String>,
153}
154
155#[derive(Clone, Debug, PartialEq, Eq)]
157#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
158#[cfg_attr(feature = "schemars", derive(JsonSchema))]
159pub struct ComponentCapability {
160 pub name: String,
162 #[cfg_attr(
164 feature = "serde",
165 serde(default, skip_serializing_if = "Option::is_none")
166 )]
167 pub description: Option<String>,
168}
169
170#[derive(Clone, Debug, PartialEq, Eq, Default)]
172#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
173#[cfg_attr(feature = "schemars", derive(JsonSchema))]
174pub struct PackSignatures {
175 #[cfg_attr(feature = "serde", serde(default))]
177 pub signatures: Vec<Signature>,
178}
179
180#[derive(Clone, Debug, PartialEq, Eq, Default)]
182#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
183#[cfg_attr(feature = "schemars", derive(JsonSchema))]
184pub struct BootstrapSpec {
185 #[cfg_attr(
187 feature = "serde",
188 serde(default, skip_serializing_if = "Option::is_none")
189 )]
190 pub install_flow: Option<String>,
191 #[cfg_attr(
193 feature = "serde",
194 serde(default, skip_serializing_if = "Option::is_none")
195 )]
196 pub upgrade_flow: Option<String>,
197 #[cfg_attr(
199 feature = "serde",
200 serde(default, skip_serializing_if = "Option::is_none")
201 )]
202 pub installer_component: Option<String>,
203}
204
205#[derive(Clone, Debug, PartialEq)]
207#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
208#[cfg_attr(feature = "serde", serde(untagged))]
209#[cfg_attr(feature = "schemars", derive(JsonSchema))]
210pub enum ExtensionInline {
211 Provider(ProviderExtensionInline),
213 Other(serde_json::Value),
215}
216
217impl ExtensionInline {
218 pub fn as_provider_inline(&self) -> Option<&ProviderExtensionInline> {
220 match self {
221 ExtensionInline::Provider(value) => Some(value),
222 ExtensionInline::Other(_) => None,
223 }
224 }
225
226 pub fn as_provider_inline_mut(&mut self) -> Option<&mut ProviderExtensionInline> {
228 match self {
229 ExtensionInline::Provider(value) => Some(value),
230 ExtensionInline::Other(_) => None,
231 }
232 }
233}
234
235#[derive(Clone, Debug, PartialEq)]
237#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
238#[cfg_attr(feature = "schemars", derive(JsonSchema))]
239pub struct ExtensionRef {
240 pub kind: String,
242 pub version: String,
244 #[cfg_attr(
246 feature = "serde",
247 serde(default, skip_serializing_if = "Option::is_none")
248 )]
249 pub digest: Option<String>,
250 #[cfg_attr(
252 feature = "serde",
253 serde(default, skip_serializing_if = "Option::is_none")
254 )]
255 pub location: Option<String>,
256 #[cfg_attr(
258 feature = "serde",
259 serde(default, skip_serializing_if = "Option::is_none")
260 )]
261 pub inline: Option<ExtensionInline>,
262}
263
264impl PackManifest {
265 pub fn provider_extension_inline(&self) -> Option<&ProviderExtensionInline> {
267 self.extensions
268 .as_ref()
269 .and_then(|extensions| extensions.get(PROVIDER_EXTENSION_ID))
270 .and_then(|extension| extension.inline.as_ref())
271 .and_then(ExtensionInline::as_provider_inline)
272 }
273
274 pub fn provider_extension_inline_mut(&mut self) -> Option<&mut ProviderExtensionInline> {
276 self.extensions
277 .as_mut()
278 .and_then(|extensions| extensions.get_mut(PROVIDER_EXTENSION_ID))
279 .and_then(|extension| extension.inline.as_mut())
280 .map(|inline| {
281 if let ExtensionInline::Other(value) = inline {
282 let parsed = serde_json::from_value(value.clone())
283 .unwrap_or_else(|_| ProviderExtensionInline::default());
284 *inline = ExtensionInline::Provider(parsed);
285 }
286 inline
287 })
288 .and_then(ExtensionInline::as_provider_inline_mut)
289 }
290
291 pub fn ensure_provider_extension_inline(&mut self) -> &mut ProviderExtensionInline {
293 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
294 let entry = extensions
295 .entry(PROVIDER_EXTENSION_ID.to_string())
296 .or_insert_with(|| ExtensionRef {
297 kind: PROVIDER_EXTENSION_ID.to_string(),
298 version: "1.0.0".to_string(),
299 digest: None,
300 location: None,
301 inline: Some(ExtensionInline::Provider(ProviderExtensionInline::default())),
302 });
303 if entry.inline.is_none() {
304 entry.inline = Some(ExtensionInline::Provider(ProviderExtensionInline::default()));
305 }
306 let inline = entry
307 .inline
308 .get_or_insert_with(|| ExtensionInline::Provider(ProviderExtensionInline::default()));
309 if let ExtensionInline::Other(value) = inline {
310 let parsed = serde_json::from_value(value.clone())
311 .unwrap_or_else(|_| ProviderExtensionInline::default());
312 *inline = ExtensionInline::Provider(parsed);
313 }
314 match inline {
315 ExtensionInline::Provider(inline) => inline,
316 ExtensionInline::Other(_) => unreachable!("provider inline should be initialised"),
317 }
318 }
319
320 #[cfg(feature = "serde")]
322 pub fn get_component_sources_v1(
323 &self,
324 ) -> Result<Option<ComponentSourcesV1>, ComponentSourcesError> {
325 let extension = self
326 .extensions
327 .as_ref()
328 .and_then(|extensions| extensions.get(EXT_COMPONENT_SOURCES_V1));
329 let inline = match extension.and_then(|entry| entry.inline.as_ref()) {
330 Some(ExtensionInline::Other(value)) => value,
331 Some(_) => return Err(ComponentSourcesError::UnexpectedInline),
332 None => return Ok(None),
333 };
334 let payload = ComponentSourcesV1::from_extension_value(inline)?;
335 Ok(Some(payload))
336 }
337
338 #[cfg(feature = "serde")]
340 pub fn set_component_sources_v1(
341 &mut self,
342 sources: ComponentSourcesV1,
343 ) -> Result<(), ComponentSourcesError> {
344 sources.validate_schema_version()?;
345 let inline = sources.to_extension_value()?;
346 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
347 extensions.insert(
348 EXT_COMPONENT_SOURCES_V1.to_string(),
349 ExtensionRef {
350 kind: EXT_COMPONENT_SOURCES_V1.to_string(),
351 version: "1.0.0".to_string(),
352 digest: None,
353 location: None,
354 inline: Some(ExtensionInline::Other(inline)),
355 },
356 );
357 Ok(())
358 }
359}