1use crate::CloudConfigFragment;
8use crate::ConfigLayerEntry;
9use crate::RequirementSource;
10use crate::RequirementsLayerEntry;
11use crate::cloud_config_layers::CloudConfigLayerError;
12use crate::cloud_config_layers::cloud_config_layers_from_fragments_strict;
13use crate::cloud_config_layers_from_fragments;
14use codex_utils_absolute_path::AbsolutePathBuf;
15use futures::future::BoxFuture;
16use futures::future::FutureExt;
17use futures::future::Shared;
18use serde::Deserialize;
19use serde::Serialize;
20use std::fmt;
21use std::future::Future;
22use thiserror::Error;
23
24#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
25pub struct CloudConfigBundle {
26 pub config_toml: CloudConfigTomlBundle,
27 pub requirements_toml: CloudRequirementsTomlBundle,
28}
29
30impl CloudConfigBundle {
31 pub fn is_empty(&self) -> bool {
32 let CloudConfigBundle {
33 config_toml,
34 requirements_toml,
35 } = self;
36 let CloudConfigTomlBundle {
37 enterprise_managed: config_enterprise_managed,
38 } = config_toml;
39 let CloudRequirementsTomlBundle {
40 enterprise_managed: requirements_enterprise_managed,
41 } = requirements_toml;
42
43 config_enterprise_managed.is_empty() && requirements_enterprise_managed.is_empty()
44 }
45}
46
47#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
48pub struct CloudConfigTomlBundle {
49 pub enterprise_managed: Vec<CloudConfigFragment>,
50}
51
52#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
53pub struct CloudRequirementsTomlBundle {
54 pub enterprise_managed: Vec<CloudRequirementsFragment>,
55}
56
57#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
58pub struct CloudRequirementsFragment {
59 pub id: String,
60 pub name: String,
61 pub contents: String,
62}
63
64#[derive(Clone, Debug)]
69pub struct CloudConfigBundleLayers {
70 pub enterprise_managed_config: Vec<ConfigLayerEntry>,
72 pub enterprise_managed_requirements: Vec<RequirementsLayerEntry>,
74}
75
76impl CloudConfigBundleLayers {
77 pub fn from_bundle(
78 bundle: CloudConfigBundle,
79 base_dir: &AbsolutePathBuf,
80 ) -> Result<Self, CloudConfigLayerError> {
81 Self::from_bundle_impl(bundle, base_dir, false)
82 }
83
84 pub fn from_bundle_strict_config(
85 bundle: CloudConfigBundle,
86 base_dir: &AbsolutePathBuf,
87 ) -> Result<Self, CloudConfigLayerError> {
88 Self::from_bundle_impl(bundle, base_dir, true)
89 }
90
91 fn from_bundle_impl(
92 bundle: CloudConfigBundle,
93 base_dir: &AbsolutePathBuf,
94 strict_config: bool,
95 ) -> Result<Self, CloudConfigLayerError> {
96 let CloudConfigBundle {
99 config_toml:
100 CloudConfigTomlBundle {
101 enterprise_managed: config_enterprise_managed,
102 },
103 requirements_toml:
104 CloudRequirementsTomlBundle {
105 enterprise_managed: requirements_enterprise_managed,
106 },
107 } = bundle;
108
109 let enterprise_managed_config = if strict_config {
110 cloud_config_layers_from_fragments_strict(config_enterprise_managed, base_dir)?
111 } else {
112 cloud_config_layers_from_fragments(config_enterprise_managed, base_dir)?
113 };
114
115 let mut enterprise_managed_requirements = requirements_enterprise_managed
116 .into_iter()
117 .map(|fragment| {
118 RequirementsLayerEntry::from_toml(
119 RequirementSource::EnterpriseManaged {
120 id: fragment.id,
121 name: fragment.name,
122 },
123 fragment.contents,
124 )
125 .with_base_dir(base_dir.clone())
126 })
127 .collect::<Vec<_>>();
128 enterprise_managed_requirements.reverse();
131
132 Ok(Self {
133 enterprise_managed_config,
134 enterprise_managed_requirements,
135 })
136 }
137}
138
139#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140pub enum CloudConfigBundleLoadErrorCode {
141 Auth,
142 Timeout,
143 RequestFailed,
144 InvalidBundle,
145 Internal,
146}
147
148#[derive(Clone, Debug, Eq, Error, PartialEq)]
149#[error("{message}")]
150pub struct CloudConfigBundleLoadError {
151 code: CloudConfigBundleLoadErrorCode,
152 message: String,
153 status_code: Option<u16>,
154}
155
156impl CloudConfigBundleLoadError {
157 pub fn new(
158 code: CloudConfigBundleLoadErrorCode,
159 status_code: Option<u16>,
160 message: impl Into<String>,
161 ) -> Self {
162 Self {
163 code,
164 message: message.into(),
165 status_code,
166 }
167 }
168
169 pub fn code(&self) -> CloudConfigBundleLoadErrorCode {
170 self.code
171 }
172
173 pub fn status_code(&self) -> Option<u16> {
174 self.status_code
175 }
176}
177
178#[derive(Clone)]
179pub struct CloudConfigBundleLoader {
180 fut: Shared<BoxFuture<'static, Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError>>>,
181}
182
183impl CloudConfigBundleLoader {
184 pub fn new<F>(fut: F) -> Self
185 where
186 F: Future<Output = Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError>>
187 + Send
188 + 'static,
189 {
190 Self {
191 fut: fut.boxed().shared(),
192 }
193 }
194
195 pub async fn get(&self) -> Result<Option<CloudConfigBundle>, CloudConfigBundleLoadError> {
196 self.fut.clone().await
197 }
198}
199
200impl fmt::Debug for CloudConfigBundleLoader {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 f.debug_struct("CloudConfigBundleLoader").finish()
203 }
204}
205
206impl Default for CloudConfigBundleLoader {
207 fn default() -> Self {
208 Self::new(async { Ok(None) })
209 }
210}
211
212#[cfg(test)]
213#[path = "cloud_config_bundle_tests.rs"]
214mod tests;