use std::collections::BTreeMap;
use chrono::Utc;
use serde_json::{json, Map, Value};
use super::{ProvisionResult, ResourceDefinition, ResourceProvisioner, StackResource};
impl ResourceProvisioner {
pub(super) fn create_codecommit_repository(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let name = cc_str(props, "RepositoryName").unwrap_or_else(|| resource.logical_id.clone());
let account = self.account_id.clone();
let region = self.region.clone();
let arn = format!("arn:aws:codecommit:{region}:{account}:{name}");
let now = Utc::now();
let mut guard = self.codecommit_state.write();
let st = guard.get_or_create(&account);
if st.repositories.contains_key(&name) {
return Err(format!("Repository named {name} already exists"));
}
let kms = cc_str(props, "KmsKeyId").unwrap_or_else(|| {
format!(
"arn:aws:kms:{region}:{account}:key/{}",
uuid::Uuid::new_v4()
)
});
let clone_http = format!("https://git-codecommit.{region}.amazonaws.com/v1/repos/{name}");
let clone_ssh = format!("ssh://git-codecommit.{region}.amazonaws.com/v1/repos/{name}");
let mut metadata = Map::new();
metadata.insert("accountId".into(), json!(account));
metadata.insert(
"repositoryId".into(),
json!(uuid::Uuid::new_v4().to_string()),
);
metadata.insert("repositoryName".into(), json!(name));
if let Some(d) = cc_str(props, "RepositoryDescription") {
metadata.insert("repositoryDescription".into(), json!(d));
}
metadata.insert("lastModifiedDate".into(), cc_ts(now));
metadata.insert("creationDate".into(), cc_ts(now));
metadata.insert("cloneUrlHttp".into(), json!(clone_http));
metadata.insert("cloneUrlSsh".into(), json!(clone_ssh));
metadata.insert("Arn".into(), json!(arn));
metadata.insert("kmsKeyId".into(), json!(kms));
let metadata = Value::Object(metadata);
let repo = fakecloud_codecommit::state::Repo {
metadata: metadata.clone(),
triggers: cc_triggers(props),
..Default::default()
};
st.repositories.insert(name.clone(), repo);
st.repository_order.push(name.clone());
let tags = cc_tags(props);
if !tags.is_empty() {
st.tags.insert(arn.clone(), tags);
}
Ok(ProvisionResult::new(name.clone())
.with("Arn", arn)
.with("CloneUrlHttp", clone_http)
.with("CloneUrlSsh", clone_ssh)
.with("Name", name))
}
pub(super) fn update_codecommit_repository(
&self,
existing: &StackResource,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let old_name = existing.physical_id.clone();
let new_name =
cc_str(props, "RepositoryName").unwrap_or_else(|| existing.logical_id.clone());
if new_name != old_name {
self.delete_codecommit_repository(&existing.physical_id);
return self.create_codecommit_repository(resource);
}
let mut guard = self.codecommit_state.write();
let st = guard.get_or_create(&self.account_id);
let repo = st
.repositories
.get_mut(&old_name)
.ok_or_else(|| format!("Repository {old_name} not yet provisioned"))?;
if let Some(obj) = repo.metadata.as_object_mut() {
match cc_str(props, "RepositoryDescription") {
Some(d) => {
obj.insert("repositoryDescription".into(), json!(d));
}
None => {
obj.remove("repositoryDescription");
}
}
if let Some(k) = cc_str(props, "KmsKeyId") {
obj.insert("kmsKeyId".into(), json!(k));
}
obj.insert("lastModifiedDate".into(), cc_ts(Utc::now()));
}
if props.get("Triggers").is_some() {
repo.triggers = cc_triggers(props);
}
let arn = repo
.metadata
.get("Arn")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string();
let clone_http = repo
.metadata
.get("cloneUrlHttp")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string();
let clone_ssh = repo
.metadata
.get("cloneUrlSsh")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string();
let tags = cc_tags(props);
if tags.is_empty() {
st.tags.remove(&arn);
} else {
st.tags.insert(arn.clone(), tags);
}
Ok(ProvisionResult::new(old_name.clone())
.with("Arn", arn)
.with("CloneUrlHttp", clone_http)
.with("CloneUrlSsh", clone_ssh)
.with("Name", old_name))
}
pub(super) fn get_att_codecommit_repository(
&self,
physical_id: &str,
attribute: &str,
) -> Option<String> {
let guard = self.codecommit_state.read();
let repo = guard.get(&self.account_id)?.repositories.get(physical_id)?;
let key = match attribute {
"Arn" => "Arn",
"CloneUrlHttp" => "cloneUrlHttp",
"CloneUrlSsh" => "cloneUrlSsh",
"Name" => "repositoryName",
_ => return None,
};
repo.metadata
.get(key)
.and_then(Value::as_str)
.map(str::to_string)
}
pub(super) fn delete_codecommit_repository(&self, physical_id: &str) {
let mut guard = self.codecommit_state.write();
let st = guard.get_or_create(&self.account_id);
if let Some(repo) = st.repositories.remove(physical_id) {
st.repository_order.retain(|n| n != physical_id);
if let Some(arn) = repo.metadata.get("Arn").and_then(Value::as_str) {
st.tags.remove(arn);
}
}
}
}
fn cc_str(props: &Value, key: &str) -> Option<String> {
props
.get(key)
.and_then(Value::as_str)
.filter(|s| !s.is_empty())
.map(str::to_string)
}
fn cc_ts(dt: chrono::DateTime<Utc>) -> Value {
json!(dt.timestamp_millis() as f64 / 1000.0)
}
fn cc_tags(props: &Value) -> BTreeMap<String, String> {
let mut out = BTreeMap::new();
if let Some(arr) = props.get("Tags").and_then(Value::as_array) {
for t in arr {
let key = t.get("Key").and_then(Value::as_str).unwrap_or("");
let value = t.get("Value").and_then(Value::as_str).unwrap_or("");
if !key.is_empty() {
out.insert(key.to_string(), value.to_string());
}
}
}
out
}
fn cc_triggers(props: &Value) -> Vec<Value> {
let mut out = Vec::new();
if let Some(arr) = props.get("Triggers").and_then(Value::as_array) {
for t in arr {
let mut trig = Map::new();
if let Some(v) = t.get("Name").and_then(Value::as_str) {
trig.insert("name".into(), json!(v));
}
if let Some(v) = t.get("DestinationArn").and_then(Value::as_str) {
trig.insert("destinationArn".into(), json!(v));
}
if let Some(v) = t.get("CustomData").and_then(Value::as_str) {
trig.insert("customData".into(), json!(v));
}
if let Some(v) = t.get("Branches").and_then(Value::as_array) {
trig.insert("branches".into(), json!(v));
}
if let Some(v) = t.get("Events").and_then(Value::as_array) {
trig.insert("events".into(), json!(v));
}
if !trig.is_empty() {
out.push(Value::Object(trig));
}
}
}
out
}