alien_core/resources/
key.rs1use crate::{
2 error::{ErrorData, Result},
3 resource::{ResourceDefinition, ResourceOutputsDefinition, ResourceRef, ResourceType},
4};
5use alien_error::AlienError;
6use bon::Builder;
7use serde::{Deserialize, Serialize};
8use std::any::Any;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
12#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
13#[serde(rename_all = "camelCase", deny_unknown_fields)]
14#[builder(start_fn = new)]
15pub struct Key {
16 #[builder(start_fn)]
18 pub id: String,
19}
20
21impl Key {
22 pub const RESOURCE_TYPE: ResourceType = ResourceType::from_static("key");
23
24 pub fn id(&self) -> &str {
25 &self.id
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
35#[serde(tag = "provider", rename_all = "lowercase", deny_unknown_fields)]
36pub enum KeyFingerprint {
37 Aws { key_arn: String },
39 Gcp { crypto_key_name: String },
41 Azure {
43 vault_resource_id: String,
44 key_name: String,
45 lineage_version_id: String,
46 },
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
52#[serde(rename_all = "camelCase")]
53pub struct KeyOutputs {
54 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub available: Option<bool>,
58 pub fingerprint: KeyFingerprint,
60 pub wrapping_key_id: String,
62}
63
64impl ResourceDefinition for Key {
65 fn get_resource_type(&self) -> ResourceType {
66 Self::RESOURCE_TYPE
67 }
68
69 fn id(&self) -> &str {
70 &self.id
71 }
72
73 fn replace_after_failed_create_is_safe(&self) -> bool {
74 false
76 }
77
78 fn get_dependencies(&self) -> Vec<ResourceRef> {
79 Vec::new()
80 }
81
82 fn validate_update(&self, new_config: &dyn ResourceDefinition) -> Result<()> {
83 let Some(new_key) = new_config.as_any().downcast_ref::<Self>() else {
84 return Err(AlienError::new(ErrorData::UnexpectedResourceType {
85 resource_id: self.id.clone(),
86 expected: Self::RESOURCE_TYPE,
87 actual: new_config.get_resource_type(),
88 }));
89 };
90
91 if self.id != new_key.id {
92 return Err(AlienError::new(ErrorData::InvalidResourceUpdate {
93 resource_id: self.id.clone(),
94 reason: "the 'id' field is immutable".to_string(),
95 }));
96 }
97
98 Ok(())
99 }
100
101 fn as_any(&self) -> &dyn Any {
102 self
103 }
104
105 fn as_any_mut(&mut self) -> &mut dyn Any {
106 self
107 }
108
109 fn box_clone(&self) -> Box<dyn ResourceDefinition> {
110 Box::new(self.clone())
111 }
112
113 fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool {
114 other.as_any().downcast_ref::<Self>() == Some(self)
115 }
116
117 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
118 serde_json::to_value(self)
119 }
120}
121
122impl ResourceOutputsDefinition for KeyOutputs {
123 fn is_available(&self) -> bool {
124 self.available.unwrap_or(true)
125 }
126
127 fn get_resource_type(&self) -> ResourceType {
128 Key::RESOURCE_TYPE
129 }
130
131 fn as_any(&self) -> &dyn Any {
132 self
133 }
134
135 fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition> {
136 Box::new(self.clone())
137 }
138
139 fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool {
140 other.as_any().downcast_ref::<Self>() == Some(self)
141 }
142
143 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
144 serde_json::to_value(self)
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn provider_rotation_preserves_the_key_family_fingerprint() {
154 let fingerprint = KeyFingerprint::Gcp {
155 crypto_key_name: "projects/example/locations/us/keyRings/data/cryptoKeys/customer"
156 .to_string(),
157 };
158 let before = KeyOutputs {
159 available: None,
160 fingerprint: fingerprint.clone(),
161 wrapping_key_id: "projects/example/locations/us/keyRings/data/cryptoKeys/customer/cryptoKeyVersions/1".to_string(),
162 };
163 let after = KeyOutputs {
164 available: None,
165 fingerprint,
166 wrapping_key_id: "projects/example/locations/us/keyRings/data/cryptoKeys/customer/cryptoKeyVersions/2".to_string(),
167 };
168
169 assert_eq!(before.fingerprint, after.fingerprint);
170 assert_ne!(before.wrapping_key_id, after.wrapping_key_id);
171 }
172}