1use serde::{Deserialize, Deserializer};
4
5pub mod aws;
6pub mod azure;
7pub mod gcp;
8pub mod kubernetes_cluster;
9
10pub use aws::{
11 AwsAiImportData, AwsArtifactRegistryImportData, AwsBuildImportData,
12 AwsComputeClusterImportData, AwsEmailDkimTokenImportData, AwsEmailDomainImportData,
13 AwsEmailImportData, AwsKeyImportData, AwsKvImportData, AwsNetworkImportData,
14 AwsOpenSearchImportData, AwsPostgresImportData, AwsQueueImportData,
15 AwsRemoteBindingsImportData, AwsRemoteStackManagementImportData, AwsSandboxImportData,
16 AwsServiceAccountImportData, AwsStorageImportData, AwsVaultImportData, AwsWorkerImportData,
17};
18pub use azure::{
19 AzureAiImportData, AzureArtifactRegistryImportData, AzureBuildImportData,
20 AzureComputeClusterImportData, AzureContainerAppsEnvironmentImportData,
21 AzureFlexibleServerPostgresImportData, AzureKeyImportData, AzureKvImportData,
22 AzureNetworkImportData, AzureQueueImportData, AzureRemoteBindingsImportData,
23 AzureRemoteStackManagementImportData, AzureResourceGroupImportData, AzureSandboxImportData,
24 AzureServiceAccountImportData, AzureServiceActivationImportData,
25 AzureServiceBusNamespaceImportData, AzureStorageAccountImportData, AzureStorageImportData,
26 AzureVaultImportData, AzureWorkerImportData,
27};
28pub use gcp::{
29 GcpAiImportData, GcpArtifactRegistryImportData, GcpBuildImportData,
30 GcpComputeClusterImportData, GcpKeyImportData, GcpKvImportData, GcpNetworkImportData,
31 GcpPostgresImportData, GcpQueueImportData, GcpRemoteBindingsImportData,
32 GcpRemoteStackManagementImportData, GcpServiceAccountImportData,
33 GcpServiceActivationImportData, GcpStorageImportData, GcpVaultImportData, GcpWorkerImportData,
34};
35pub use kubernetes_cluster::{
36 AzureApplicationGatewayForContainersBootstrap, KubernetesClusterImportData,
37};
38
39pub(crate) fn deserialize_bool_from_bool_or_string<'de, D>(
40 deserializer: D,
41) -> Result<bool, D::Error>
42where
43 D: Deserializer<'de>,
44{
45 let value = serde_json::Value::deserialize(deserializer)?;
46 match value {
47 serde_json::Value::Bool(value) => Ok(value),
48 serde_json::Value::String(value) if value.eq_ignore_ascii_case("true") => Ok(true),
49 serde_json::Value::String(value) if value.eq_ignore_ascii_case("false") => Ok(false),
50 other => Err(serde::de::Error::custom(format!(
51 "expected boolean or boolean string, got {other}"
52 ))),
53 }
54}
55
56pub(crate) fn deserialize_u16_vec_from_numbers_or_strings<'de, D>(
59 deserializer: D,
60) -> Result<Vec<u16>, D::Error>
61where
62 D: Deserializer<'de>,
63{
64 let values = Vec::<serde_json::Value>::deserialize(deserializer)?;
65 values
66 .into_iter()
67 .map(|value| match value {
68 serde_json::Value::Number(number) => number
69 .as_u64()
70 .and_then(|n| u16::try_from(n).ok())
71 .ok_or_else(|| serde::de::Error::custom(format!("port {number} is out of range"))),
72 serde_json::Value::String(text) => text.parse::<u16>().map_err(|_| {
73 serde::de::Error::custom(format!("expected a port number, got {text:?}"))
74 }),
75 other => Err(serde::de::Error::custom(format!(
76 "expected a port number or numeric string, got {other}"
77 ))),
78 })
79 .collect()
80}
81
82#[cfg(all(test, feature = "jsonschema"))]
83mod schema_snapshots {
84 use super::*;
85 use indexmap::IndexMap;
86
87 fn schema<T: schemars::JsonSchema>() -> serde_json::Value {
88 serde_json::to_value(schemars::schema_for!(T)).expect("schema should serialize")
89 }
90
91 #[test]
92 fn import_data_schema_snapshot() {
93 let schemas = IndexMap::from([
94 ("aws_ai", schema::<AwsAiImportData>()),
95 (
96 "aws_artifact_registry",
97 schema::<AwsArtifactRegistryImportData>(),
98 ),
99 ("aws_build", schema::<AwsBuildImportData>()),
100 (
101 "aws_compute_cluster",
102 schema::<AwsComputeClusterImportData>(),
103 ),
104 ("aws_email", schema::<AwsEmailImportData>()),
105 ("aws_function", schema::<AwsWorkerImportData>()),
106 ("aws_kv", schema::<AwsKvImportData>()),
107 ("aws_key", schema::<AwsKeyImportData>()),
108 ("aws_sandbox", schema::<AwsSandboxImportData>()),
109 ("aws_network", schema::<AwsNetworkImportData>()),
110 ("aws_open_search", schema::<AwsOpenSearchImportData>()),
111 ("aws_postgres", schema::<AwsPostgresImportData>()),
112 ("aws_queue", schema::<AwsQueueImportData>()),
113 (
114 "aws_remote_stack_management",
115 schema::<AwsRemoteStackManagementImportData>(),
116 ),
117 (
118 "aws_service_account",
119 schema::<AwsServiceAccountImportData>(),
120 ),
121 ("aws_storage", schema::<AwsStorageImportData>()),
122 ("aws_vault", schema::<AwsVaultImportData>()),
123 ("azure_ai", schema::<AzureAiImportData>()),
124 (
125 "azure_artifact_registry",
126 schema::<AzureArtifactRegistryImportData>(),
127 ),
128 ("azure_build", schema::<AzureBuildImportData>()),
129 (
130 "azure_container_apps_environment",
131 schema::<AzureContainerAppsEnvironmentImportData>(),
132 ),
133 (
134 "azure_compute_cluster",
135 schema::<AzureComputeClusterImportData>(),
136 ),
137 ("azure_function", schema::<AzureWorkerImportData>()),
138 ("azure_kv", schema::<AzureKvImportData>()),
139 ("azure_key", schema::<AzureKeyImportData>()),
140 ("azure_sandbox", schema::<AzureSandboxImportData>()),
141 ("azure_network", schema::<AzureNetworkImportData>()),
142 (
143 "azure_postgres",
144 schema::<AzureFlexibleServerPostgresImportData>(),
145 ),
146 ("azure_queue", schema::<AzureQueueImportData>()),
147 (
148 "azure_remote_stack_management",
149 schema::<AzureRemoteStackManagementImportData>(),
150 ),
151 (
152 "azure_resource_group",
153 schema::<AzureResourceGroupImportData>(),
154 ),
155 (
156 "azure_service_account",
157 schema::<AzureServiceAccountImportData>(),
158 ),
159 (
160 "azure_service_activation",
161 schema::<AzureServiceActivationImportData>(),
162 ),
163 (
164 "azure_service_bus_namespace",
165 schema::<AzureServiceBusNamespaceImportData>(),
166 ),
167 ("azure_storage", schema::<AzureStorageImportData>()),
168 (
169 "azure_storage_account",
170 schema::<AzureStorageAccountImportData>(),
171 ),
172 ("azure_vault", schema::<AzureVaultImportData>()),
173 ("gcp_ai", schema::<GcpAiImportData>()),
174 (
175 "gcp_artifact_registry",
176 schema::<GcpArtifactRegistryImportData>(),
177 ),
178 ("gcp_build", schema::<GcpBuildImportData>()),
179 (
180 "gcp_compute_cluster",
181 schema::<GcpComputeClusterImportData>(),
182 ),
183 ("gcp_function", schema::<GcpWorkerImportData>()),
184 ("gcp_kv", schema::<GcpKvImportData>()),
185 ("gcp_key", schema::<GcpKeyImportData>()),
186 ("gcp_network", schema::<GcpNetworkImportData>()),
187 ("gcp_postgres", schema::<GcpPostgresImportData>()),
188 ("gcp_queue", schema::<GcpQueueImportData>()),
189 (
190 "gcp_remote_stack_management",
191 schema::<GcpRemoteStackManagementImportData>(),
192 ),
193 (
194 "kubernetes_cluster",
195 schema::<KubernetesClusterImportData>(),
196 ),
197 (
198 "gcp_service_account",
199 schema::<GcpServiceAccountImportData>(),
200 ),
201 (
202 "gcp_service_activation",
203 schema::<GcpServiceActivationImportData>(),
204 ),
205 ("gcp_storage", schema::<GcpStorageImportData>()),
206 ("gcp_vault", schema::<GcpVaultImportData>()),
207 ]);
208
209 insta::assert_json_snapshot!("import_data_schemas", schemas);
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use serde_json::json;
217
218 #[test]
219 fn aws_import_data_accepts_cloudformation_stringified_leaves() {
220 let network: AwsNetworkImportData = serde_json::from_value(json!({
221 "vpcId": "vpc-123",
222 "cidrBlock": null,
223 "internetGatewayId": null,
224 "natGatewayId": null,
225 "eipAllocationId": null,
226 "publicSubnetIds": ["subnet-public"],
227 "privateSubnetIds": ["subnet-private"],
228 "publicRouteTableId": null,
229 "privateRouteTableId": null,
230 "securityGroupId": "sg-123",
231 "availabilityZones": [],
232 "isByoVpc": "true",
233 }))
234 .expect("network import data should parse");
235 assert!(network.is_byo_vpc);
236
237 let remote_stack_management: AwsRemoteStackManagementImportData =
238 serde_json::from_value(json!({
239 "roleName": "alien-manager",
240 "roleArn": "arn:aws:iam::123456789012:role/alien-manager",
241 "managementPermissionsApplied": "true",
242 }))
243 .expect("remote stack management import data should parse");
244 assert!(remote_stack_management.management_permissions_applied);
245
246 let service_account: AwsServiceAccountImportData = serde_json::from_value(json!({
247 "roleName": "alien-worker",
248 "roleArn": "arn:aws:iam::123456789012:role/alien-worker",
249 "stackPermissionsApplied": "false",
250 }))
251 .expect("service account import data should parse");
252 assert!(!service_account.stack_permissions_applied);
253
254 let sandbox: AwsSandboxImportData = serde_json::from_value(json!({
258 "imageIdentifier": "runner",
259 "imageArn": "arn:aws:lambda:us-east-2:123456789012:microvm-image/runner",
260 "imageVersion": "1",
261 "egressConnectorArns": [],
262 "allowEgress": "true",
263 "previewPorts": ["3000", "8080"],
264 }))
265 .expect("sandbox import data should parse CloudFormation's stringified leaves");
266 assert!(sandbox.allow_egress);
267 assert_eq!(sandbox.preview_ports, vec![3000, 8080]);
268
269 let native: AwsSandboxImportData = serde_json::from_value(json!({
270 "imageIdentifier": "runner",
271 "imageArn": "arn:aws:lambda:us-east-2:123456789012:microvm-image/runner",
272 "imageVersion": "1",
273 "egressConnectorArns": [],
274 "allowEgress": false,
275 "previewPorts": [3000],
276 }))
277 .expect("sandbox import data should still parse native JSON types");
278 assert!(!native.allow_egress);
279 assert_eq!(native.preview_ports, vec![3000]);
280 }
281
282 #[test]
286 fn a_runtime_provisioned_sandbox_registers_without_an_image() {
287 let runtime: AwsSandboxImportData = serde_json::from_value(json!({
288 "egressConnectorArns": ["arn:aws:lambda:us-east-2:123456789012:network-connector/c-1"],
289 "allowEgress": "false",
290 "previewPorts": ["8080"],
291 "buildRoleArn": "arn:aws:iam::123456789012:role/acme-agents-build",
292 "bundleUri": "s3://acme-artifacts-us-east-2/agents/bundle.zip",
293 }))
294 .expect("a runtime-provisioned sandbox must register without image fields");
295
296 assert_eq!(runtime.image_arn, None);
297 assert_eq!(runtime.image_identifier, None);
298 assert_eq!(runtime.image_version, None);
299 assert_eq!(
300 runtime.build_role_arn.as_deref(),
301 Some("arn:aws:iam::123456789012:role/acme-agents-build")
302 );
303 assert_eq!(
304 runtime.bundle_uri.as_deref(),
305 Some("s3://acme-artifacts-us-east-2/agents/bundle.zip")
306 );
307 assert_eq!(runtime.preview_ports, vec![8080]);
308
309 let frozen: AwsSandboxImportData = serde_json::from_value(json!({
312 "imageIdentifier": "runner",
313 "imageArn": "arn:aws:lambda:us-east-2:123456789012:microvm-image/runner",
314 "imageVersion": "1.0",
315 }))
316 .expect("the setup-built shape still parses");
317 assert_eq!(frozen.build_role_arn, None);
318 assert_eq!(frozen.bundle_uri, None);
319 assert_eq!(frozen.image_version.as_deref(), Some("1.0"));
320 }
321}