Skip to content

Commit

Permalink
feat: add embedded gateway support for debugging
Browse files Browse the repository at this point in the history
  • Loading branch information
HoKim98 committed Jul 12, 2024
1 parent 31a1eea commit 072e50c
Show file tree
Hide file tree
Showing 20 changed files with 486 additions and 195 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/gh_pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
run: sed -i '/\[\[hooks\]\]/,$d' 'Trunk.toml'

- name: Build
run: trunk build --release
run: trunk build --release --features examples

- name: Setup Pages
uses: actions/configure-pages@v5
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ members = [
"crates/cassette",
"crates/cassette-core",
"crates/cassette-gateway",
"crates/cassette-loader-core",
"crates/cassette-loader-file",
"crates/cassette-operator",
"crates/cassette-plugin-kubernetes",
]
Expand Down Expand Up @@ -46,9 +48,11 @@ gloo-net = { version = "0.5", default-features = false, features = [
"json",
] }
gloo-utils = { version = "0.2", default-features = false }
include_dir = { version = "0.7" }
inflector = { package = "Inflector", version = "0.11" }
k8s-openapi = { version = "0.22", features = ["latest", "schemars"] }
kube = { version = "0.91", default-features = false }
once_cell = { version = "1.19" }
patternfly-yew = { version = "0.6", features = [
"icons-fab",
"tree",
Expand All @@ -58,6 +62,7 @@ regex = { version = "1.10", default-features = false }
schemars = { version = "0.8", default-features = false, features = ["uuid1"] }
serde = { version = "1.0", default-features = false }
serde_json = { version = "1.0", default-features = false }
serde_yml = { version = "0.0", default-features = false }
tokio = { version = "1.38", default-features = false }
tracing = { version = "0.1" }
tracing-subscriber = { version = "0.3" }
Expand Down
2 changes: 2 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ build *ARGS: ( _trunk "build" ARGS )

run *ARGS: ( _trunk "serve" ARGS )

run-examples *ARGS: ( _trunk "serve" "--features" "examples" ARGS )

run-gateway *ARGS:
cargo run --package 'cassette-gateway' --release

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ This is a Cloud-native Template-based dynamic declarative web UI framework built

You can get started by experiencing the various features here: https://api.ulagbulag.io

You can also deploy your own example app using one-line simple command:

```bash
just run-examples # trunk serve --features examples
```

## Tutorial

TBD
Expand Down
3 changes: 3 additions & 0 deletions crates/cassette-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ default = []
actix-web = ["dep:actix-web"]
ui = ["dep:gloo-net", "dep:yew"]

# for demo ONLY
examples = []

[dependencies]
actix-web = { workspace = true, optional = true }
garde = { workspace = true }
Expand Down
30 changes: 30 additions & 0 deletions crates/cassette-core/src/document.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use serde::{Deserialize, Serialize, Serializer};

use crate::{cassette::CassetteCrd, component::CassetteComponentCrd};

#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "kind")]
pub enum Document {
Cassette(CassetteCrd),
CassetteComponent(CassetteComponentCrd),
}

impl Serialize for Document {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
#[serde(untagged)]
enum DocumentSer<'a> {
Cassette(&'a CassetteCrd),
CassetteComponent(&'a CassetteComponentCrd),
}

let document = match self {
Document::Cassette(cr) => DocumentSer::Cassette(cr),
Document::CassetteComponent(cr) => DocumentSer::CassetteComponent(cr),
};
document.serialize(serializer)
}
}
1 change: 1 addition & 0 deletions crates/cassette-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod cassette;
pub mod component;
pub mod document;
#[cfg(feature = "ui")]
pub mod net;
pub mod result;
Expand Down
65 changes: 47 additions & 18 deletions crates/cassette-core/src/net/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,30 +59,59 @@ pub fn use_query(key: &str) -> Option<String> {

#[hook]
pub fn use_gateway() -> String {
use_query("gateway").unwrap_or_else(|| {
#[cfg(debug_assertions)]
{
"http://localhost:8080".into()
}

#[cfg(not(debug_assertions))]
{
"/v1/cassette".into()
}
})
#[cfg(feature = "examples")]
{
"(embedded)".into()
}

#[cfg(not(feature = "examples"))]
{
use_query("gateway").unwrap_or_else(|| {
#[cfg(debug_assertions)]
{
"http://localhost:8080".into()
}

#[cfg(not(debug_assertions))]
{
"/v1/cassette".into()
}
})
}
}

#[hook]
pub fn use_gateway_status() -> String {
let state = use_fetch_unchecked::<String, _>(move || FetchRequest {
method: Method::GET,
name: "gateway health",
url: "/_health",
});
state.to_string()
#[cfg(feature = "examples")]
{
"healthy".into()
}

#[cfg(not(feature = "examples"))]
{
let state = use_fetch_unchecked::<String, _>(move || FetchRequest {
method: Method::GET,
name: "gateway health",
url: "/_health",
});
state.to_string()
}
}

#[hook]
pub fn use_namespace() -> String {
use_query("namespace").unwrap_or_else(|| "default".into())
#[cfg(feature = "examples")]
{
DEFAULT_NAMESPACE.into()
}

#[cfg(not(feature = "examples"))]
{
use_query("namespace").unwrap_or_else(|| DEFAULT_NAMESPACE.into())
}
}

#[cfg(feature = "examples")]
pub const DEFAULT_NAMESPACE: &str = "examples";
#[cfg(not(feature = "examples"))]
pub const DEFAULT_NAMESPACE: &str = "default";
1 change: 1 addition & 0 deletions crates/cassette-core/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub enum TaskState {
#[serde(transparent)]
pub struct TaskSpec(Map<String, Value>);

#[cfg(feature = "ui")]
impl TaskSpec {
pub fn get_string(&self, key: &str) -> TaskResult<String> {
self.0
Expand Down
1 change: 1 addition & 0 deletions crates/cassette-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ rustls-tls = ["actix-web/rustls", "kube/rustls-tls"]

[dependencies]
cassette-core = { path = "../cassette-core", features = ["actix-web"] }
cassette-loader-core = { path = "../cassette-loader-core" }
cassette-plugin-kubernetes = { path = "../cassette-plugin-kubernetes", optional = true, features = [
"api",
] }
Expand Down
163 changes: 2 additions & 161 deletions crates/cassette-gateway/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
use std::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
};
use std::sync::Arc;

use cassette_core::{
cassette::{Cassette, CassetteCrd, CassetteRef},
component::CassetteComponentCrd,
};
use kube::ResourceExt;
use cassette_loader_core::CassetteDB as CassetteDBInner;
use tokio::sync::RwLock;
use uuid::Uuid;

Expand Down Expand Up @@ -43,159 +40,3 @@ impl CassetteDB {
self.0.write().await.remove_component(cr)
}
}

#[derive(Default)]
struct CassetteDBInner {
cassettes: BTreeMap<String, BTreeSet<Cassette<String>>>,
components: BTreeMap<Uuid, CassetteComponentCrd>,
components_scopes: BTreeMap<Scope, Uuid>,
}

impl CassetteDBInner {
fn get(&self, namespace: &str, id: Uuid) -> Option<Cassette> {
let Cassette {
id,
component: component_name,
name,
group,
description,
priority,
} = self
.cassettes
.get(namespace)
.and_then(|cassettes| cassettes.iter().find(|cassette| cassette.id == id).cloned())?;

let scope = Scope {
namespace: namespace.into(),
name: component_name,
};
let component_id = self.components_scopes.get(&scope)?;
let component = self
.components
.get(component_id)
.map(|cr| cr.spec.clone())?;

Some(Cassette {
id,
component,
name,
group,
description,
priority,
})
}

fn list(&self, namespace: &str) -> Vec<CassetteRef> {
self.cassettes
.get(namespace)
.map(|cassettes| {
cassettes
.iter()
.cloned()
.filter_map(|cassette| self.find_component(namespace, cassette))
.collect()
})
.unwrap_or_default()
}
}

impl CassetteDBInner {
fn insert(&mut self, cr: CassetteCrd) {
let id = match cr.uid().and_then(|uid| uid.parse().ok()) {
Some(uid) => uid,
None => return,
};
let namespace = cr.namespace().unwrap_or_else(|| "default".into());
let name = cr.name_any();

let cassette = Cassette {
id,
component: cr.spec.component,
name,
group: cr.spec.group,
description: cr.spec.description,
priority: cr.spec.priority,
};
self.cassettes
.entry(namespace)
.or_default()
.insert(cassette);
}

fn remove(&mut self, cr: CassetteCrd) {
let id: Uuid = match cr.uid().and_then(|uid| uid.parse().ok()) {
Some(uid) => uid,
None => return,
};
let namespace = cr.namespace().unwrap_or_else(|| "default".into());

if let Some(cassettes) = self.cassettes.get_mut(&namespace) {
cassettes.remove(&id);
if cassettes.is_empty() {
self.cassettes.remove(&namespace);
}
}
}
}

impl CassetteDBInner {
fn find_component(&self, namespace: &str, cassette: Cassette<String>) -> Option<CassetteRef> {
let Cassette {
id,
component,
name,
group,
description,
priority,
} = cassette;

let scope = Scope {
namespace: namespace.into(),
name: component,
};
let component = self.components_scopes.get(&scope).copied()?;

Some(Cassette {
id,
component,
name,
group,
description,
priority,
})
}

fn insert_component(&mut self, cr: CassetteComponentCrd) {
let id = match cr.uid().and_then(|uid| uid.parse().ok()) {
Some(uid) => uid,
None => return,
};
let namespace = cr.namespace().unwrap_or_else(|| "default".into());
let name = cr.name_any();

let scope = Scope { namespace, name };

self.components.insert(id, cr);
self.components_scopes.insert(scope, id);
}

fn remove_component(&mut self, cr: CassetteComponentCrd) {
let id: Uuid = match cr.uid().and_then(|uid| uid.parse().ok()) {
Some(uid) => uid,
None => return,
};
let namespace = cr.namespace().unwrap_or_else(|| "default".into());
let name = cr.name_any();

let scope = Scope { namespace, name };

self.components.remove(&id);
self.components_scopes.remove(&scope);
}
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Scope {
namespace: String,
name: String,
}
Loading

0 comments on commit 072e50c

Please sign in to comment.