use std::path::Path;
use greentic_distributor_client::signing::TrustedKey;
use greentic_deploy_spec::{
EnvId, Environment, EnvironmentHostConfig, HealthStatus, IdempotencyKey, RetentionPolicy,
RevocationConfig, SchemaVersion,
};
use super::mutations::{
ExtensionKey, MigrateMergePayload, TrustRootAddOutcome, TrustRootRemoveOutcome, TrustRootSeed,
UpdateEnvironmentPayload,
};
use super::store::{LocalFsStore, StoreError};
use super::trust_root::{self as store_trust_root, trust_root_path};
impl LocalFsStore {
pub fn create_environment(
&self,
env_id: &EnvId,
name: String,
host_config: EnvironmentHostConfig,
) -> Result<Environment, StoreError> {
self.transact(env_id, |locked| {
match locked.load() {
Ok(_) => {
return Err(StoreError::Conflict(format!(
"environment `{}` already exists",
locked.env_id()
)));
}
Err(StoreError::NotFound(_)) => {}
Err(e) => return Err(e),
}
let env = fresh_environment(
locked.env_id(),
name,
host_config,
RevocationConfig::default(),
RetentionPolicy::default(),
HealthStatus::default(),
);
locked.save(&env)?;
Ok(env)
})
}
pub fn update_environment(
&self,
env_id: &EnvId,
patch: UpdateEnvironmentPayload,
) -> Result<Environment, StoreError> {
self.transact(env_id, |locked| {
let mut env = locked.load()?;
if let Some(name) = patch.name {
env.name = name;
}
if let Some(region) = patch.region {
env.host_config.region = Some(region);
}
if let Some(org) = patch.tenant_org_id {
env.host_config.tenant_org_id = Some(org);
}
if let Some(addr) = patch.listen_addr {
env.host_config.listen_addr = Some(addr);
}
if let Some(url) = patch.public_base_url {
env.host_config.public_base_url = Some(url);
}
locked.save(&env)?;
Ok(env)
})
}
pub fn migrate_merge_bindings(
&self,
target_env_id: &EnvId,
payload: MigrateMergePayload,
) -> Result<(Vec<String>, Vec<String>), StoreError> {
let MigrateMergePayload {
packs,
extensions,
seed_if_missing,
} = payload;
self.transact(target_env_id, |locked| {
let mut target_env = match locked.load() {
Ok(env) => env,
Err(StoreError::NotFound(id)) => match seed_if_missing {
Some(seed) => fresh_environment(
locked.env_id(),
locked.env_id().as_str().to_string(),
seed.host_config,
seed.revocation,
seed.retention,
seed.health,
),
None => return Err(StoreError::NotFound(id)),
},
Err(e) => return Err(e),
};
let mut added_slots = Vec::new();
for binding in packs {
if target_env.packs.iter().any(|b| b.slot == binding.slot) {
continue;
}
added_slots.push(binding.slot.to_string());
target_env.packs.push(binding);
}
let mut added_extensions = Vec::new();
for ext in extensions {
let key = ExtensionKey::from_binding(&ext);
if target_env.extensions.iter().any(|e| key.matches(e)) {
continue;
}
added_extensions.push(key.to_string());
target_env.extensions.push(ext);
}
locked.save(&target_env)?;
Ok((added_slots, added_extensions))
})
}
pub fn bootstrap_trust_root(&self, env_id: &EnvId) -> Result<TrustRootSeed, StoreError> {
let op_key = crate::operator_key::load_or_generate()?;
let env_dir = self.env_dir(env_id)?;
self.transact(env_id, |_locked| seed_op_key(&env_dir, op_key))
}
pub fn seed_trust_root_if_absent(
&self,
env_id: &EnvId,
) -> Result<Option<TrustRootSeed>, StoreError> {
let env_dir = self.env_dir(env_id)?;
let tr_path = trust_root_path(&env_dir);
self.transact(env_id, |_locked| {
if tr_path.exists() {
return Ok(None);
}
let op_key = crate::operator_key::load_or_generate()?;
seed_op_key(&env_dir, op_key).map(Some)
})
}
pub fn add_trusted_key(
&self,
env_id: &EnvId,
key_id: String,
public_key_pem: String,
_idempotency_key: IdempotencyKey,
) -> Result<TrustRootAddOutcome, StoreError> {
let env_dir = self.env_dir(env_id)?;
self.transact(env_id, |_locked| {
let trust = store_trust_root::add_trusted_key(
&env_dir,
TrustedKey {
key_id: key_id.clone(),
public_key_pem,
},
)?;
Ok(TrustRootAddOutcome {
added_key_id: key_id,
trusted_key_count: trust.keys.len(),
})
})
}
pub fn remove_trusted_key(
&self,
env_id: &EnvId,
key_id: String,
_idempotency_key: IdempotencyKey,
) -> Result<TrustRootRemoveOutcome, StoreError> {
let env_dir = self.env_dir(env_id)?;
self.transact(env_id, |_locked| {
let pre = store_trust_root::load(&env_dir)?;
let removed_public_key_pem = pre
.keys
.iter()
.find(|k| k.key_id.eq_ignore_ascii_case(&key_id))
.map(|k| k.public_key_pem.clone());
let trust = store_trust_root::remove_trusted_key(&env_dir, &key_id)?;
Ok(TrustRootRemoveOutcome {
removed_key_id: key_id,
removed_public_key_pem,
trusted_key_count: trust.keys.len(),
})
})
}
}
fn fresh_environment(
env_id: &EnvId,
name: String,
host_config: EnvironmentHostConfig,
revocation: RevocationConfig,
retention: RetentionPolicy,
health: HealthStatus,
) -> Environment {
Environment {
schema: SchemaVersion::new(SchemaVersion::ENVIRONMENT_V1),
environment_id: env_id.clone(),
name,
host_config: EnvironmentHostConfig {
env_id: env_id.clone(),
..host_config
},
packs: Vec::new(),
credentials_ref: None,
bundles: Vec::new(),
revisions: Vec::new(),
traffic_splits: Vec::new(),
messaging_endpoints: Vec::new(),
extensions: Vec::new(),
revocation,
retention,
health,
}
}
fn seed_op_key(
env_dir: &Path,
op_key: crate::operator_key::OperatorKey,
) -> Result<TrustRootSeed, StoreError> {
let trust = store_trust_root::add_trusted_key(
env_dir,
TrustedKey {
key_id: op_key.key_id.clone(),
public_key_pem: op_key.public_pem.clone(),
},
)?;
Ok(TrustRootSeed {
key_id: op_key.key_id,
public_key_pem: op_key.public_pem,
trusted_key_count: trust.keys.len(),
})
}