1use crate::error::ErrorData;
12use alien_error::{AlienError, Context, IntoAlienError};
13use serde::{Deserialize, Serialize};
14use serde_json::Value as JsonValue;
15use std::collections::HashMap;
16
17mod ai;
18mod artifact_registry;
19mod build;
20mod container;
21mod container_apps_environment;
22mod key;
23mod kv;
24mod postgres;
25mod queue;
26mod sandbox;
27mod service_account;
28mod storage;
29mod vault;
30mod worker;
31
32pub use ai::{AiBinding, BedrockAiBinding, ExternalAiBinding, FoundryAiBinding, VertexAiBinding};
33pub use artifact_registry::{
34 AcrArtifactRegistryBinding, ArtifactRegistryBinding, EcrArtifactRegistryBinding,
35 GarArtifactRegistryBinding, LocalArtifactRegistryBinding,
36};
37pub use build::{
38 AcaBuildBinding, BuildBinding, CloudbuildBuildBinding, CodebuildBuildBinding, LocalBuildBinding,
39};
40pub use container::{
41 ContainerBinding, HorizonContainerBinding, KubernetesContainerBinding, LocalContainerBinding,
42};
43pub use container_apps_environment::ContainerAppsEnvironmentBinding;
44pub use key::{AwsKmsKeyBinding, AzureKeyVaultKeyBinding, GcpCloudKmsKeyBinding, KeyBinding};
45pub use kv::{
46 DynamodbKvBinding, FirestoreKvBinding, KvBinding, LocalKvBinding, RedisKvBinding,
47 TableStorageKvBinding,
48};
49pub use postgres::{
50 AuroraPostgresBinding, CloudSqlPostgresBinding, ExternalPostgresBinding,
51 ExternalPostgresSslMode, FlexibleServerPostgresBinding, LocalPostgresBinding, PostgresBinding,
52};
53pub use queue::{
54 LocalQueueBinding, PubSubQueueBinding, QueueBinding, ServiceBusQueueBinding, SqsQueueBinding,
55};
56pub use sandbox::{
57 AwsSandboxBinding, AzureSandboxBinding, GcpAgentPlatformSandboxBinding,
58 KubernetesSandboxBinding, LocalSandboxBinding, SandboxBinding, SandboxEgressBinding,
59 SandboxEgressGateway, SandboxEgressSigningKey,
60};
61pub use service_account::{
62 AwsServiceAccountBinding, AzureServiceAccountBinding, GcpServiceAccountBinding,
63 ServiceAccountBinding,
64};
65pub use storage::{
66 BlobStorageBinding, GcsStorageBinding, LocalStorageBinding, S3StorageBinding, StorageBinding,
67};
68pub use vault::{
69 KeyVaultBinding, KubernetesSecretVaultBinding, LocalVaultBinding, ParameterStoreVaultBinding,
70 SecretManagerVaultBinding, VaultBinding,
71};
72pub use worker::{
73 CloudRunWorkerBinding, ContainerAppWorkerBinding, KubernetesWorkerBinding, LambdaWorkerBinding,
74 LocalWorkerBinding, WorkerBinding,
75};
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
81#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
82#[serde(untagged)]
83pub enum BindingValue<T> {
84 Value(T),
86 #[serde(rename_all = "camelCase")]
88 SecretRef { secret_ref: SecretReference },
89 #[cfg_attr(feature = "jsonschema", schemars(skip))]
91 Expression(JsonValue),
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
97#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
98#[serde(rename_all = "camelCase")]
99pub struct SecretReference {
100 pub name: String,
101 pub key: String,
102}
103
104impl<T> BindingValue<T> {
105 pub fn value(val: T) -> Self {
107 Self::Value(val)
108 }
109
110 pub fn expression(expr: JsonValue) -> Self {
112 Self::Expression(expr)
113 }
114
115 pub fn into_value(self, binding_name: &str, field_name: &str) -> crate::error::Result<T> {
117 match self {
118 BindingValue::Value(val) => Ok(val),
119 BindingValue::Expression(_) => Err(AlienError::new(ErrorData::BindingConfigInvalid {
120 binding_name: binding_name.to_string(),
121 reason: format!("Template expressions not supported in runtime bindings for field '{}'", field_name),
122 })),
123 BindingValue::SecretRef { .. } => Err(AlienError::new(ErrorData::BindingConfigInvalid {
124 binding_name: binding_name.to_string(),
125 reason: format!("SecretRef not resolved for field '{}' - this should have been resolved by the controller", field_name),
126 }))
127 }
128 }
129}
130
131impl<T> From<T> for BindingValue<T> {
132 fn from(val: T) -> Self {
133 Self::Value(val)
134 }
135}
136
137impl From<&str> for BindingValue<String> {
138 fn from(val: &str) -> Self {
139 Self::Value(val.to_string())
140 }
141}
142
143impl From<JsonValue> for BindingValue<String> {
144 fn from(val: JsonValue) -> Self {
145 Self::Expression(val)
146 }
147}
148
149pub fn serialize_binding_as_env_var<T: Serialize>(
151 binding_name: &str,
152 binding: &T,
153) -> crate::error::Result<HashMap<String, String>> {
154 let mut env_vars = HashMap::new();
155 let key = binding_env_var_name(binding_name);
156 let binding_json = serde_json::to_string(binding).into_alien_error().context(
157 ErrorData::BindingConfigInvalid {
158 binding_name: binding_name.to_string(),
159 reason: "Failed to serialize binding to JSON".to_string(),
160 },
161 )?;
162 env_vars.insert(key, binding_json);
163 Ok(env_vars)
164}
165
166pub fn serialize_binding_for_template<T: Serialize>(
168 binding_name: &str,
169 binding: &T,
170) -> crate::error::Result<HashMap<String, JsonValue>> {
171 let mut env_vars = HashMap::new();
172 let key = binding_env_var_name(binding_name);
173 let binding_json = serde_json::to_value(binding).into_alien_error().context(
174 ErrorData::BindingConfigInvalid {
175 binding_name: binding_name.to_string(),
176 reason: "Failed to serialize binding to JSON for template".to_string(),
177 },
178 )?;
179
180 env_vars.insert(
182 key,
183 JsonValue::Object({
184 let mut map = serde_json::Map::new();
185 map.insert("Fn::ToJsonString".to_string(), binding_json);
186 map
187 }),
188 );
189
190 Ok(env_vars)
191}
192
193pub fn binding_env_var_name(binding_name: &str) -> String {
195 format!(
196 "ALIEN_{}_BINDING",
197 binding_name.replace('-', "_").to_uppercase()
198 )
199}
200
201pub fn parse_binding_from_env<T: for<'de> Deserialize<'de>>(
203 env: &HashMap<String, String>,
204 binding_name: &str,
205) -> crate::error::Result<T> {
206 let key = binding_env_var_name(binding_name);
207 let json_str = env.get(&key).ok_or_else(|| {
208 AlienError::new(ErrorData::BindingEnvVarMissing {
209 binding_name: binding_name.to_string(),
210 env_var: key.clone(),
211 })
212 })?;
213
214 serde_json::from_str(json_str)
215 .into_alien_error()
216 .context(ErrorData::BindingJsonParseFailed {
217 binding_name: binding_name.to_string(),
218 reason: "Invalid JSON format".to_string(),
219 })
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::bindings::{ArtifactRegistryBinding, BuildBinding, StorageBinding};
226 use serde_json::json;
227 use std::collections::HashMap;
228
229 #[test]
230 fn test_serialize_storage_binding_as_env_var() {
231 let binding = StorageBinding::s3("my-bucket");
232
233 let env_vars = serialize_binding_as_env_var("TEST", &binding).unwrap();
234
235 assert_eq!(env_vars.len(), 1);
236 let json_str = env_vars.get("ALIEN_TEST_BINDING").unwrap();
237 let parsed: StorageBinding = serde_json::from_str(json_str).unwrap();
238 assert_eq!(binding, parsed);
239 }
240
241 #[test]
242 fn test_serialize_binding_for_template() {
243 let binding = StorageBinding::S3(S3StorageBinding::bucket(BindingValue::expression(
244 json!({"Ref": "MyBucket"}),
245 )));
246
247 let env_vars = serialize_binding_for_template("TEST", &binding).unwrap();
248
249 assert_eq!(env_vars.len(), 1);
250 let fn_to_json_string = env_vars.get("ALIEN_TEST_BINDING").unwrap();
251
252 assert!(fn_to_json_string.get("Fn::ToJsonString").is_some());
254 }
255
256 #[test]
257 fn test_artifact_registry_binding_roundtrip() {
258 let binding = ArtifactRegistryBinding::ecr(
259 "my-project",
260 Some("arn:aws:iam::123456789012:role/PullRole".to_string()),
261 None::<String>,
262 );
263
264 let env_vars = serialize_binding_as_env_var("TEST", &binding).unwrap();
265
266 let reconstructed: ArtifactRegistryBinding =
267 parse_binding_from_env(&env_vars, "TEST").unwrap();
268 assert_eq!(binding, reconstructed);
269 }
270
271 #[test]
272 fn test_service_type_serialization() {
273 let s3_binding = StorageBinding::s3("my-bucket");
275 let s3_json = serde_json::to_string(&s3_binding).unwrap();
276 assert!(s3_json.contains(r#""service":"s3""#));
277
278 let ecr_binding = ArtifactRegistryBinding::ecr("my-repo", None::<String>, None::<String>);
280 let ecr_json = serde_json::to_string(&ecr_binding).unwrap();
281 assert!(ecr_json.contains(r#""service":"ecr""#));
282
283 let build_binding = BuildBinding::codebuild("my-project", HashMap::new(), None);
285 let build_json = serde_json::to_string(&build_binding).unwrap();
286 assert!(build_json.contains(r#""service":"codebuild""#));
287 }
288
289 #[test]
290 fn test_cross_provider_bindings() {
291 let storage_binding = StorageBinding::s3("prod-bucket");
293 let registry_binding = ArtifactRegistryBinding::acr("myregistry", "mygroup");
294 let build_binding = BuildBinding::cloudbuild(
295 HashMap::new(),
296 "build@project.iam.gserviceaccount.com",
297 None,
298 );
299
300 let storage_env = serialize_binding_as_env_var("STORAGE", &storage_binding).unwrap();
302 let registry_env = serialize_binding_as_env_var("REGISTRY", ®istry_binding).unwrap();
303 let build_env = serialize_binding_as_env_var("BUILD", &build_binding).unwrap();
304
305 assert!(storage_env.contains_key("ALIEN_STORAGE_BINDING"));
307 assert!(registry_env.contains_key("ALIEN_REGISTRY_BINDING"));
308 assert!(build_env.contains_key("ALIEN_BUILD_BINDING"));
309 }
310
311 #[test]
312 fn test_binding_value_secret_ref() {
313 let secret_ref: BindingValue<String> = BindingValue::SecretRef {
315 secret_ref: SecretReference {
316 name: "my-secret".to_string(),
317 key: "password".to_string(),
318 },
319 };
320
321 let json = serde_json::to_string(&secret_ref).unwrap();
322 assert!(json.contains(r#""secretRef""#));
323 assert!(json.contains(r#""name":"my-secret""#));
324 assert!(json.contains(r#""key":"password""#));
325
326 let parsed: BindingValue<String> = serde_json::from_str(&json).unwrap();
328 assert_eq!(secret_ref, parsed);
329 }
330
331 #[test]
332 fn test_binding_value_into_value_secret_ref() {
333 let secret_ref: BindingValue<String> = BindingValue::SecretRef {
334 secret_ref: SecretReference {
335 name: "my-secret".to_string(),
336 key: "password".to_string(),
337 },
338 };
339
340 let result = secret_ref.into_value("test", "password");
341 assert!(result.is_err());
342 assert!(result
343 .unwrap_err()
344 .to_string()
345 .contains("SecretRef not resolved"));
346 }
347
348 #[test]
349 fn test_binding_value_variants() {
350 let value: BindingValue<String> = BindingValue::value("test".to_string());
352 assert_eq!(value.into_value("test", "field").unwrap(), "test");
353
354 let expr: BindingValue<String> = BindingValue::expression(json!({"Ref": "Test"}));
356 assert!(expr.into_value("test", "field").is_err());
357 }
358}