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, sandbox_needs_connector};
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 if !sandbox_needs_connector(stack, sandbox) {
39 return Ok(None);
40 }
41 let network = match sandbox_egress_network(stack) {
42 Ok(network) => network,
43 Err(refusal) => return Err(refusal.to_string()),
44 };
45 if network.entry.lifecycle != ResourceLifecycle::Frozen {
46 return Err(
47 "an AWS sandbox routes session traffic through a VPC egress connector; its network \
48 must be created by setup, and this one is created at runtime"
49 .to_string(),
50 );
51 }
52 Ok(Some(network.id))
53}
54
55pub fn aws_sandbox_build_role<'a>(
57 sandbox: &'a Sandbox,
58 bundle_uri: &'a str,
59 lifecycle: ResourceLifecycle,
60 account: SetupAccount<'a>,
61) -> SandboxBuildRole<'a> {
62 SandboxBuildRole::builder()
63 .sandbox_id(&sandbox.id)
64 .partition(account.partition)
65 .account_id(account.account_id)
66 .region(account.region)
67 .bundle_uri(bundle_uri)
68 .runtime_built(lifecycle == ResourceLifecycle::Live)
69 .maybe_private_base_image(sandbox.private_base_image.as_deref())
70 .build()
71}
72
73pub fn aws_sandbox_remote_grant(
75 stack: &Stack,
76 sandbox: &Sandbox,
77) -> Option<&'static RemoteBindingDefinition> {
78 stack
79 .resources
80 .get(&sandbox.id)
81 .filter(|entry| remote_binding_is_deliverable(entry))
82 .and_then(remote_binding_for_entry)
83}
84
85pub fn aws_sandbox_setup_inputs(
88 stack: &Stack,
89 sandbox: &Sandbox,
90 lifecycle: ResourceLifecycle,
91 account: SetupAccount<'_>,
92) -> Result<Vec<(&'static str, Value)>> {
93 let refuse = |reason: String| {
94 AlienError::new(ErrorData::OperationNotSupported {
95 operation: format!("setup inputs for sandbox '{}'", sandbox.id),
96 reason,
97 })
98 };
99 let SandboxCode::Image { image } = &sandbox.code else {
100 return Err(refuse(
101 "an AWS sandbox is built from a prebuilt s3:// bundle, not from source".to_string(),
102 ));
103 };
104 let policy = aws_sandbox_build_role(sandbox, image, lifecycle, account).policy()?;
105 let network = aws_sandbox_egress_network_id(stack, sandbox).map_err(refuse)?;
106 let grant =
109 aws_sandbox_remote_grant(stack, sandbox).map(|definition| definition.permission_set);
110 Ok(vec![
111 ("egress", sandbox.setup_digest_value()),
112 ("egress network", serde_json::json!(network)),
113 (
114 "build role policy",
115 serde_json::to_value(policy).expect("a build role policy serializes to JSON"),
116 ),
117 ("remote grant", serde_json::json!(grant)),
118 ])
119}
120
121pub fn comparable_aws_sandbox_setup_inputs(
124 stack: &Stack,
125 sandbox: &Sandbox,
126 lifecycle: ResourceLifecycle,
127) -> Vec<(&'static str, Value)> {
128 aws_sandbox_setup_inputs(stack, sandbox, lifecycle, SETUP_INPUTS_COMPARISON_ACCOUNT)
129 .unwrap_or_else(|_| {
130 vec![("configuration", {
131 let mut value =
132 serde_json::to_value(sandbox).expect("a sandbox serializes to JSON");
133 value["egress"] = sandbox.setup_digest_value();
134 value
135 .as_object_mut()
136 .expect("sandbox config is an object")
137 .remove("defaultPolicy");
138 value
139 })]
140 })
141}