use std::collections::BTreeMap;
use std::fmt;
use olai_stack_topology::EnvManifest;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
macro_rules! id {
($(#[$doc:meta])* $name:ident) => {
$(#[$doc])*
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(pub String);
impl $name {
pub(crate) fn generate() -> Self {
$name(uuid::Uuid::new_v4().to_string())
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
};
}
id!(
WorkspaceId
);
id!(
EnvironmentId
);
id!(
InstanceId
);
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SecretRef {
pub provider: String,
pub key: String,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VarValue {
Literal(String),
Secret(SecretRef),
}
impl fmt::Debug for VarValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VarValue::Literal(_) => f.write_str("Literal(<redacted>)"),
VarValue::Secret(r) => f.debug_tuple("Secret").field(r).finish(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct VarSet(pub BTreeMap<String, VarValue>);
impl VarSet {
pub fn new() -> Self {
Self::default()
}
pub fn set(&mut self, name: impl Into<String>, value: VarValue) -> &mut Self {
self.0.insert(name.into(), value);
self
}
pub fn get(&self, name: &str) -> Option<&VarValue> {
self.0.get(name)
}
pub fn remove(&mut self, name: &str) -> Option<VarValue> {
self.0.remove(name)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VarScope {
Workspace(WorkspaceId),
Environment(EnvironmentId),
Draft(EnvironmentId),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceBinding {
pub name: String,
#[serde(default)]
pub config: serde_json::Value,
}
impl SourceBinding {
pub fn new(name: impl Into<String>) -> Self {
SourceBinding {
name: name.into(),
config: serde_json::Value::Null,
}
}
pub fn with_config(mut self, config: serde_json::Value) -> Self {
self.config = config;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Workspace {
pub id: WorkspaceId,
pub name: String,
pub mesh: bool,
pub vars: VarSet,
pub sources: BTreeMap<String, serde_json::Value>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Draft {
pub manifest: EnvManifest,
pub modules: BTreeMap<String, String>,
pub vars: VarSet,
}
impl Draft {
pub fn new(manifest: EnvManifest) -> Self {
Draft {
manifest,
modules: BTreeMap::new(),
vars: VarSet::new(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Environment {
pub id: EnvironmentId,
pub workspace: WorkspaceId,
pub name: String,
pub draft: Draft,
pub vars: VarSet,
pub head: Option<u32>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Revision {
pub(crate) environment: EnvironmentId,
pub(crate) number: u32,
pub(crate) manifest: EnvManifest,
pub(crate) manifest_toml: String,
pub(crate) modules: BTreeMap<String, String>,
pub(crate) vars: VarSet,
pub(crate) digest: String,
}
impl Revision {
pub fn environment(&self) -> &EnvironmentId {
&self.environment
}
pub fn number(&self) -> u32 {
self.number
}
pub fn manifest(&self) -> &EnvManifest {
&self.manifest
}
pub fn manifest_toml(&self) -> &str {
&self.manifest_toml
}
pub fn modules(&self) -> &BTreeMap<String, String> {
&self.modules
}
pub fn vars(&self) -> &VarSet {
&self.vars
}
pub fn digest(&self) -> &str {
&self.digest
}
}
pub(crate) fn digest(
manifest_toml: &str,
modules: &BTreeMap<String, String>,
vars: &VarSet,
) -> Result<String, serde_json::Error> {
#[derive(Serialize)]
struct Content<'a> {
manifest: &'a str,
modules: &'a BTreeMap<String, String>,
vars: &'a VarSet,
}
let bytes = serde_json::to_vec(&Content {
manifest: manifest_toml,
modules,
vars,
})?;
Ok(hex::encode(Sha256::digest(bytes)))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Desired {
Running,
Stopped,
}
impl Desired {
pub(crate) fn as_str(self) -> &'static str {
match self {
Desired::Running => "running",
Desired::Stopped => "stopped",
}
}
pub(crate) fn parse(s: &str) -> Self {
if s == "running" {
Desired::Running
} else {
Desired::Stopped
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Instance {
pub id: InstanceId,
pub environment: EnvironmentId,
pub revision: u32,
pub desired: Desired,
pub overrides: VarSet,
pub sources: Vec<SourceBinding>,
pub slot: Option<u16>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SlotLease {
pub slot: u16,
pub instance: InstanceId,
}