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 GcpAgentPlatformEngineImportData, GcpAiImportData, GcpArtifactRegistryImportData,
30 GcpBuildImportData, GcpComputeClusterImportData, GcpKeyImportData, GcpKvImportData,
31 GcpNetworkImportData, GcpPostgresImportData, GcpQueueImportData, GcpRemoteBindingsImportData,
32 GcpRemoteStackManagementImportData, GcpSandboxImportData, 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 (
174 "gcp_agent_platform_engine",
175 schema::<GcpAgentPlatformEngineImportData>(),
176 ),
177 ("gcp_ai", schema::<GcpAiImportData>()),
178 (
179 "gcp_artifact_registry",
180 schema::<GcpArtifactRegistryImportData>(),
181 ),
182 ("gcp_build", schema::<GcpBuildImportData>()),
183 (
184 "gcp_compute_cluster",
185 schema::<GcpComputeClusterImportData>(),
186 ),
187 ("gcp_function", schema::<GcpWorkerImportData>()),
188 ("gcp_kv", schema::<GcpKvImportData>()),
189 ("gcp_key", schema::<GcpKeyImportData>()),
190 ("gcp_network", schema::<GcpNetworkImportData>()),
191 ("gcp_postgres", schema::<GcpPostgresImportData>()),
192 ("gcp_queue", schema::<GcpQueueImportData>()),
193 (
194 "gcp_remote_stack_management",
195 schema::<GcpRemoteStackManagementImportData>(),
196 ),
197 (
198 "kubernetes_cluster",
199 schema::<KubernetesClusterImportData>(),
200 ),
201 (
202 "gcp_service_account",
203 schema::<GcpServiceAccountImportData>(),
204 ),
205 (
206 "gcp_service_activation",
207 schema::<GcpServiceActivationImportData>(),
208 ),
209 ("gcp_sandbox", schema::<GcpSandboxImportData>()),
210 ("gcp_storage", schema::<GcpStorageImportData>()),
211 ("gcp_vault", schema::<GcpVaultImportData>()),
212 ]);
213
214 insta::assert_json_snapshot!("import_data_schemas", schemas);
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use serde_json::json;
222
223 #[test]
224 fn aws_import_data_accepts_cloudformation_stringified_leaves() {
225 let network: AwsNetworkImportData = serde_json::from_value(json!({
226 "vpcId": "vpc-123",
227 "cidrBlock": null,
228 "internetGatewayId": null,
229 "natGatewayId": null,
230 "eipAllocationId": null,
231 "publicSubnetIds": ["subnet-public"],
232 "privateSubnetIds": ["subnet-private"],
233 "publicRouteTableId": null,
234 "privateRouteTableId": null,
235 "securityGroupId": "sg-123",
236 "availabilityZones": [],
237 "isByoVpc": "true",
238 }))
239 .expect("network import data should parse");
240 assert!(network.is_byo_vpc);
241
242 let remote_stack_management: AwsRemoteStackManagementImportData =
243 serde_json::from_value(json!({
244 "roleName": "alien-manager",
245 "roleArn": "arn:aws:iam::123456789012:role/alien-manager",
246 "managementPermissionsApplied": "true",
247 }))
248 .expect("remote stack management import data should parse");
249 assert!(remote_stack_management.management_permissions_applied);
250
251 let service_account: AwsServiceAccountImportData = serde_json::from_value(json!({
252 "roleName": "alien-worker",
253 "roleArn": "arn:aws:iam::123456789012:role/alien-worker",
254 "stackPermissionsApplied": "false",
255 }))
256 .expect("service account import data should parse");
257 assert!(!service_account.stack_permissions_applied);
258
259 let sandbox: AwsSandboxImportData = serde_json::from_value(json!({
263 "imageIdentifier": "runner",
264 "imageArn": "arn:aws:lambda:us-east-2:123456789012:microvm-image/runner",
265 "imageVersion": "1",
266 "egressConnectorArns": [],
267 "allowEgress": "true",
268 "previewPorts": ["3000", "8080"],
269 }))
270 .expect("sandbox import data should parse CloudFormation's stringified leaves");
271 assert!(sandbox.allow_egress);
272 assert_eq!(sandbox.preview_ports, vec![3000, 8080]);
273
274 let native: AwsSandboxImportData = serde_json::from_value(json!({
275 "imageIdentifier": "runner",
276 "imageArn": "arn:aws:lambda:us-east-2:123456789012:microvm-image/runner",
277 "imageVersion": "1",
278 "egressConnectorArns": [],
279 "allowEgress": false,
280 "previewPorts": [3000],
281 }))
282 .expect("sandbox import data should still parse native JSON types");
283 assert!(!native.allow_egress);
284 assert_eq!(native.preview_ports, vec![3000]);
285 }
286
287 #[test]
291 fn a_runtime_provisioned_sandbox_registers_without_an_image() {
292 let runtime: AwsSandboxImportData = serde_json::from_value(json!({
293 "egressConnectorArns": ["arn:aws:lambda:us-east-2:123456789012:network-connector/c-1"],
294 "allowEgress": "false",
295 "previewPorts": ["8080"],
296 "buildRoleArn": "arn:aws:iam::123456789012:role/acme-agents-build",
297 "bundleUri": "s3://acme-artifacts-us-east-2/agents/bundle.zip",
298 }))
299 .expect("a runtime-provisioned sandbox must register without image fields");
300
301 assert_eq!(runtime.image_arn, None);
302 assert_eq!(runtime.image_identifier, None);
303 assert_eq!(runtime.image_version, None);
304 assert_eq!(
305 runtime.build_role_arn.as_deref(),
306 Some("arn:aws:iam::123456789012:role/acme-agents-build")
307 );
308 assert_eq!(
309 runtime.bundle_uri.as_deref(),
310 Some("s3://acme-artifacts-us-east-2/agents/bundle.zip")
311 );
312 assert_eq!(runtime.preview_ports, vec![8080]);
313
314 let frozen: AwsSandboxImportData = serde_json::from_value(json!({
317 "imageIdentifier": "runner",
318 "imageArn": "arn:aws:lambda:us-east-2:123456789012:microvm-image/runner",
319 "imageVersion": "1.0",
320 }))
321 .expect("the setup-built shape still parses");
322 assert_eq!(frozen.build_role_arn, None);
323 assert_eq!(frozen.bundle_uri, None);
324 assert_eq!(frozen.image_version.as_deref(), Some("1.0"));
325 }
326}