use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use chrono::{DateTime, Utc};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use fakecloud_core::multi_account::{AccountState, MultiAccountState};
pub const RESOURCE_GROUPS_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceQuery {
pub type_: String,
pub query: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceGroup {
pub name: String,
pub arn: String,
pub description: Option<String>,
pub query: Option<ResourceQuery>,
pub configuration: Vec<serde_json::Value>,
pub tags: BTreeMap<String, String>,
pub criticality: Option<i32>,
pub owner: Option<String>,
pub display_name: Option<String>,
pub application_tag: BTreeMap<String, String>,
pub resources: BTreeSet<String>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TagSyncTask {
pub task_arn: String,
pub group_arn: String,
pub group_name: String,
pub tag_key: Option<String>,
pub tag_value: Option<String>,
pub resource_query: Option<ResourceQuery>,
pub role_arn: String,
pub status: String,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AccountSettings {
pub desired_status: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ResourceGroupsState {
pub groups: BTreeMap<String, ResourceGroup>,
pub tag_sync_tasks: BTreeMap<String, TagSyncTask>,
pub account_settings: AccountSettings,
}
impl ResourceGroupsState {
pub fn resolve_name<'a>(&'a self, name_or_arn: &'a str) -> Option<&'a str> {
if self.groups.contains_key(name_or_arn) {
return Some(name_or_arn);
}
if let Some(g) = self.groups.values().find(|g| g.arn == name_or_arn) {
return Some(g.name.as_str());
}
if let Some(resource) = name_or_arn
.starts_with("arn:")
.then(|| name_or_arn.split(":group/").nth(1))
.flatten()
{
let group_name = resource.split('/').next().unwrap_or(resource);
if let Some((stored, _)) = self.groups.get_key_value(group_name) {
return Some(stored.as_str());
}
}
None
}
}
impl AccountState for ResourceGroupsState {
fn new_for_account(_account_id: &str, _region: &str, _endpoint: &str) -> Self {
Self::default()
}
}
pub type SharedResourceGroupsState = Arc<RwLock<MultiAccountState<ResourceGroupsState>>>;
#[derive(Debug, Serialize, Deserialize)]
pub struct ResourceGroupsSnapshot {
pub schema_version: u32,
pub accounts: MultiAccountState<ResourceGroupsState>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_by_name_and_arn() {
let mut st = ResourceGroupsState::default();
st.groups.insert(
"g1".into(),
ResourceGroup {
name: "g1".into(),
arn: "arn:aws:resource-groups:us-east-1:123456789012:group/g1/abcd".into(),
description: None,
query: None,
configuration: vec![],
tags: BTreeMap::new(),
criticality: None,
owner: None,
display_name: None,
application_tag: BTreeMap::new(),
resources: BTreeSet::new(),
created_at: Utc::now(),
},
);
assert_eq!(st.resolve_name("g1"), Some("g1"));
assert_eq!(
st.resolve_name("arn:aws:resource-groups:us-east-1:123456789012:group/g1/abcd"),
Some("g1")
);
assert_eq!(
st.resolve_name("arn:aws:resource-groups:us-east-1:123456789012:group/g1"),
Some("g1")
);
assert_eq!(
st.resolve_name("arn:aws:resource-groups:us-west-2:123456789012:group/g1/zzzz"),
Some("g1")
);
assert_eq!(st.resolve_name("missing"), None);
assert_eq!(
st.resolve_name("arn:aws:resource-groups:us-east-1:123456789012:group/missing"),
None
);
}
}