use std::sync::Arc;
use async_trait::async_trait;
use parking_lot::RwLock;
use tokio::sync::Mutex as AsyncMutex;
use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
use fakecloud_persistence::SnapshotStore;
use crate::state::{
RedshiftAccounts, RedshiftSnapshot, SharedRedshiftState, REDSHIFT_SNAPSHOT_SCHEMA_VERSION,
};
mod access;
mod clusters;
mod groups;
pub(crate) mod helpers;
mod resources;
mod snapshots;
mod subscriptions;
#[cfg(test)]
mod tests;
const SUPPORTED_ACTIONS: &[&str] = &[
"AcceptReservedNodeExchange",
"AddPartner",
"AssociateDataShareConsumer",
"AuthorizeClusterSecurityGroupIngress",
"AuthorizeDataShare",
"AuthorizeEndpointAccess",
"AuthorizeSnapshotAccess",
"BatchDeleteClusterSnapshots",
"BatchModifyClusterSnapshots",
"CancelResize",
"CopyClusterSnapshot",
"CreateAuthenticationProfile",
"CreateCluster",
"CreateClusterParameterGroup",
"CreateClusterSecurityGroup",
"CreateClusterSnapshot",
"CreateClusterSubnetGroup",
"CreateCustomDomainAssociation",
"CreateEndpointAccess",
"CreateEventSubscription",
"CreateHsmClientCertificate",
"CreateHsmConfiguration",
"CreateIntegration",
"CreateRedshiftIdcApplication",
"CreateScheduledAction",
"CreateSnapshotCopyGrant",
"CreateSnapshotSchedule",
"CreateTags",
"CreateUsageLimit",
"DeauthorizeDataShare",
"DeleteAuthenticationProfile",
"DeleteCluster",
"DeleteClusterParameterGroup",
"DeleteClusterSecurityGroup",
"DeleteClusterSnapshot",
"DeleteClusterSubnetGroup",
"DeleteCustomDomainAssociation",
"DeleteEndpointAccess",
"DeleteEventSubscription",
"DeleteHsmClientCertificate",
"DeleteHsmConfiguration",
"DeleteIntegration",
"DeletePartner",
"DeleteRedshiftIdcApplication",
"DeleteResourcePolicy",
"DeleteScheduledAction",
"DeleteSnapshotCopyGrant",
"DeleteSnapshotSchedule",
"DeleteTags",
"DeleteUsageLimit",
"DeregisterNamespace",
"DescribeAccountAttributes",
"DescribeAuthenticationProfiles",
"DescribeClusterDbRevisions",
"DescribeClusterParameterGroups",
"DescribeClusterParameters",
"DescribeClusterSecurityGroups",
"DescribeClusterSnapshots",
"DescribeClusterSubnetGroups",
"DescribeClusterTracks",
"DescribeClusterVersions",
"DescribeClusters",
"DescribeCustomDomainAssociations",
"DescribeDataShares",
"DescribeDataSharesForConsumer",
"DescribeDataSharesForProducer",
"DescribeDefaultClusterParameters",
"DescribeEndpointAccess",
"DescribeEndpointAuthorization",
"DescribeEventCategories",
"DescribeEventSubscriptions",
"DescribeEvents",
"DescribeHsmClientCertificates",
"DescribeHsmConfigurations",
"DescribeInboundIntegrations",
"DescribeIntegrations",
"DescribeLoggingStatus",
"DescribeNodeConfigurationOptions",
"DescribeOrderableClusterOptions",
"DescribePartners",
"DescribeRedshiftIdcApplications",
"DescribeReservedNodeExchangeStatus",
"DescribeReservedNodeOfferings",
"DescribeReservedNodes",
"DescribeResize",
"DescribeScheduledActions",
"DescribeSnapshotCopyGrants",
"DescribeSnapshotSchedules",
"DescribeStorage",
"DescribeTableRestoreStatus",
"DescribeTags",
"DescribeUsageLimits",
"DisableLogging",
"DisableSnapshotCopy",
"DisassociateDataShareConsumer",
"EnableLogging",
"EnableSnapshotCopy",
"FailoverPrimaryCompute",
"GetClusterCredentials",
"GetClusterCredentialsWithIAM",
"GetIdentityCenterAuthToken",
"GetReservedNodeExchangeConfigurationOptions",
"GetReservedNodeExchangeOfferings",
"GetResourcePolicy",
"ListRecommendations",
"ModifyAquaConfiguration",
"ModifyAuthenticationProfile",
"ModifyCluster",
"ModifyClusterDbRevision",
"ModifyClusterIamRoles",
"ModifyClusterMaintenance",
"ModifyClusterParameterGroup",
"ModifyClusterSnapshot",
"ModifyClusterSnapshotSchedule",
"ModifyClusterSubnetGroup",
"ModifyCustomDomainAssociation",
"ModifyEndpointAccess",
"ModifyEventSubscription",
"ModifyIntegration",
"ModifyLakehouseConfiguration",
"ModifyRedshiftIdcApplication",
"ModifyScheduledAction",
"ModifySnapshotCopyRetentionPeriod",
"ModifySnapshotSchedule",
"ModifyUsageLimit",
"PauseCluster",
"PurchaseReservedNodeOffering",
"PutResourcePolicy",
"RebootCluster",
"RegisterNamespace",
"RejectDataShare",
"ResetClusterParameterGroup",
"ResizeCluster",
"RestoreFromClusterSnapshot",
"RestoreTableFromClusterSnapshot",
"ResumeCluster",
"RevokeClusterSecurityGroupIngress",
"RevokeEndpointAccess",
"RevokeSnapshotAccess",
"RotateEncryptionKey",
"UpdatePartnerStatus",
];
pub struct RedshiftService {
state: SharedRedshiftState,
snapshot_store: Option<Arc<dyn SnapshotStore>>,
snapshot_lock: Arc<AsyncMutex<()>>,
}
impl RedshiftService {
pub fn new(state: SharedRedshiftState) -> Self {
Self {
state,
snapshot_store: None,
snapshot_lock: Arc::new(AsyncMutex::new(())),
}
}
pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
self.snapshot_store = Some(store);
self
}
pub fn shared_state(&self) -> SharedRedshiftState {
Arc::clone(&self.state)
}
async fn save_snapshot(&self) {
save_redshift_snapshot(
&self.state,
self.snapshot_store.clone(),
&self.snapshot_lock,
)
.await;
}
pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
let store = self.snapshot_store.clone()?;
let state = self.state.clone();
let lock = self.snapshot_lock.clone();
Some(Arc::new(move || {
let state = state.clone();
let store = store.clone();
let lock = lock.clone();
Box::pin(async move {
save_redshift_snapshot(&state, Some(store), &lock).await;
})
}))
}
fn is_mutating(action: &str) -> bool {
!(action.starts_with("Describe") || action.starts_with("Get") || action.starts_with("List"))
}
fn dispatch(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
crate::validation::validate_request(&req.action, req)?;
match req.action.as_str() {
"AcceptReservedNodeExchange" => self.accept_reserved_node_exchange(req),
"AddPartner" => self.add_partner(req),
"AssociateDataShareConsumer" => self.associate_data_share_consumer(req),
"AuthorizeClusterSecurityGroupIngress" => {
self.authorize_cluster_security_group_ingress(req)
}
"AuthorizeDataShare" => self.authorize_data_share(req),
"AuthorizeEndpointAccess" => self.authorize_endpoint_access(req),
"AuthorizeSnapshotAccess" => self.authorize_snapshot_access(req),
"BatchDeleteClusterSnapshots" => self.batch_delete_cluster_snapshots(req),
"BatchModifyClusterSnapshots" => self.batch_modify_cluster_snapshots(req),
"CancelResize" => self.cancel_resize(req),
"CopyClusterSnapshot" => self.copy_cluster_snapshot(req),
"CreateAuthenticationProfile" => self.create_authentication_profile(req),
"CreateCluster" => self.create_cluster(req),
"CreateClusterParameterGroup" => self.create_cluster_parameter_group(req),
"CreateClusterSecurityGroup" => self.create_cluster_security_group(req),
"CreateClusterSnapshot" => self.create_cluster_snapshot(req),
"CreateClusterSubnetGroup" => self.create_cluster_subnet_group(req),
"CreateCustomDomainAssociation" => self.create_custom_domain_association(req),
"CreateEndpointAccess" => self.create_endpoint_access(req),
"CreateEventSubscription" => self.create_event_subscription(req),
"CreateHsmClientCertificate" => self.create_hsm_client_certificate(req),
"CreateHsmConfiguration" => self.create_hsm_configuration(req),
"CreateIntegration" => self.create_integration(req),
"CreateRedshiftIdcApplication" => self.create_redshift_idc_application(req),
"CreateScheduledAction" => self.create_scheduled_action(req),
"CreateSnapshotCopyGrant" => self.create_snapshot_copy_grant(req),
"CreateSnapshotSchedule" => self.create_snapshot_schedule(req),
"CreateTags" => self.create_tags(req),
"CreateUsageLimit" => self.create_usage_limit(req),
"DeauthorizeDataShare" => self.deauthorize_data_share(req),
"DeleteAuthenticationProfile" => self.delete_authentication_profile(req),
"DeleteCluster" => self.delete_cluster(req),
"DeleteClusterParameterGroup" => self.delete_cluster_parameter_group(req),
"DeleteClusterSecurityGroup" => self.delete_cluster_security_group(req),
"DeleteClusterSnapshot" => self.delete_cluster_snapshot(req),
"DeleteClusterSubnetGroup" => self.delete_cluster_subnet_group(req),
"DeleteCustomDomainAssociation" => self.delete_custom_domain_association(req),
"DeleteEndpointAccess" => self.delete_endpoint_access(req),
"DeleteEventSubscription" => self.delete_event_subscription(req),
"DeleteHsmClientCertificate" => self.delete_hsm_client_certificate(req),
"DeleteHsmConfiguration" => self.delete_hsm_configuration(req),
"DeleteIntegration" => self.delete_integration(req),
"DeletePartner" => self.delete_partner(req),
"DeleteRedshiftIdcApplication" => self.delete_redshift_idc_application(req),
"DeleteResourcePolicy" => self.delete_resource_policy(req),
"DeleteScheduledAction" => self.delete_scheduled_action(req),
"DeleteSnapshotCopyGrant" => self.delete_snapshot_copy_grant(req),
"DeleteSnapshotSchedule" => self.delete_snapshot_schedule(req),
"DeleteTags" => self.delete_tags(req),
"DeleteUsageLimit" => self.delete_usage_limit(req),
"DeregisterNamespace" => self.deregister_namespace(req),
"DescribeAccountAttributes" => self.describe_account_attributes(req),
"DescribeAuthenticationProfiles" => self.describe_authentication_profiles(req),
"DescribeClusterDbRevisions" => self.describe_cluster_db_revisions(req),
"DescribeClusterParameterGroups" => self.describe_cluster_parameter_groups(req),
"DescribeClusterParameters" => self.describe_cluster_parameters(req),
"DescribeClusterSecurityGroups" => self.describe_cluster_security_groups(req),
"DescribeClusterSnapshots" => self.describe_cluster_snapshots(req),
"DescribeClusterSubnetGroups" => self.describe_cluster_subnet_groups(req),
"DescribeClusterTracks" => self.describe_cluster_tracks(req),
"DescribeClusterVersions" => self.describe_cluster_versions(req),
"DescribeClusters" => self.describe_clusters(req),
"DescribeCustomDomainAssociations" => self.describe_custom_domain_associations(req),
"DescribeDataShares" => self.describe_data_shares(req),
"DescribeDataSharesForConsumer" => self.describe_data_shares_for_consumer(req),
"DescribeDataSharesForProducer" => self.describe_data_shares_for_producer(req),
"DescribeDefaultClusterParameters" => self.describe_default_cluster_parameters(req),
"DescribeEndpointAccess" => self.describe_endpoint_access(req),
"DescribeEndpointAuthorization" => self.describe_endpoint_authorization(req),
"DescribeEventCategories" => self.describe_event_categories(req),
"DescribeEventSubscriptions" => self.describe_event_subscriptions(req),
"DescribeEvents" => self.describe_events(req),
"DescribeHsmClientCertificates" => self.describe_hsm_client_certificates(req),
"DescribeHsmConfigurations" => self.describe_hsm_configurations(req),
"DescribeInboundIntegrations" => self.describe_inbound_integrations(req),
"DescribeIntegrations" => self.describe_integrations(req),
"DescribeLoggingStatus" => self.describe_logging_status(req),
"DescribeNodeConfigurationOptions" => self.describe_node_configuration_options(req),
"DescribeOrderableClusterOptions" => self.describe_orderable_cluster_options(req),
"DescribePartners" => self.describe_partners(req),
"DescribeRedshiftIdcApplications" => self.describe_redshift_idc_applications(req),
"DescribeReservedNodeExchangeStatus" => {
self.describe_reserved_node_exchange_status(req)
}
"DescribeReservedNodeOfferings" => self.describe_reserved_node_offerings(req),
"DescribeReservedNodes" => self.describe_reserved_nodes(req),
"DescribeResize" => self.describe_resize(req),
"DescribeScheduledActions" => self.describe_scheduled_actions(req),
"DescribeSnapshotCopyGrants" => self.describe_snapshot_copy_grants(req),
"DescribeSnapshotSchedules" => self.describe_snapshot_schedules(req),
"DescribeStorage" => self.describe_storage(req),
"DescribeTableRestoreStatus" => self.describe_table_restore_status(req),
"DescribeTags" => self.describe_tags(req),
"DescribeUsageLimits" => self.describe_usage_limits(req),
"DisableLogging" => self.disable_logging(req),
"DisableSnapshotCopy" => self.disable_snapshot_copy(req),
"DisassociateDataShareConsumer" => self.disassociate_data_share_consumer(req),
"EnableLogging" => self.enable_logging(req),
"EnableSnapshotCopy" => self.enable_snapshot_copy(req),
"FailoverPrimaryCompute" => self.failover_primary_compute(req),
"GetClusterCredentials" => self.get_cluster_credentials(req),
"GetClusterCredentialsWithIAM" => self.get_cluster_credentials_with_iam(req),
"GetIdentityCenterAuthToken" => self.get_identity_center_auth_token(req),
"GetReservedNodeExchangeConfigurationOptions" => {
self.get_reserved_node_exchange_configuration_options(req)
}
"GetReservedNodeExchangeOfferings" => self.get_reserved_node_exchange_offerings(req),
"GetResourcePolicy" => self.get_resource_policy(req),
"ListRecommendations" => self.list_recommendations(req),
"ModifyAquaConfiguration" => self.modify_aqua_configuration(req),
"ModifyAuthenticationProfile" => self.modify_authentication_profile(req),
"ModifyCluster" => self.modify_cluster(req),
"ModifyClusterDbRevision" => self.modify_cluster_db_revision(req),
"ModifyClusterIamRoles" => self.modify_cluster_iam_roles(req),
"ModifyClusterMaintenance" => self.modify_cluster_maintenance(req),
"ModifyClusterParameterGroup" => self.modify_cluster_parameter_group(req),
"ModifyClusterSnapshot" => self.modify_cluster_snapshot(req),
"ModifyClusterSnapshotSchedule" => self.modify_cluster_snapshot_schedule(req),
"ModifyClusterSubnetGroup" => self.modify_cluster_subnet_group(req),
"ModifyCustomDomainAssociation" => self.modify_custom_domain_association(req),
"ModifyEndpointAccess" => self.modify_endpoint_access(req),
"ModifyEventSubscription" => self.modify_event_subscription(req),
"ModifyIntegration" => self.modify_integration(req),
"ModifyLakehouseConfiguration" => self.modify_lakehouse_configuration(req),
"ModifyRedshiftIdcApplication" => self.modify_redshift_idc_application(req),
"ModifyScheduledAction" => self.modify_scheduled_action(req),
"ModifySnapshotCopyRetentionPeriod" => self.modify_snapshot_copy_retention_period(req),
"ModifySnapshotSchedule" => self.modify_snapshot_schedule(req),
"ModifyUsageLimit" => self.modify_usage_limit(req),
"PauseCluster" => self.pause_cluster(req),
"PurchaseReservedNodeOffering" => self.purchase_reserved_node_offering(req),
"PutResourcePolicy" => self.put_resource_policy(req),
"RebootCluster" => self.reboot_cluster(req),
"RegisterNamespace" => self.register_namespace(req),
"RejectDataShare" => self.reject_data_share(req),
"ResetClusterParameterGroup" => self.reset_cluster_parameter_group(req),
"ResizeCluster" => self.resize_cluster(req),
"RestoreFromClusterSnapshot" => self.restore_from_cluster_snapshot(req),
"RestoreTableFromClusterSnapshot" => self.restore_table_from_cluster_snapshot(req),
"ResumeCluster" => self.resume_cluster(req),
"RevokeClusterSecurityGroupIngress" => self.revoke_cluster_security_group_ingress(req),
"RevokeEndpointAccess" => self.revoke_endpoint_access(req),
"RevokeSnapshotAccess" => self.revoke_snapshot_access(req),
"RotateEncryptionKey" => self.rotate_encryption_key(req),
"UpdatePartnerStatus" => self.update_partner_status(req),
other => Err(AwsServiceError::action_not_implemented("redshift", other)),
}
}
}
pub async fn save_redshift_snapshot(
state: &SharedRedshiftState,
store: Option<Arc<dyn SnapshotStore>>,
lock: &AsyncMutex<()>,
) {
let Some(store) = store else {
return;
};
let _guard = lock.lock().await;
let snapshot = RedshiftSnapshot {
schema_version: REDSHIFT_SNAPSHOT_SCHEMA_VERSION,
accounts: Some(state.read().clone()),
};
let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
let bytes = serde_json::to_vec(&snapshot)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
store.save(&bytes)
})
.await;
match join {
Ok(Ok(())) => {}
Ok(Err(err)) => tracing::error!(%err, "failed to write redshift snapshot"),
Err(err) => tracing::error!(%err, "redshift snapshot task panicked"),
}
}
impl Default for RedshiftService {
fn default() -> Self {
Self::new(Arc::new(RwLock::new(RedshiftAccounts::new())))
}
}
#[async_trait]
impl AwsService for RedshiftService {
fn service_name(&self) -> &str {
"redshift"
}
fn supported_actions(&self) -> &[&str] {
SUPPORTED_ACTIONS
}
async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
let mutates = Self::is_mutating(&req.action);
let result = self.dispatch(&req);
if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
self.save_snapshot().await;
}
result
}
}