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(with = "String", description = "SemVer version")
68 )]
69 pub version: Version,
70 pub kind: PackKind,
72 pub publisher: String,
74 #[cfg_attr(feature = "serde", serde(default))]
76 pub components: Vec<ComponentManifest>,
77 #[cfg_attr(feature = "serde", serde(default))]
79 pub flows: Vec<PackFlowEntry>,
80 #[cfg_attr(feature = "serde", serde(default))]
82 pub dependencies: Vec<PackDependency>,
83 #[cfg_attr(feature = "serde", serde(default))]
85 pub capabilities: Vec<ComponentCapability>,
86 #[cfg_attr(
88 feature = "serde",
89 serde(default, skip_serializing_if = "Vec::is_empty")
90 )]
91 #[cfg_attr(feature = "schemars", schemars(default = "empty_secret_requirements"))]
92 pub secret_requirements: Vec<SecretRequirement>,
93 #[cfg_attr(feature = "serde", serde(default))]
95 pub signatures: PackSignatures,
96 #[cfg_attr(
98 feature = "serde",
99 serde(default, skip_serializing_if = "Option::is_none")
100 )]
101 pub bootstrap: Option<BootstrapSpec>,
102 #[cfg_attr(
104 feature = "serde",
105 serde(default, skip_serializing_if = "extensions_is_empty")
106 )]
107 pub extensions: Option<BTreeMap<String, ExtensionRef>>,
108}
109
110#[derive(Clone, Debug, PartialEq)]
112#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
113#[cfg_attr(feature = "schemars", derive(JsonSchema))]
114pub struct PackFlowEntry {
115 pub id: FlowId,
117 pub kind: FlowKind,
119 pub flow: Flow,
121 #[cfg_attr(feature = "serde", serde(default))]
123 pub tags: Vec<String>,
124 #[cfg_attr(feature = "serde", serde(default))]
126 pub entrypoints: Vec<String>,
127}
128
129#[derive(Clone, Debug, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
132#[cfg_attr(feature = "schemars", derive(JsonSchema))]
133pub struct PackDependency {
134 pub alias: String,
136 pub pack_id: PackId,
138 pub version_req: SemverReq,
140 #[cfg_attr(feature = "serde", serde(default))]
142 pub required_capabilities: Vec<String>,
143}
144
145#[derive(Clone, Debug, PartialEq, Eq)]
147#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
148#[cfg_attr(feature = "schemars", derive(JsonSchema))]
149pub struct ComponentCapability {
150 pub name: String,
152 #[cfg_attr(
154 feature = "serde",
155 serde(default, skip_serializing_if = "Option::is_none")
156 )]
157 pub description: Option<String>,
158}
159
160#[derive(Clone, Debug, PartialEq, Eq, Default)]
162#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
163#[cfg_attr(feature = "schemars", derive(JsonSchema))]
164pub struct PackSignatures {
165 #[cfg_attr(feature = "serde", serde(default))]
167 pub signatures: Vec<Signature>,
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 BootstrapSpec {
175 #[cfg_attr(
177 feature = "serde",
178 serde(default, skip_serializing_if = "Option::is_none")
179 )]
180 pub install_flow: Option<String>,
181 #[cfg_attr(
183 feature = "serde",
184 serde(default, skip_serializing_if = "Option::is_none")
185 )]
186 pub upgrade_flow: Option<String>,
187 #[cfg_attr(
189 feature = "serde",
190 serde(default, skip_serializing_if = "Option::is_none")
191 )]
192 pub installer_component: Option<String>,
193}
194
195#[derive(Clone, Debug, PartialEq)]
197#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
198#[cfg_attr(feature = "serde", serde(untagged))]
199#[cfg_attr(feature = "schemars", derive(JsonSchema))]
200pub enum ExtensionInline {
201 Provider(ProviderExtensionInline),
203 Other(serde_json::Value),
205}
206
207impl ExtensionInline {
208 pub fn as_provider_inline(&self) -> Option<&ProviderExtensionInline> {
210 match self {
211 ExtensionInline::Provider(value) => Some(value),
212 ExtensionInline::Other(_) => None,
213 }
214 }
215
216 pub fn as_provider_inline_mut(&mut self) -> Option<&mut ProviderExtensionInline> {
218 match self {
219 ExtensionInline::Provider(value) => Some(value),
220 ExtensionInline::Other(_) => None,
221 }
222 }
223}
224
225#[derive(Clone, Debug, PartialEq)]
227#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
228#[cfg_attr(feature = "schemars", derive(JsonSchema))]
229pub struct ExtensionRef {
230 pub kind: String,
232 pub version: String,
234 #[cfg_attr(
236 feature = "serde",
237 serde(default, skip_serializing_if = "Option::is_none")
238 )]
239 pub digest: Option<String>,
240 #[cfg_attr(
242 feature = "serde",
243 serde(default, skip_serializing_if = "Option::is_none")
244 )]
245 pub location: Option<String>,
246 #[cfg_attr(
248 feature = "serde",
249 serde(default, skip_serializing_if = "Option::is_none")
250 )]
251 pub inline: Option<ExtensionInline>,
252}
253
254impl PackManifest {
255 pub fn provider_extension_inline(&self) -> Option<&ProviderExtensionInline> {
257 self.extensions
258 .as_ref()
259 .and_then(|extensions| extensions.get(PROVIDER_EXTENSION_ID))
260 .and_then(|extension| extension.inline.as_ref())
261 .and_then(ExtensionInline::as_provider_inline)
262 }
263
264 pub fn provider_extension_inline_mut(&mut self) -> Option<&mut ProviderExtensionInline> {
266 self.extensions
267 .as_mut()
268 .and_then(|extensions| extensions.get_mut(PROVIDER_EXTENSION_ID))
269 .and_then(|extension| extension.inline.as_mut())
270 .map(|inline| {
271 if let ExtensionInline::Other(value) = inline {
272 let parsed = serde_json::from_value(value.clone())
273 .unwrap_or_else(|_| ProviderExtensionInline::default());
274 *inline = ExtensionInline::Provider(parsed);
275 }
276 inline
277 })
278 .and_then(ExtensionInline::as_provider_inline_mut)
279 }
280
281 pub fn ensure_provider_extension_inline(&mut self) -> &mut ProviderExtensionInline {
283 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
284 let entry = extensions
285 .entry(PROVIDER_EXTENSION_ID.to_string())
286 .or_insert_with(|| ExtensionRef {
287 kind: PROVIDER_EXTENSION_ID.to_string(),
288 version: "1.0.0".to_string(),
289 digest: None,
290 location: None,
291 inline: Some(ExtensionInline::Provider(ProviderExtensionInline::default())),
292 });
293 if entry.inline.is_none() {
294 entry.inline = Some(ExtensionInline::Provider(ProviderExtensionInline::default()));
295 }
296 let inline = entry
297 .inline
298 .get_or_insert_with(|| ExtensionInline::Provider(ProviderExtensionInline::default()));
299 if let ExtensionInline::Other(value) = inline {
300 let parsed = serde_json::from_value(value.clone())
301 .unwrap_or_else(|_| ProviderExtensionInline::default());
302 *inline = ExtensionInline::Provider(parsed);
303 }
304 match inline {
305 ExtensionInline::Provider(inline) => inline,
306 ExtensionInline::Other(_) => unreachable!("provider inline should be initialised"),
307 }
308 }
309
310 #[cfg(feature = "serde")]
312 pub fn get_component_sources_v1(
313 &self,
314 ) -> Result<Option<ComponentSourcesV1>, ComponentSourcesError> {
315 let extension = self
316 .extensions
317 .as_ref()
318 .and_then(|extensions| extensions.get(EXT_COMPONENT_SOURCES_V1));
319 let inline = match extension.and_then(|entry| entry.inline.as_ref()) {
320 Some(ExtensionInline::Other(value)) => value,
321 Some(_) => return Err(ComponentSourcesError::UnexpectedInline),
322 None => return Ok(None),
323 };
324 let payload = ComponentSourcesV1::from_extension_value(inline)?;
325 Ok(Some(payload))
326 }
327
328 #[cfg(feature = "serde")]
330 pub fn set_component_sources_v1(
331 &mut self,
332 sources: ComponentSourcesV1,
333 ) -> Result<(), ComponentSourcesError> {
334 sources.validate_schema_version()?;
335 let inline = sources.to_extension_value()?;
336 let extensions = self.extensions.get_or_insert_with(BTreeMap::new);
337 extensions.insert(
338 EXT_COMPONENT_SOURCES_V1.to_string(),
339 ExtensionRef {
340 kind: EXT_COMPONENT_SOURCES_V1.to_string(),
341 version: "1.0.0".to_string(),
342 digest: None,
343 location: None,
344 inline: Some(ExtensionInline::Other(inline)),
345 },
346 );
347 Ok(())
348 }
349}