diff --git a/.sqlx/query-54c55fa3609a5fef17ffa8508ee834ffc978aaab3ce92ad9b276de98c429efbd.json b/.sqlx/query-a8589c22d0d0d06df19bbe4629639652645b0b631069bc6e44301e2d8d24cddf.json similarity index 74% rename from .sqlx/query-54c55fa3609a5fef17ffa8508ee834ffc978aaab3ce92ad9b276de98c429efbd.json rename to .sqlx/query-a8589c22d0d0d06df19bbe4629639652645b0b631069bc6e44301e2d8d24cddf.json index 22366a9..c286edc 100644 --- a/.sqlx/query-54c55fa3609a5fef17ffa8508ee834ffc978aaab3ce92ad9b276de98c429efbd.json +++ b/.sqlx/query-a8589c22d0d0d06df19bbe4629639652645b0b631069bc6e44301e2d8d24cddf.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n INSERT INTO resource (\n id,\n project_id,\n kind,\n data,\n status,\n created_at,\n updated_at\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ", + "query": "\n INSERT INTO resource (\n id,\n project_id,\n kind,\n spec,\n status,\n created_at,\n updated_at\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ", "describe": { "columns": [], "parameters": { @@ -8,5 +8,5 @@ }, "nullable": [] }, - "hash": "54c55fa3609a5fef17ffa8508ee834ffc978aaab3ce92ad9b276de98c429efbd" + "hash": "a8589c22d0d0d06df19bbe4629639652645b0b631069bc6e44301e2d8d24cddf" } diff --git a/Cargo.lock b/Cargo.lock index be5ac13..2da8d43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -476,7 +476,7 @@ dependencies = [ [[package]] name = "dmtri" version = "0.1.0" -source = "git+https://github.com/demeter-run/specs.git#b5c06294353816a4b24fe6a41d24da12a33d0226" +source = "git+https://github.com/demeter-run/specs.git#bc937050d620011c02371c6040a71c042200e6f5" dependencies = [ "bytes", "pbjson", @@ -2257,9 +2257,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -3073,12 +3073,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", "windows-sys 0.52.0", ] diff --git a/src/domain/event/mod.rs b/src/domain/event/mod.rs index 0f2ab5c..dfb3e59 100644 --- a/src/domain/event/mod.rs +++ b/src/domain/event/mod.rs @@ -41,7 +41,7 @@ pub struct ResourceCreated { pub project_id: String, pub project_namespace: String, pub kind: String, - pub data: String, + pub spec: String, pub status: String, pub created_at: DateTime, pub updated_at: DateTime, @@ -138,7 +138,7 @@ mod tests { project_id: Uuid::new_v4().to_string(), project_namespace: "prj-test".into(), kind: "CardanoNode".into(), - data: "{\"spec\":{\"operatorVersion\":\"1\",\"kupoVersion\":\"v1\",\"network\":\"mainnet\",\"pruneUtxo\":false,\"throughputTier\":\"0\"}}".into(), + spec: "{\"operatorVersion\":\"1\",\"kupoVersion\":\"v1\",\"network\":\"mainnet\",\"pruneUtxo\":false,\"throughputTier\":\"0\"}".into(), status: ResourceStatus::Active.to_string(), created_at: Utc::now(), updated_at: Utc::now(), diff --git a/src/domain/resource/cluster.rs b/src/domain/resource/cluster.rs index 42d24f4..f4420fc 100644 --- a/src/domain/resource/cluster.rs +++ b/src/domain/resource/cluster.rs @@ -5,6 +5,7 @@ use kube::{ api::{ApiResource, DynamicObject, ObjectMeta}, ResourceExt, }; +use serde_json::json; use tracing::info; use crate::domain::event::{ResourceCreated, ResourceDeleted}; @@ -27,7 +28,9 @@ pub async fn apply_manifest( namespace: Some(evt.project_namespace), ..Default::default() }; - obj.data = serde_json::from_str(&evt.data)?; + + let spec = serde_json::from_str(&evt.spec)?; + obj.data = json!({ "spec": serde_json::Value::Object(spec) }); cluster.create(&obj).await?; diff --git a/src/domain/resource/command.rs b/src/domain/resource/command.rs index d67f369..d195ce6 100644 --- a/src/domain/resource/command.rs +++ b/src/domain/resource/command.rs @@ -1,7 +1,10 @@ use std::sync::Arc; -use anyhow::{bail, ensure, Result}; +use anyhow::{bail, ensure, Error, Result}; +use argon2::{password_hash::SaltString, Argon2}; +use bech32::{Bech32m, Hrp}; use chrono::Utc; +use rand::rngs::OsRng; use tracing::info; use uuid::Uuid; @@ -38,12 +41,17 @@ pub async fn create( bail!("project doesnt exist") }; + let auth_token = build_auth_token(&project.id, &cmd.id, &cmd.kind)?; + + let mut spec = cmd.spec.clone(); + spec.insert("authToken".into(), serde_json::Value::String(auth_token)); + let evt = ResourceCreated { id: cmd.id, project_id: project.id, project_namespace: project.namespace, kind: cmd.kind.clone(), - data: cmd.data, + spec: serde_json::to_string(&spec)?, status: ResourceStatus::Active.to_string(), created_at: Utc::now(), updated_at: Utc::now(), @@ -116,6 +124,24 @@ fn assert_project_resource(project: &Project, resource: &Resource) -> Result<()> Ok(()) } +pub fn build_auth_token(project_id: &str, resource_id: &str, kind: &str) -> Result { + let argon2 = Argon2::default(); + let key = format!("{project_id}{resource_id}").as_bytes().to_vec(); + + let salt = SaltString::generate(&mut OsRng); + + let mut output = vec![0; 8]; + argon2 + .hash_password_into(&key, salt.as_str().as_bytes(), &mut output) + .map_err(|err| Error::msg(err.to_string()))?; + + let prefix = format!("dmtr_{}", kind.to_lowercase()); + let hrp = Hrp::parse(&prefix)?; + let bech = bech32::encode::(hrp, &output)?; + + Ok(bech) +} + #[derive(Debug, Clone)] pub struct FetchCmd { pub credential: Credential, @@ -147,16 +173,17 @@ impl FetchCmd { } } +pub type Spec = serde_json::value::Map; #[derive(Debug, Clone)] pub struct CreateCmd { pub credential: Credential, pub id: String, pub project_id: String, pub kind: String, - pub data: String, + pub spec: Spec, } impl CreateCmd { - pub fn new(credential: Credential, project_id: String, kind: String, data: String) -> Self { + pub fn new(credential: Credential, project_id: String, kind: String, spec: Spec) -> Self { let id = Uuid::new_v4().to_string(); Self { @@ -164,7 +191,7 @@ impl CreateCmd { id, project_id, kind, - data, + spec, } } } @@ -240,7 +267,7 @@ mod tests { id: Uuid::new_v4().to_string(), project_id: Uuid::new_v4().to_string(), kind: "CardanoNode".into(), - data: "{\"spec\":{\"operatorVersion\":\"1\",\"kupoVersion\":\"v1\",\"network\":\"mainnet\",\"pruneUtxo\":false,\"throughputTier\":\"0\"}}".into(), + spec: serde_json::Map::new(), } } } diff --git a/src/domain/resource/mod.rs b/src/domain/resource/mod.rs index 120c11b..336e8ff 100644 --- a/src/domain/resource/mod.rs +++ b/src/domain/resource/mod.rs @@ -13,7 +13,7 @@ pub struct Resource { pub id: String, pub project_id: String, pub kind: String, - pub data: String, + pub spec: String, pub status: ResourceStatus, pub created_at: DateTime, pub updated_at: DateTime, @@ -26,7 +26,7 @@ impl TryFrom for Resource { id: value.id, project_id: value.project_id, kind: value.kind, - data: value.data, + spec: value.spec, status: value.status.parse()?, created_at: value.created_at, updated_at: value.updated_at, @@ -71,7 +71,7 @@ mod tests { id: Uuid::new_v4().to_string(), project_id: Uuid::new_v4().to_string(), kind: "CardanoNode".into(), - data: "{\"spec\":{\"operatorVersion\":\"1\",\"kupoVersion\":\"v1\",\"network\":\"mainnet\",\"pruneUtxo\":false,\"throughputTier\":\"0\"}}".into(), + spec: "{\"operatorVersion\":\"1\",\"kupoVersion\":\"v1\",\"network\":\"mainnet\",\"pruneUtxo\":false,\"throughputTier\":\"0\"}".into(), status: ResourceStatus::Active, created_at: Utc::now(), updated_at: Utc::now(), diff --git a/src/driven/cache/migrations/20240606_tables.sql b/src/driven/cache/migrations/20240606_tables.sql index 381734b..f673adf 100644 --- a/src/driven/cache/migrations/20240606_tables.sql +++ b/src/driven/cache/migrations/20240606_tables.sql @@ -12,7 +12,7 @@ CREATE TABLE IF NOT EXISTS resource ( id TEXT PRIMARY KEY NOT NULL, project_id TEXT NOT NULL, kind TEXT NOT NULL, - data TEXT NOT NULL, + spec TEXT NOT NULL, status TEXT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, diff --git a/src/driven/cache/resource.rs b/src/driven/cache/resource.rs index 042ce04..13bfa74 100644 --- a/src/driven/cache/resource.rs +++ b/src/driven/cache/resource.rs @@ -26,7 +26,7 @@ impl ResourceDrivenCache for SqliteResourceDrivenCache { r.id, r.project_id, r.kind, - r.data, + r.spec, r.status, r.created_at, r.updated_at @@ -53,7 +53,7 @@ impl ResourceDrivenCache for SqliteResourceDrivenCache { r.id, r.project_id, r.kind, - r.data, + r.spec, r.status, r.created_at, r.updated_at @@ -77,7 +77,7 @@ impl ResourceDrivenCache for SqliteResourceDrivenCache { id, project_id, kind, - data, + spec, status, created_at, updated_at @@ -87,7 +87,7 @@ impl ResourceDrivenCache for SqliteResourceDrivenCache { resource.id, resource.project_id, resource.kind, - resource.data, + resource.spec, status, resource.created_at, resource.updated_at @@ -127,7 +127,7 @@ impl FromRow<'_, SqliteRow> for Resource { id: row.try_get("id")?, project_id: row.try_get("project_id")?, kind: row.try_get("kind")?, - data: row.try_get("data")?, + spec: row.try_get("spec")?, status: status .parse() .map_err(|err: Error| sqlx::Error::Decode(err.into()))?, diff --git a/src/drivers/grpc/resource.rs b/src/drivers/grpc/resource.rs index 4cdf5d8..35fc8e9 100644 --- a/src/drivers/grpc/resource.rs +++ b/src/drivers/grpc/resource.rs @@ -1,4 +1,5 @@ use dmtri::demeter::ops::v1alpha::{self as proto, DeleteResourceResponse}; +use serde_json::Value; use std::sync::Arc; use tonic::{async_trait, Status}; @@ -65,7 +66,14 @@ impl proto::resource_service_server::ResourceService for ResourceServiceImpl { let req = request.into_inner(); - let cmd = command::CreateCmd::new(credential, req.project_id, req.kind, req.data); + let value = serde_json::from_str(&req.spec) + .map_err(|_| Status::failed_precondition("spec must be a json"))?; + let spec = match value { + Value::Object(v) => Ok(v), + _ => Err(Status::failed_precondition("invalid spec json")), + }?; + + let cmd = command::CreateCmd::new(credential, req.project_id, req.kind, spec); command::create(self.project_cache.clone(), self.event.clone(), cmd.clone()) .await @@ -113,7 +121,7 @@ impl From for proto::Resource { Self { id: value.id, kind: value.kind, - data: value.data, + spec: value.spec, status: value.status.to_string(), created_at: value.created_at.to_rfc3339(), updated_at: value.updated_at.to_rfc3339(),