Skip to main content

alien_core/resources/
remote_stack_management.rs

1use crate::resource::{ResourceDefinition, ResourceOutputsDefinition, ResourceRef, ResourceType};
2use alien_error::AlienError;
3use bon::Builder;
4use serde::{Deserialize, Serialize};
5use std::any::Any;
6use std::fmt::Debug;
7
8/// Represents cross-account management access configuration for a stack deployed
9/// on AWS, GCP, or Azure platforms. This resource sets up the necessary IAM/RBAC
10/// configuration to allow another cloud account to manage the stack.
11///
12/// Maps to:
13/// - AWS: Cross-account IAM role with management permissions
14/// - GCP: Service account with management permissions and impersonation rights
15/// - Azure: User-assigned managed identity with federated credential and custom RBAC
16///
17/// This resource is automatically created for AWS, GCP, and Azure platforms
18/// when the stack needs to be managed by another account. The management account
19/// and identity information comes from the platform configuration.
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
21#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
22#[serde(rename_all = "camelCase", deny_unknown_fields)]
23#[builder(start_fn = new)]
24pub struct RemoteStackManagement {
25    /// Identifier for the remote stack management. Must contain only alphanumeric characters, hyphens, and underscores ([A-Za-z0-9-_]).
26    /// Maximum 64 characters.
27    #[builder(start_fn)]
28    pub id: String,
29}
30
31impl RemoteStackManagement {
32    /// The resource type identifier for RemoteStackManagement
33    pub const RESOURCE_TYPE: ResourceType = ResourceType::from_static("remote-stack-management");
34
35    /// Returns the remote stack management's unique identifier.
36    pub fn id(&self) -> &str {
37        &self.id
38    }
39}
40
41/// Resource outputs for RemoteStackManagement.
42/// Different platforms will provide different outputs based on their implementation.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
45#[serde(rename_all = "camelCase", deny_unknown_fields)]
46pub struct RemoteStackManagementOutputs {
47    /// Platform-specific management resource identifier
48    /// For AWS: The ARN of the created cross-account role
49    /// For GCP: The email of the created service account
50    /// For Azure: The resource ID of the target user-assigned managed identity
51    pub management_resource_id: String,
52
53    /// Platform-specific access configuration
54    /// For AWS: The role ARN to assume
55    /// For GCP: The service account email to impersonate
56    /// For Azure: JSON containing the target managed identity client ID and tenant ID
57    pub access_configuration: String,
58
59    /// Setup-owned identity whose data-plane permissions are the union of the
60    /// permissions explicitly enabled for remote bindings in this stack.
61    ///
62    /// This identity is deliberately separate from the management identity:
63    /// its short-lived credentials may be returned to an external application,
64    /// while management credentials must never leave the manager.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub remote_bindings_access: Option<RemoteBindingsAccessOutputs>,
67}
68
69/// Provider-neutral handoff for the stack's Remote Bindings identity.
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
72#[serde(rename_all = "camelCase", deny_unknown_fields)]
73pub struct RemoteBindingsAccessOutputs {
74    /// Provider resource identifier (role ARN, service-account email, or UAMI
75    /// resource id) of the setup-owned Remote Bindings identity.
76    pub resource_id: String,
77
78    /// Provider-specific impersonation configuration consumed by the manager.
79    pub access_configuration: String,
80}
81
82// Implementation of ResourceDefinition trait for RemoteStackManagement
83impl ResourceDefinition for RemoteStackManagement {
84    fn get_resource_type(&self) -> ResourceType {
85        Self::RESOURCE_TYPE
86    }
87
88    fn id(&self) -> &str {
89        &self.id
90    }
91
92    fn get_dependencies(&self) -> Vec<ResourceRef> {
93        // RemoteStackManagement typically doesn't depend on other resources,
94        // but may depend on infrastructure requirements like resource groups
95        Vec::new()
96    }
97
98    fn validate_update(&self, new_config: &dyn ResourceDefinition) -> crate::error::Result<()> {
99        // Try to downcast to RemoteStackManagement for type-specific validation
100        if let Some(new_remote_mgmt) = new_config.as_any().downcast_ref::<RemoteStackManagement>() {
101            // Validate that the ID matches
102            if self.id != new_remote_mgmt.id {
103                return Err(AlienError::new(
104                    crate::error::ErrorData::InvalidResourceUpdate {
105                        resource_id: self.id.clone(),
106                        reason: "the 'id' field is immutable".to_string(),
107                    },
108                ));
109            }
110
111            // RemoteStackManagement configuration can be updated
112            Ok(())
113        } else {
114            Err(AlienError::new(
115                crate::error::ErrorData::UnexpectedResourceType {
116                    resource_id: self.id.clone(),
117                    expected: Self::RESOURCE_TYPE,
118                    actual: new_config.get_resource_type(),
119                },
120            ))
121        }
122    }
123
124    fn as_any(&self) -> &dyn Any {
125        self
126    }
127
128    fn as_any_mut(&mut self) -> &mut dyn Any {
129        self
130    }
131
132    fn box_clone(&self) -> Box<dyn ResourceDefinition> {
133        Box::new(self.clone())
134    }
135
136    fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool {
137        other
138            .as_any()
139            .downcast_ref::<RemoteStackManagement>()
140            .map(|other_remote_mgmt| self == other_remote_mgmt)
141            .unwrap_or(false)
142    }
143
144    fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
145        serde_json::to_value(self)
146    }
147}
148
149impl ResourceOutputsDefinition for RemoteStackManagementOutputs {
150    fn get_resource_type(&self) -> ResourceType {
151        RemoteStackManagement::RESOURCE_TYPE.clone()
152    }
153
154    fn as_any(&self) -> &dyn Any {
155        self
156    }
157
158    fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition> {
159        Box::new(self.clone())
160    }
161
162    fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool {
163        other
164            .as_any()
165            .downcast_ref::<RemoteStackManagementOutputs>()
166            == Some(self)
167    }
168
169    fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
170        serde_json::to_value(self)
171    }
172}