alien_core/resources/
artifact_registry.rs1use crate::error::{ErrorData, Result};
2use crate::resource::{ResourceDefinition, ResourceOutputsDefinition, ResourceRef, ResourceType};
3use alien_error::AlienError;
4use bon::Builder;
5use serde::{Deserialize, Serialize};
6use std::any::Any;
7use std::fmt::Debug;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
20#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
21#[serde(rename_all = "camelCase", deny_unknown_fields)]
22#[builder(start_fn = new)]
23pub struct ArtifactRegistry {
24 #[builder(start_fn)]
27 pub id: String,
28
29 #[serde(default, skip_serializing_if = "Vec::is_empty")]
34 #[builder(default)]
35 pub replication_regions: Vec<String>,
36}
37
38impl ArtifactRegistry {
39 pub const RESOURCE_TYPE: ResourceType = ResourceType::from_static("artifact-registry");
41
42 pub fn validate_aws_replication(&self) -> Result<()> {
45 if !self.replication_regions.is_empty() {
46 tracing::warn!(resource_id = %self.id, "Per-deployment ECR replicationRegions is ignored; configure replication through the registry account's infrastructure. Existing account replication rules remain unchanged.");
47 }
48 Ok(())
49 }
50
51 pub fn id(&self) -> &str {
53 &self.id
54 }
55}
56
57impl ResourceDefinition for ArtifactRegistry {
59 fn get_resource_type(&self) -> ResourceType {
60 Self::RESOURCE_TYPE
61 }
62
63 fn id(&self) -> &str {
64 &self.id
65 }
66
67 fn replace_after_failed_create_is_safe(&self) -> bool {
68 false
71 }
72
73 fn get_dependencies(&self) -> Vec<ResourceRef> {
74 Vec::new()
75 }
76
77 fn validate_update(&self, new_config: &dyn ResourceDefinition) -> Result<()> {
78 let new_registry = new_config
80 .as_any()
81 .downcast_ref::<ArtifactRegistry>()
82 .ok_or_else(|| {
83 AlienError::new(ErrorData::UnexpectedResourceType {
84 resource_id: self.id.clone(),
85 expected: Self::RESOURCE_TYPE,
86 actual: new_config.get_resource_type(),
87 })
88 })?;
89
90 if self.id != new_registry.id {
91 return Err(AlienError::new(ErrorData::InvalidResourceUpdate {
92 resource_id: self.id.clone(),
93 reason: "the 'id' field is immutable".to_string(),
94 }));
95 }
96 Ok(())
97 }
98
99 fn as_any(&self) -> &dyn Any {
100 self
101 }
102
103 fn as_any_mut(&mut self) -> &mut dyn Any {
104 self
105 }
106
107 fn box_clone(&self) -> Box<dyn ResourceDefinition> {
108 Box::new(self.clone())
109 }
110
111 fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool {
112 other.as_any().downcast_ref::<ArtifactRegistry>() == Some(self)
113 }
114
115 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
116 serde_json::to_value(self)
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
123#[serde(rename_all = "camelCase")]
124pub struct ArtifactRegistryOutputs {
125 pub registry_id: String,
130
131 pub registry_endpoint: String,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
142 pub pull_role: Option<String>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
149 pub push_role: Option<String>,
150}
151
152impl ResourceOutputsDefinition for ArtifactRegistryOutputs {
153 fn get_resource_type(&self) -> ResourceType {
154 ArtifactRegistry::RESOURCE_TYPE.clone()
155 }
156
157 fn as_any(&self) -> &dyn Any {
158 self
159 }
160
161 fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition> {
162 Box::new(self.clone())
163 }
164
165 fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool {
166 other.as_any().downcast_ref::<ArtifactRegistryOutputs>() == Some(self)
167 }
168
169 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
170 serde_json::to_value(self)
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn test_artifact_registry_creation() {
180 let registry = ArtifactRegistry::new("my-registry".to_string()).build();
181 assert_eq!(registry.id, "my-registry");
182 }
183
184 #[test]
185 fn aws_registry_does_not_promise_per_deployment_replication() {
186 let mut registry = ArtifactRegistry::new("images".to_string()).build();
187 assert!(registry.validate_aws_replication().is_ok());
188 registry.replication_regions = vec!["us-west-2".to_string()];
189 assert!(registry.validate_aws_replication().is_ok());
190 }
191
192 #[test]
193 fn test_artifact_registry_dependencies() {
194 let registry = ArtifactRegistry::new("my-registry".to_string()).build();
195 let dependencies = registry.get_dependencies();
196 assert!(dependencies.is_empty());
197 }
198}