alien_core/
sandbox_setup_inputs.rs1use alien_error::AlienError;
5use serde_json::Value;
6
7use crate::remote_bindings::{
8 remote_binding_for_entry, remote_binding_is_deliverable, RemoteBindingDefinition,
9};
10use crate::sandbox_build_role::SandboxBuildRole;
11use crate::sandbox_egress::sandbox_egress_network;
12use crate::{
13 ErrorData, ResourceLifecycle, Result, Sandbox, SandboxCode, Stack, BUNDLE_REGION_TOKEN,
14};
15
16#[derive(Debug, Clone, Copy)]
18pub struct SetupAccount<'a> {
19 pub partition: &'a str,
20 pub account_id: &'a str,
21 pub region: &'a str,
22}
23
24pub const SETUP_INPUTS_COMPARISON_ACCOUNT: SetupAccount<'static> = SetupAccount {
27 partition: "aws",
28 account_id: "000000000000",
29 region: BUNDLE_REGION_TOKEN,
30};
31
32pub fn aws_sandbox_egress_network_id<'a>(
35 stack: &'a Stack,
36 sandbox: &Sandbox,
37) -> std::result::Result<Option<&'a str>, String> {
38 let network = match sandbox_egress_network(stack, &sandbox.egress) {
39 Ok(Some(network)) => network,
40 Ok(None) => return Ok(None),
41 Err(refusal) => return Err(refusal.to_string()),
42 };
43 if network.entry.lifecycle != ResourceLifecycle::Frozen {
44 return Err(
45 "an AWS sandbox routes session traffic through a VPC egress connector; its network \
46 must be created by setup, and this one is created at runtime"
47 .to_string(),
48 );
49 }
50 Ok(Some(network.id))
51}
52
53pub fn aws_sandbox_build_role<'a>(
55 sandbox: &'a Sandbox,
56 bundle_uri: &'a str,
57 lifecycle: ResourceLifecycle,
58 account: SetupAccount<'a>,
59) -> SandboxBuildRole<'a> {
60 SandboxBuildRole::builder()
61 .sandbox_id(&sandbox.id)
62 .partition(account.partition)
63 .account_id(account.account_id)
64 .region(account.region)
65 .bundle_uri(bundle_uri)
66 .runtime_built(lifecycle == ResourceLifecycle::Live)
67 .maybe_private_base_image(sandbox.private_base_image.as_deref())
68 .build()
69}
70
71pub fn aws_sandbox_remote_grant(
73 stack: &Stack,
74 sandbox: &Sandbox,
75) -> Option<&'static RemoteBindingDefinition> {
76 stack
77 .resources
78 .get(&sandbox.id)
79 .filter(|entry| remote_binding_is_deliverable(entry))
80 .and_then(remote_binding_for_entry)
81}
82
83pub fn aws_sandbox_setup_inputs(
86 stack: &Stack,
87 sandbox: &Sandbox,
88 lifecycle: ResourceLifecycle,
89 account: SetupAccount<'_>,
90) -> Result<Vec<(&'static str, Value)>> {
91 let refuse = |reason: String| {
92 AlienError::new(ErrorData::OperationNotSupported {
93 operation: format!("setup inputs for sandbox '{}'", sandbox.id),
94 reason,
95 })
96 };
97 let SandboxCode::Image { image } = &sandbox.code else {
98 return Err(refuse(
99 "an AWS sandbox is built from a prebuilt s3:// bundle, not from source".to_string(),
100 ));
101 };
102 let policy = aws_sandbox_build_role(sandbox, image, lifecycle, account).policy()?;
103 let network = aws_sandbox_egress_network_id(stack, sandbox).map_err(refuse)?;
104 let grant =
107 aws_sandbox_remote_grant(stack, sandbox).map(|definition| definition.permission_set);
108 Ok(vec![
109 (
110 "egress",
111 serde_json::to_value(&sandbox.egress).expect("sandbox egress serializes to JSON"),
112 ),
113 ("egress network", serde_json::json!(network)),
114 (
115 "build role policy",
116 serde_json::to_value(policy).expect("a build role policy serializes to JSON"),
117 ),
118 ("remote grant", serde_json::json!(grant)),
119 ])
120}
121
122pub fn comparable_aws_sandbox_setup_inputs(
125 stack: &Stack,
126 sandbox: &Sandbox,
127 lifecycle: ResourceLifecycle,
128) -> Vec<(&'static str, Value)> {
129 aws_sandbox_setup_inputs(stack, sandbox, lifecycle, SETUP_INPUTS_COMPARISON_ACCOUNT)
130 .unwrap_or_else(|_| {
131 vec![(
132 "configuration",
133 serde_json::to_value(sandbox).expect("a sandbox serializes to JSON"),
134 )]
135 })
136}