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 #[cfg_attr(
125 feature = "serde",
126 serde(default, skip_serializing_if = "BTreeMap::is_empty")
127 )]
128 #[cfg_attr(feature = "schemars", schemars(default))]
129 pub agents: BTreeMap<String, serde_json::Value>,
130}
131
132#[derive(Clone, Debug, PartialEq)]
134#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
135#[cfg_attr(feature = "schemars", derive(JsonSchema))]
136pub struct PackFlowEntry {
137 pub id: FlowId,
139 pub kind: FlowKind,
141 pub flow: Flow,
143 #[cfg_attr(feature = "serde", serde(default))]
145 pub tags: Vec<String>,
146 #[cfg_attr(feature = "serde", serde(default))]
148 pub entrypoints: Vec<String>,
149}
150
151#[derive(Clone, Debug, PartialEq, Eq)]
153#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
154#[cfg_attr(feature = "schemars", derive(JsonSchema))]
155pub struct PackDependency {
156 pub alias: String,
158 pub pack_id: PackId,
160 pub version_req: SemverReq,
162 #[cfg_attr(feature = "serde", serde(default))]
164 pub required_capabilities: Vec<String>,
165}
166
167#[derive(Clone, Debug, PartialEq, Eq)]
169#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
170#[cfg_attr(feature = "schemars", derive(JsonSchema))]
171pub struct ComponentCapability {
172 pub name: String,
174 #[cfg_attr(
176 feature = "serde",
177 serde(default, skip_serializing_if = "Option::is_none")
178 )]
179 pub description: Option<String>,
180}
181
182#[derive(Clone, Debug, PartialEq, Eq, Default)]
184#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
185#[cfg_attr(feature = "schemars", derive(JsonSchema))]
186pub struct PackSignatures {
187 #[cfg_attr(feature = "serde", serde(default))]
189 pub signatures: Vec<Signature>,
190}
191
192#[derive(Clone, Debug, PartialEq, Eq, Default)]
194#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
195#[cfg_attr(feature = "schemars", derive(JsonSchema))]
196pub struct BootstrapSpec {
197 #[cfg_attr(
199 feature = "serde",
200 serde(default, skip_serializing_if = "Option::is_none")
201 )]
202 pub install_flow: Option<String>,
203 #[cfg_attr(
205 feature = "serde",
206 serde(default, skip_serializing_if = "Option::is_none")
207 )]
208 pub upgrade_flow: Option<String>,
209 #[cfg_attr(
211 feature = "serde",
212 serde(default, skip_serializing_if = "Option::is_none")
213 )]
214 pub installer_component: Option<String>,
215}
216
217#[derive(Clone, Debug, PartialEq)]
219#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
220#[cfg_attr(feature = "serde", serde(untagged))]
221#[cfg_attr(feature = "schemars", derive(JsonSchema))]
222pub enum ExtensionInline {
223 Provider(ProviderExtensionInline),
225 Other(serde_json::Value),
227}
228
229impl ExtensionInline {
230 pub fn as_provider_inline(&self) -> Option<&ProviderExtensionInline> {
232 match self {
233 ExtensionInline::Provider(value) => Some(value),
234 ExtensionInline::Other(_) => None,
235 }
236 }
237
238 pub fn as_provider_inline_mut(&mut self) -> Option<&mut ProviderExtensionInline> {
240 match self {
241 ExtensionInline::Provider(value) => Some(value),
242 ExtensionInline::Other(_) => None,
243 }
244 }
245}
246
247#[derive(Clone, Debug, PartialEq)]
249#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
250#[cfg_attr(feature = "schemars", derive(JsonSchema))]
251pub struct ExtensionRef {
252 pub kind: String,
254 pub version: String,
256 #[cfg_attr(
258 feature = "serde",
259 serde(default, skip_serializing_if = "Option::is_none")
260 )]
261 pub digest: Option<String>,
262 #[cfg_attr(
264 feature = "serde",
265 serde(default, skip_serializing_if = "Option::is_none")
266 )]
267 pub location: Option<String>,
268 #[cfg_attr(
270 feature = "serde",
271 serde(default, skip_serializing_if = "Option::is_none")
272 )]
273 pub inline: Option<ExtensionInline>,
274}
275
276impl PackManifest {
277 pub fn provider_extension_inline(&self) -> Option<&ProviderExtensionInline> {
279 self.extensions
280 .as_ref()
281 .and_then(|extensions| extensions.get(PROVIDER_EXTENSION_ID))
282 .and_then(|extension| extension.inline.as_ref())
283 .and_then(ExtensionInline::as_provider_inline)
284 }
285
286 pub fn provider_extension_inline_mut(&mut self) -> Option<&mut ProviderExtensionInline> {
288 self.extensions
289 .as_mut()
290 .and_then(|extensions| extensions.get_mut(PROVIDER_EXTENSION_ID))
291 .and_then(|extension| extension.inline.as_mut())
292 .map(|inline| {
293 if let ExtensionInline::Other(value) = inline {
294 let parsed = serde_json::from_value(value.clone())
295 .unwrap_or_else(|_| ProviderExtensionInline::default());
296 *inline = ExtensionInline::Provider(parsed);
297 }
298 inline
299 })
300 .and_then(ExtensionInline::as_provider_inline_mut)
301 }
302
303 pub fn ensure_provider_extension_inline(&mut self) -> &mut ProviderExtensionInline {
305 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
306 let entry = extensions
307 .entry(PROVIDER_EXTENSION_ID.to_string())
308 .or_insert_with(|| ExtensionRef {
309 kind: PROVIDER_EXTENSION_ID.to_string(),
310 version: "1.0.0".to_string(),
311 digest: None,
312 location: None,
313 inline: Some(ExtensionInline::Provider(ProviderExtensionInline::default())),
314 });
315 if entry.inline.is_none() {
316 entry.inline = Some(ExtensionInline::Provider(ProviderExtensionInline::default()));
317 }
318 let inline = entry
319 .inline
320 .get_or_insert_with(|| ExtensionInline::Provider(ProviderExtensionInline::default()));
321 if let ExtensionInline::Other(value) = inline {
322 let parsed = serde_json::from_value(value.clone())
323 .unwrap_or_else(|_| ProviderExtensionInline::default());
324 *inline = ExtensionInline::Provider(parsed);
325 }
326 match inline {
327 ExtensionInline::Provider(inline) => inline,
328 ExtensionInline::Other(_) => unreachable!("provider inline should be initialised"),
329 }
330 }
331
332 #[cfg(feature = "serde")]
334 pub fn get_component_sources_v1(
335 &self,
336 ) -> Result<Option<ComponentSourcesV1>, ComponentSourcesError> {
337 let extension = self
338 .extensions
339 .as_ref()
340 .and_then(|extensions| extensions.get(EXT_COMPONENT_SOURCES_V1));
341 let inline = match extension.and_then(|entry| entry.inline.as_ref()) {
342 Some(ExtensionInline::Other(value)) => value,
343 Some(_) => return Err(ComponentSourcesError::UnexpectedInline),
344 None => return Ok(None),
345 };
346 let payload = ComponentSourcesV1::from_extension_value(inline)?;
347 Ok(Some(payload))
348 }
349
350 #[cfg(feature = "serde")]
352 pub fn set_component_sources_v1(
353 &mut self,
354 sources: ComponentSourcesV1,
355 ) -> Result<(), ComponentSourcesError> {
356 sources.validate_schema_version()?;
357 let inline = sources.to_extension_value()?;
358 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
359 extensions.insert(
360 EXT_COMPONENT_SOURCES_V1.to_string(),
361 ExtensionRef {
362 kind: EXT_COMPONENT_SOURCES_V1.to_string(),
363 version: "1.0.0".to_string(),
364 digest: None,
365 location: None,
366 inline: Some(ExtensionInline::Other(inline)),
367 },
368 );
369 Ok(())
370 }
371
372 #[cfg(feature = "serde")]
374 pub fn get_capabilities_extension_v1(
375 &self,
376 ) -> Result<Option<CapabilitiesExtensionV1>, CapabilitiesExtensionError> {
377 let extension = self
378 .extensions
379 .as_ref()
380 .and_then(|extensions| extensions.get(EXT_CAPABILITIES_V1));
381 let inline = match extension.and_then(|entry| entry.inline.as_ref()) {
382 Some(ExtensionInline::Other(value)) => value,
383 Some(_) => return Err(CapabilitiesExtensionError::UnexpectedInline),
384 None => return Ok(None),
385 };
386 let payload = CapabilitiesExtensionV1::from_extension_value(inline)?;
387 Ok(Some(payload))
388 }
389
390 #[cfg(feature = "serde")]
392 pub fn set_capabilities_extension_v1(
393 &mut self,
394 capabilities: CapabilitiesExtensionV1,
395 ) -> Result<(), CapabilitiesExtensionError> {
396 capabilities.validate()?;
397 let inline = capabilities.to_extension_value()?;
398 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
399 extensions.insert(
400 EXT_CAPABILITIES_V1.to_string(),
401 ExtensionRef {
402 kind: EXT_CAPABILITIES_V1.to_string(),
403 version: "1.0.0".to_string(),
404 digest: None,
405 location: None,
406 inline: Some(ExtensionInline::Other(inline)),
407 },
408 );
409 Ok(())
410 }
411}