1use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use semver::Version;
8
9use crate::pack::extensions::capabilities::{
10 CapabilitiesExtensionError, CapabilitiesExtensionV1, EXT_CAPABILITIES_V1,
11};
12use crate::pack::extensions::component_sources::{
13 ComponentSourcesError, ComponentSourcesV1, EXT_COMPONENT_SOURCES_V1,
14};
15use crate::{
16 ComponentManifest, Flow, FlowId, FlowKind, PROVIDER_EXTENSION_ID, PackId,
17 ProviderExtensionInline, SecretRequirement, SemverReq, Signature,
18};
19
20#[cfg(feature = "schemars")]
21use schemars::JsonSchema;
22#[cfg(feature = "serde")]
23use serde::{Deserialize, Serialize};
24
25#[cfg(feature = "schemars")]
26fn empty_secret_requirements() -> Vec<SecretRequirement> {
27 Vec::new()
28}
29
30pub(crate) fn extensions_is_empty(value: &Option<BTreeMap<String, ExtensionRef>>) -> bool {
31 value.as_ref().is_none_or(BTreeMap::is_empty)
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
38#[cfg_attr(feature = "schemars", derive(JsonSchema))]
39pub enum PackKind {
40 Application,
42 Provider,
44 Infrastructure,
46 Library,
48}
49
50#[derive(Clone, Debug, PartialEq)]
52#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
53#[cfg_attr(
54 feature = "schemars",
55 derive(JsonSchema),
56 schemars(
57 title = "Greentic PackManifest v1",
58 description = "Canonical pack manifest embedding flows, components, dependencies and signatures.",
59 rename = "greentic.pack-manifest.v1"
60 )
61)]
62pub struct PackManifest {
63 pub schema_version: String,
65 pub pack_id: PackId,
67 #[cfg_attr(
69 feature = "schemars",
70 schemars(default, description = "Optional pack name")
71 )]
72 #[cfg_attr(
73 feature = "serde",
74 serde(default, skip_serializing_if = "Option::is_none")
75 )]
76 pub name: Option<String>,
77 #[cfg_attr(
79 feature = "schemars",
80 schemars(with = "String", description = "SemVer version")
81 )]
82 pub version: Version,
83 pub kind: PackKind,
85 pub publisher: String,
87 #[cfg_attr(feature = "serde", serde(default))]
89 pub components: Vec<ComponentManifest>,
90 #[cfg_attr(feature = "serde", serde(default))]
92 pub flows: Vec<PackFlowEntry>,
93 #[cfg_attr(feature = "serde", serde(default))]
95 pub dependencies: Vec<PackDependency>,
96 #[cfg_attr(feature = "serde", serde(default))]
98 pub capabilities: Vec<ComponentCapability>,
99 #[cfg_attr(
101 feature = "serde",
102 serde(default, skip_serializing_if = "Vec::is_empty")
103 )]
104 #[cfg_attr(feature = "schemars", schemars(default = "empty_secret_requirements"))]
105 pub secret_requirements: Vec<SecretRequirement>,
106 #[cfg_attr(feature = "serde", serde(default))]
108 pub signatures: PackSignatures,
109 #[cfg_attr(
111 feature = "serde",
112 serde(default, skip_serializing_if = "Option::is_none")
113 )]
114 pub bootstrap: Option<BootstrapSpec>,
115 #[cfg_attr(
117 feature = "serde",
118 serde(default, skip_serializing_if = "extensions_is_empty")
119 )]
120 pub extensions: Option<BTreeMap<String, ExtensionRef>>,
121}
122
123#[derive(Clone, Debug, PartialEq)]
125#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
126#[cfg_attr(feature = "schemars", derive(JsonSchema))]
127pub struct PackFlowEntry {
128 pub id: FlowId,
130 pub kind: FlowKind,
132 pub flow: Flow,
134 #[cfg_attr(feature = "serde", serde(default))]
136 pub tags: Vec<String>,
137 #[cfg_attr(feature = "serde", serde(default))]
139 pub entrypoints: Vec<String>,
140}
141
142#[derive(Clone, Debug, PartialEq, Eq)]
144#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
145#[cfg_attr(feature = "schemars", derive(JsonSchema))]
146pub struct PackDependency {
147 pub alias: String,
149 pub pack_id: PackId,
151 pub version_req: SemverReq,
153 #[cfg_attr(feature = "serde", serde(default))]
155 pub required_capabilities: Vec<String>,
156}
157
158#[derive(Clone, Debug, PartialEq, Eq)]
160#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
161#[cfg_attr(feature = "schemars", derive(JsonSchema))]
162pub struct ComponentCapability {
163 pub name: String,
165 #[cfg_attr(
167 feature = "serde",
168 serde(default, skip_serializing_if = "Option::is_none")
169 )]
170 pub description: Option<String>,
171}
172
173#[derive(Clone, Debug, PartialEq, Eq, Default)]
175#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
176#[cfg_attr(feature = "schemars", derive(JsonSchema))]
177pub struct PackSignatures {
178 #[cfg_attr(feature = "serde", serde(default))]
180 pub signatures: Vec<Signature>,
181}
182
183#[derive(Clone, Debug, PartialEq, Eq, Default)]
185#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
186#[cfg_attr(feature = "schemars", derive(JsonSchema))]
187pub struct BootstrapSpec {
188 #[cfg_attr(
190 feature = "serde",
191 serde(default, skip_serializing_if = "Option::is_none")
192 )]
193 pub install_flow: Option<String>,
194 #[cfg_attr(
196 feature = "serde",
197 serde(default, skip_serializing_if = "Option::is_none")
198 )]
199 pub upgrade_flow: Option<String>,
200 #[cfg_attr(
202 feature = "serde",
203 serde(default, skip_serializing_if = "Option::is_none")
204 )]
205 pub installer_component: Option<String>,
206}
207
208#[derive(Clone, Debug, PartialEq)]
210#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
211#[cfg_attr(feature = "serde", serde(untagged))]
212#[cfg_attr(feature = "schemars", derive(JsonSchema))]
213pub enum ExtensionInline {
214 Provider(ProviderExtensionInline),
216 Other(serde_json::Value),
218}
219
220impl ExtensionInline {
221 pub fn as_provider_inline(&self) -> Option<&ProviderExtensionInline> {
223 match self {
224 ExtensionInline::Provider(value) => Some(value),
225 ExtensionInline::Other(_) => None,
226 }
227 }
228
229 pub fn as_provider_inline_mut(&mut self) -> Option<&mut ProviderExtensionInline> {
231 match self {
232 ExtensionInline::Provider(value) => Some(value),
233 ExtensionInline::Other(_) => None,
234 }
235 }
236}
237
238#[derive(Clone, Debug, PartialEq)]
240#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
241#[cfg_attr(feature = "schemars", derive(JsonSchema))]
242pub struct ExtensionRef {
243 pub kind: String,
245 pub version: String,
247 #[cfg_attr(
249 feature = "serde",
250 serde(default, skip_serializing_if = "Option::is_none")
251 )]
252 pub digest: Option<String>,
253 #[cfg_attr(
255 feature = "serde",
256 serde(default, skip_serializing_if = "Option::is_none")
257 )]
258 pub location: Option<String>,
259 #[cfg_attr(
261 feature = "serde",
262 serde(default, skip_serializing_if = "Option::is_none")
263 )]
264 pub inline: Option<ExtensionInline>,
265}
266
267impl PackManifest {
268 pub fn provider_extension_inline(&self) -> Option<&ProviderExtensionInline> {
270 self.extensions
271 .as_ref()
272 .and_then(|extensions| extensions.get(PROVIDER_EXTENSION_ID))
273 .and_then(|extension| extension.inline.as_ref())
274 .and_then(ExtensionInline::as_provider_inline)
275 }
276
277 pub fn provider_extension_inline_mut(&mut self) -> Option<&mut ProviderExtensionInline> {
279 self.extensions
280 .as_mut()
281 .and_then(|extensions| extensions.get_mut(PROVIDER_EXTENSION_ID))
282 .and_then(|extension| extension.inline.as_mut())
283 .map(|inline| {
284 if let ExtensionInline::Other(value) = inline {
285 let parsed = serde_json::from_value(value.clone())
286 .unwrap_or_else(|_| ProviderExtensionInline::default());
287 *inline = ExtensionInline::Provider(parsed);
288 }
289 inline
290 })
291 .and_then(ExtensionInline::as_provider_inline_mut)
292 }
293
294 pub fn ensure_provider_extension_inline(&mut self) -> &mut ProviderExtensionInline {
296 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
297 let entry = extensions
298 .entry(PROVIDER_EXTENSION_ID.to_string())
299 .or_insert_with(|| ExtensionRef {
300 kind: PROVIDER_EXTENSION_ID.to_string(),
301 version: "1.0.0".to_string(),
302 digest: None,
303 location: None,
304 inline: Some(ExtensionInline::Provider(ProviderExtensionInline::default())),
305 });
306 if entry.inline.is_none() {
307 entry.inline = Some(ExtensionInline::Provider(ProviderExtensionInline::default()));
308 }
309 let inline = entry
310 .inline
311 .get_or_insert_with(|| ExtensionInline::Provider(ProviderExtensionInline::default()));
312 if let ExtensionInline::Other(value) = inline {
313 let parsed = serde_json::from_value(value.clone())
314 .unwrap_or_else(|_| ProviderExtensionInline::default());
315 *inline = ExtensionInline::Provider(parsed);
316 }
317 match inline {
318 ExtensionInline::Provider(inline) => inline,
319 ExtensionInline::Other(_) => unreachable!("provider inline should be initialised"),
320 }
321 }
322
323 #[cfg(feature = "serde")]
325 pub fn get_component_sources_v1(
326 &self,
327 ) -> Result<Option<ComponentSourcesV1>, ComponentSourcesError> {
328 let extension = self
329 .extensions
330 .as_ref()
331 .and_then(|extensions| extensions.get(EXT_COMPONENT_SOURCES_V1));
332 let inline = match extension.and_then(|entry| entry.inline.as_ref()) {
333 Some(ExtensionInline::Other(value)) => value,
334 Some(_) => return Err(ComponentSourcesError::UnexpectedInline),
335 None => return Ok(None),
336 };
337 let payload = ComponentSourcesV1::from_extension_value(inline)?;
338 Ok(Some(payload))
339 }
340
341 #[cfg(feature = "serde")]
343 pub fn set_component_sources_v1(
344 &mut self,
345 sources: ComponentSourcesV1,
346 ) -> Result<(), ComponentSourcesError> {
347 sources.validate_schema_version()?;
348 let inline = sources.to_extension_value()?;
349 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
350 extensions.insert(
351 EXT_COMPONENT_SOURCES_V1.to_string(),
352 ExtensionRef {
353 kind: EXT_COMPONENT_SOURCES_V1.to_string(),
354 version: "1.0.0".to_string(),
355 digest: None,
356 location: None,
357 inline: Some(ExtensionInline::Other(inline)),
358 },
359 );
360 Ok(())
361 }
362
363 #[cfg(feature = "serde")]
365 pub fn get_capabilities_extension_v1(
366 &self,
367 ) -> Result<Option<CapabilitiesExtensionV1>, CapabilitiesExtensionError> {
368 let extension = self
369 .extensions
370 .as_ref()
371 .and_then(|extensions| extensions.get(EXT_CAPABILITIES_V1));
372 let inline = match extension.and_then(|entry| entry.inline.as_ref()) {
373 Some(ExtensionInline::Other(value)) => value,
374 Some(_) => return Err(CapabilitiesExtensionError::UnexpectedInline),
375 None => return Ok(None),
376 };
377 let payload = CapabilitiesExtensionV1::from_extension_value(inline)?;
378 Ok(Some(payload))
379 }
380
381 #[cfg(feature = "serde")]
383 pub fn set_capabilities_extension_v1(
384 &mut self,
385 capabilities: CapabilitiesExtensionV1,
386 ) -> Result<(), CapabilitiesExtensionError> {
387 capabilities.validate()?;
388 let inline = capabilities.to_extension_value()?;
389 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
390 extensions.insert(
391 EXT_CAPABILITIES_V1.to_string(),
392 ExtensionRef {
393 kind: EXT_CAPABILITIES_V1.to_string(),
394 version: "1.0.0".to_string(),
395 digest: None,
396 location: None,
397 inline: Some(ExtensionInline::Other(inline)),
398 },
399 );
400 Ok(())
401 }
402}