Skip to main content

alien_core/bindings/
artifact_registry.rs

1//! Service-type based artifact registry binding definitions
2
3use super::BindingValue;
4use serde::{Deserialize, Serialize};
5
6/// The OCI `DENIED` message a manager answers a push to a repository under a legacy prefix with.
7/// A client that resolved its repository before the registry was renamed matches it exactly to
8/// tell this refusal apart from any other and resolve the current repository again.
9pub const LEGACY_REPOSITORY_PUSH_REFUSAL: &str =
10    "Repository is under a legacy prefix, which is read-only; push under the current prefix.";
11
12/// AWS ECR (Elastic Container Registry) binding configuration
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
15#[serde(rename_all = "camelCase")]
16pub struct EcrArtifactRegistryBinding {
17    /// Repository prefix for this registry (used to construct ECR repository names)
18    pub repository_prefix: BindingValue<String>,
19    /// Optional namespace for sandbox images served by regional ECR pull-through caches.
20    /// Configure cache rules before enabling this; workers keep using `repositoryPrefix`.
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub sandbox_repository_prefix: Option<BindingValue<String>>,
23    /// Repository prefixes this registry used before `repositoryPrefix`. Images already released
24    /// under them stay readable and keep their cross-account grants; nothing new is written there.
25    #[serde(default, skip_serializing_if = "Vec::is_empty")]
26    pub legacy_repository_prefixes: Vec<String>,
27    /// Sandbox namespaces this registry used before `sandboxRepositoryPrefix`, read the same way.
28    #[serde(default, skip_serializing_if = "Vec::is_empty")]
29    pub legacy_sandbox_repository_prefixes: Vec<String>,
30    /// ARN of the IAM role for pull permissions (optional — omit for single-account)
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub pull_role_arn: Option<BindingValue<String>>,
33    /// ARN of the IAM role for push+pull permissions (optional — omit for single-account)
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub push_role_arn: Option<BindingValue<String>>,
36}
37
38/// Azure Container Registry binding configuration
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
41#[serde(rename_all = "camelCase")]
42pub struct AcrArtifactRegistryBinding {
43    /// Registry name (e.g., "myregistry") - endpoint is derived from this
44    pub registry_name: BindingValue<String>,
45    /// Resource group name where the registry is located
46    pub resource_group_name: BindingValue<String>,
47    /// Repository prefix for this registry (used for proxy routing)
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub repository_prefix: Option<BindingValue<String>>,
50}
51
52/// Google Artifact Registry binding configuration
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
55#[serde(rename_all = "camelCase")]
56pub struct GarArtifactRegistryBinding {
57    /// Repository name in the Artifact Registry (e.g., "alien-test").
58    /// Used as the default when cross-account access methods are called
59    /// without a specific repo name.
60    pub repository_name: BindingValue<String>,
61    /// Optional service account email for pull permissions (omit for single-project)
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub pull_service_account_email: Option<BindingValue<String>>,
64    /// Optional service account email for push+pull permissions (omit for single-project)
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub push_service_account_email: Option<BindingValue<String>>,
67}
68
69/// Local container registry binding configuration.
70///
71/// The local registry runs on localhost only and does not require authentication.
72/// Security boundary is the OS process isolation on the customer's machine.
73/// External image access is secured by the manager's registry proxy (deployment tokens).
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
76#[serde(rename_all = "camelCase")]
77pub struct LocalArtifactRegistryBinding {
78    /// The registry URL endpoint (e.g., "localhost:5000")
79    pub registry_url: BindingValue<String>,
80    /// Optional base directory for registry data
81    pub data_dir: BindingValue<Option<String>>,
82}
83
84/// Service-type based artifact registry binding that supports multiple registry providers
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
86#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
87#[serde(tag = "service", rename_all = "lowercase")]
88pub enum ArtifactRegistryBinding {
89    /// AWS ECR (Elastic Container Registry)
90    Ecr(EcrArtifactRegistryBinding),
91    /// Azure Container Registry
92    Acr(AcrArtifactRegistryBinding),
93    /// Google Artifact Registry
94    Gar(GarArtifactRegistryBinding),
95    /// Local container registry
96    Local(LocalArtifactRegistryBinding),
97}
98
99impl ArtifactRegistryBinding {
100    /// Creates an ECR artifact registry binding
101    pub fn ecr(
102        repository_prefix: impl Into<BindingValue<String>>,
103        pull_role_arn: Option<impl Into<BindingValue<String>>>,
104        push_role_arn: Option<impl Into<BindingValue<String>>>,
105    ) -> Self {
106        Self::Ecr(EcrArtifactRegistryBinding {
107            sandbox_repository_prefix: None,
108            legacy_repository_prefixes: Vec::new(),
109            legacy_sandbox_repository_prefixes: Vec::new(),
110            repository_prefix: repository_prefix.into(),
111            pull_role_arn: pull_role_arn.map(|v| v.into()),
112            push_role_arn: push_role_arn.map(|v| v.into()),
113        })
114    }
115
116    /// Creates an ACR artifact registry binding
117    pub fn acr(
118        registry_name: impl Into<BindingValue<String>>,
119        resource_group_name: impl Into<BindingValue<String>>,
120    ) -> Self {
121        Self::Acr(AcrArtifactRegistryBinding {
122            registry_name: registry_name.into(),
123            resource_group_name: resource_group_name.into(),
124            repository_prefix: None,
125        })
126    }
127
128    /// Creates a GAR artifact registry binding
129    pub fn gar(
130        repository_name: impl Into<BindingValue<String>>,
131        pull_service_account_email: Option<impl Into<BindingValue<String>>>,
132        push_service_account_email: Option<impl Into<BindingValue<String>>>,
133    ) -> Self {
134        Self::Gar(GarArtifactRegistryBinding {
135            repository_name: repository_name.into(),
136            pull_service_account_email: pull_service_account_email.map(|v| v.into()),
137            push_service_account_email: push_service_account_email.map(|v| v.into()),
138        })
139    }
140
141    /// Creates a local artifact registry binding
142    pub fn local(
143        registry_url: impl Into<BindingValue<String>>,
144        data_dir: impl Into<BindingValue<Option<String>>>,
145    ) -> Self {
146        Self::Local(LocalArtifactRegistryBinding {
147            registry_url: registry_url.into(),
148            data_dir: data_dir.into(),
149        })
150    }
151}