use serde::{Deserialize, Deserializer};
pub mod aws;
pub mod azure;
pub mod gcp;
pub mod kubernetes_cluster;
pub use aws::{
AwsAiImportData, AwsArtifactRegistryImportData, AwsBuildImportData,
AwsComputeClusterImportData, AwsEmailDkimTokenImportData, AwsEmailDomainImportData,
AwsEmailImportData, AwsKeyImportData, AwsKvImportData, AwsNetworkImportData,
AwsOpenSearchImportData, AwsPostgresImportData, AwsQueueImportData,
AwsRemoteBindingsImportData, AwsRemoteStackManagementImportData, AwsSandboxImportData,
AwsServiceAccountImportData, AwsStorageImportData, AwsVaultImportData, AwsWorkerImportData,
};
pub use azure::{
AzureAiImportData, AzureArtifactRegistryImportData, AzureBuildImportData,
AzureComputeClusterImportData, AzureContainerAppsEnvironmentImportData,
AzureFlexibleServerPostgresImportData, AzureKeyImportData, AzureKvImportData,
AzureNetworkImportData, AzureQueueImportData, AzureRemoteBindingsImportData,
AzureRemoteStackManagementImportData, AzureResourceGroupImportData, AzureSandboxImportData,
AzureServiceAccountImportData, AzureServiceActivationImportData,
AzureServiceBusNamespaceImportData, AzureStorageAccountImportData, AzureStorageImportData,
AzureVaultImportData, AzureWorkerImportData,
};
pub use gcp::{
GcpAiImportData, GcpArtifactRegistryImportData, GcpBuildImportData,
GcpComputeClusterImportData, GcpKeyImportData, GcpKvImportData, GcpNetworkImportData,
GcpPostgresImportData, GcpQueueImportData, GcpRemoteBindingsImportData,
GcpRemoteStackManagementImportData, GcpServiceAccountImportData,
GcpServiceActivationImportData, GcpStorageImportData, GcpVaultImportData, GcpWorkerImportData,
};
pub use kubernetes_cluster::{
AzureApplicationGatewayForContainersBootstrap, KubernetesClusterImportData,
};
pub(crate) fn deserialize_bool_from_bool_or_string<'de, D>(
deserializer: D,
) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::Bool(value) => Ok(value),
serde_json::Value::String(value) if value.eq_ignore_ascii_case("true") => Ok(true),
serde_json::Value::String(value) if value.eq_ignore_ascii_case("false") => Ok(false),
other => Err(serde::de::Error::custom(format!(
"expected boolean or boolean string, got {other}"
))),
}
}
pub(crate) fn deserialize_u16_vec_from_numbers_or_strings<'de, D>(
deserializer: D,
) -> Result<Vec<u16>, D::Error>
where
D: Deserializer<'de>,
{
let values = Vec::<serde_json::Value>::deserialize(deserializer)?;
values
.into_iter()
.map(|value| match value {
serde_json::Value::Number(number) => number
.as_u64()
.and_then(|n| u16::try_from(n).ok())
.ok_or_else(|| serde::de::Error::custom(format!("port {number} is out of range"))),
serde_json::Value::String(text) => text.parse::<u16>().map_err(|_| {
serde::de::Error::custom(format!("expected a port number, got {text:?}"))
}),
other => Err(serde::de::Error::custom(format!(
"expected a port number or numeric string, got {other}"
))),
})
.collect()
}
#[cfg(all(test, feature = "jsonschema"))]
mod schema_snapshots {
use super::*;
use indexmap::IndexMap;
fn schema<T: schemars::JsonSchema>() -> serde_json::Value {
serde_json::to_value(schemars::schema_for!(T)).expect("schema should serialize")
}
#[test]
fn import_data_schema_snapshot() {
let schemas = IndexMap::from([
("aws_ai", schema::<AwsAiImportData>()),
(
"aws_artifact_registry",
schema::<AwsArtifactRegistryImportData>(),
),
("aws_build", schema::<AwsBuildImportData>()),
(
"aws_compute_cluster",
schema::<AwsComputeClusterImportData>(),
),
("aws_email", schema::<AwsEmailImportData>()),
("aws_function", schema::<AwsWorkerImportData>()),
("aws_kv", schema::<AwsKvImportData>()),
("aws_key", schema::<AwsKeyImportData>()),
("aws_sandbox", schema::<AwsSandboxImportData>()),
("aws_network", schema::<AwsNetworkImportData>()),
("aws_open_search", schema::<AwsOpenSearchImportData>()),
("aws_postgres", schema::<AwsPostgresImportData>()),
("aws_queue", schema::<AwsQueueImportData>()),
(
"aws_remote_stack_management",
schema::<AwsRemoteStackManagementImportData>(),
),
(
"aws_service_account",
schema::<AwsServiceAccountImportData>(),
),
("aws_storage", schema::<AwsStorageImportData>()),
("aws_vault", schema::<AwsVaultImportData>()),
("azure_ai", schema::<AzureAiImportData>()),
(
"azure_artifact_registry",
schema::<AzureArtifactRegistryImportData>(),
),
("azure_build", schema::<AzureBuildImportData>()),
(
"azure_container_apps_environment",
schema::<AzureContainerAppsEnvironmentImportData>(),
),
(
"azure_compute_cluster",
schema::<AzureComputeClusterImportData>(),
),
("azure_function", schema::<AzureWorkerImportData>()),
("azure_kv", schema::<AzureKvImportData>()),
("azure_key", schema::<AzureKeyImportData>()),
("azure_sandbox", schema::<AzureSandboxImportData>()),
("azure_network", schema::<AzureNetworkImportData>()),
(
"azure_postgres",
schema::<AzureFlexibleServerPostgresImportData>(),
),
("azure_queue", schema::<AzureQueueImportData>()),
(
"azure_remote_stack_management",
schema::<AzureRemoteStackManagementImportData>(),
),
(
"azure_resource_group",
schema::<AzureResourceGroupImportData>(),
),
(
"azure_service_account",
schema::<AzureServiceAccountImportData>(),
),
(
"azure_service_activation",
schema::<AzureServiceActivationImportData>(),
),
(
"azure_service_bus_namespace",
schema::<AzureServiceBusNamespaceImportData>(),
),
("azure_storage", schema::<AzureStorageImportData>()),
(
"azure_storage_account",
schema::<AzureStorageAccountImportData>(),
),
("azure_vault", schema::<AzureVaultImportData>()),
("gcp_ai", schema::<GcpAiImportData>()),
(
"gcp_artifact_registry",
schema::<GcpArtifactRegistryImportData>(),
),
("gcp_build", schema::<GcpBuildImportData>()),
(
"gcp_compute_cluster",
schema::<GcpComputeClusterImportData>(),
),
("gcp_function", schema::<GcpWorkerImportData>()),
("gcp_kv", schema::<GcpKvImportData>()),
("gcp_key", schema::<GcpKeyImportData>()),
("gcp_network", schema::<GcpNetworkImportData>()),
("gcp_postgres", schema::<GcpPostgresImportData>()),
("gcp_queue", schema::<GcpQueueImportData>()),
(
"gcp_remote_stack_management",
schema::<GcpRemoteStackManagementImportData>(),
),
(
"kubernetes_cluster",
schema::<KubernetesClusterImportData>(),
),
(
"gcp_service_account",
schema::<GcpServiceAccountImportData>(),
),
(
"gcp_service_activation",
schema::<GcpServiceActivationImportData>(),
),
("gcp_storage", schema::<GcpStorageImportData>()),
("gcp_vault", schema::<GcpVaultImportData>()),
]);
insta::assert_json_snapshot!("import_data_schemas", schemas);
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn aws_import_data_accepts_cloudformation_stringified_leaves() {
let network: AwsNetworkImportData = serde_json::from_value(json!({
"vpcId": "vpc-123",
"cidrBlock": null,
"internetGatewayId": null,
"natGatewayId": null,
"eipAllocationId": null,
"publicSubnetIds": ["subnet-public"],
"privateSubnetIds": ["subnet-private"],
"publicRouteTableId": null,
"privateRouteTableId": null,
"securityGroupId": "sg-123",
"availabilityZones": [],
"isByoVpc": "true",
}))
.expect("network import data should parse");
assert!(network.is_byo_vpc);
let remote_stack_management: AwsRemoteStackManagementImportData =
serde_json::from_value(json!({
"roleName": "alien-manager",
"roleArn": "arn:aws:iam::123456789012:role/alien-manager",
"managementPermissionsApplied": "true",
}))
.expect("remote stack management import data should parse");
assert!(remote_stack_management.management_permissions_applied);
let service_account: AwsServiceAccountImportData = serde_json::from_value(json!({
"roleName": "alien-worker",
"roleArn": "arn:aws:iam::123456789012:role/alien-worker",
"stackPermissionsApplied": "false",
}))
.expect("service account import data should parse");
assert!(!service_account.stack_permissions_applied);
let sandbox: AwsSandboxImportData = serde_json::from_value(json!({
"imageIdentifier": "runner",
"imageArn": "arn:aws:lambda:us-east-2:123456789012:microvm-image/runner",
"imageVersion": "1",
"egressConnectorArns": [],
"allowEgress": "true",
"previewPorts": ["3000", "8080"],
}))
.expect("sandbox import data should parse CloudFormation's stringified leaves");
assert!(sandbox.allow_egress);
assert_eq!(sandbox.preview_ports, vec![3000, 8080]);
let native: AwsSandboxImportData = serde_json::from_value(json!({
"imageIdentifier": "runner",
"imageArn": "arn:aws:lambda:us-east-2:123456789012:microvm-image/runner",
"imageVersion": "1",
"egressConnectorArns": [],
"allowEgress": false,
"previewPorts": [3000],
}))
.expect("sandbox import data should still parse native JSON types");
assert!(!native.allow_egress);
assert_eq!(native.preview_ports, vec![3000]);
}
#[test]
fn a_runtime_provisioned_sandbox_registers_without_an_image() {
let runtime: AwsSandboxImportData = serde_json::from_value(json!({
"egressConnectorArns": ["arn:aws:lambda:us-east-2:123456789012:network-connector/c-1"],
"allowEgress": "false",
"previewPorts": ["8080"],
"buildRoleArn": "arn:aws:iam::123456789012:role/acme-agents-build",
"bundleUri": "s3://acme-artifacts-us-east-2/agents/bundle.zip",
}))
.expect("a runtime-provisioned sandbox must register without image fields");
assert_eq!(runtime.image_arn, None);
assert_eq!(runtime.image_identifier, None);
assert_eq!(runtime.image_version, None);
assert_eq!(
runtime.build_role_arn.as_deref(),
Some("arn:aws:iam::123456789012:role/acme-agents-build")
);
assert_eq!(
runtime.bundle_uri.as_deref(),
Some("s3://acme-artifacts-us-east-2/agents/bundle.zip")
);
assert_eq!(runtime.preview_ports, vec![8080]);
let frozen: AwsSandboxImportData = serde_json::from_value(json!({
"imageIdentifier": "runner",
"imageArn": "arn:aws:lambda:us-east-2:123456789012:microvm-image/runner",
"imageVersion": "1.0",
}))
.expect("the setup-built shape still parses");
assert_eq!(frozen.build_role_arn, None);
assert_eq!(frozen.bundle_uri, None);
assert_eq!(frozen.image_version.as_deref(), Some("1.0"));
}
}