Compare commits
93 Commits
Author | SHA1 | Date | |
---|---|---|---|
7ab7dc7a6f
|
|||
098f0033d0 | |||
5cf69eaf3a | |||
9fafe6d3aa | |||
74d196a45c | |||
192e0212ae | |||
dbe7d998ae | |||
1fca3c5b8e | |||
49d72a2677 | |||
8dc643f614 | |||
3150db8653 | |||
c454354a94 | |||
e06d55a593 | |||
483143d2cc | |||
ff16753ac9 | |||
117fb0bed8 | |||
187423d5f4
|
|||
34020d2939
|
|||
5b3432d6d8 | |||
52d931ce42 | |||
b92e352096 | |||
02cbcf336c | |||
8716e64c4c | |||
f911a5a27e | |||
a1841c28e8 | |||
e86ed49b65 | |||
fe80539ddd | |||
b70f3f4f72 | |||
29a98b5ef5 | |||
a9c81a8f1b | |||
3956366d28 | |||
943cfcef2e | |||
932959bc5c
|
|||
578e6486c2 | |||
65f145825a | |||
05d33f1fab | |||
1af78da2a2 | |||
5ca9997068 | |||
8ae66f1469 | |||
84a2a07173 | |||
634dcda630 | |||
f5013373ec | |||
f08acd5bab | |||
2724e43237 | |||
82173faf0f
|
|||
eaa51611d9
|
|||
fde0c09a1b
|
|||
4457b6fc02
|
|||
38e5919ea0
|
|||
42219228ed
|
|||
e19bea34c3
|
|||
19390304d8
|
|||
16a1b5eb50 | |||
e5db5d8a0f
|
|||
dfc4a4d0b6
|
|||
35d3168ae2
|
|||
ad9ca49f7c
|
|||
bb6331c5e5
|
|||
36aea1c05c
|
|||
c08dcb049d
|
|||
284648fe79
|
|||
77c6c96fb0
|
|||
b8fde5644c
|
|||
8f806474d1
|
|||
252d0852ae
|
|||
c2b13dab9f
|
|||
b24056354f
|
|||
f24f2706ae
|
|||
c609dc4dd5
|
|||
57137daa4e
|
|||
2f74098afe
|
|||
616d23c550
|
|||
4bbca20797
|
|||
6bb28cbfe3
|
|||
c4c71766d9
|
|||
22efa0fbe6
|
|||
984d1fd259
|
|||
74569c3b15
|
|||
22527aadc6
|
|||
d9ce9748b4
|
|||
c76601a695
|
|||
cdae730c6b
|
|||
b980ac949e | |||
6e8e63e5ee
|
|||
3e06479cda
|
|||
52007c82e0
|
|||
1aa8562509
|
|||
545439923f
|
|||
843139591c
|
|||
af57ef6cc8
|
|||
10a56ad690 | |||
|
6e6ce96855 | ||
32281c02bb
|
86
CHANGELOG.md
86
CHANGELOG.md
@@ -6,6 +6,92 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.2.0] - 2024-11-29
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- feat/add-postgres-database (#20)
|
||||||
|
|
||||||
|
feat: add postgres database
|
||||||
|
|
||||||
|
Signed-off-by: kjuulh <contact@kjuulh.io>
|
||||||
|
|
||||||
|
feat: add postgres and more templates
|
||||||
|
|
||||||
|
Signed-off-by: kjuulh <contact@kjuulh.io>
|
||||||
|
|
||||||
|
Reviewed-on: https://git.front.kjuulh.io/kjuulh/cuddle-clusters/pulls/20
|
||||||
|
Co-authored-by: kjuulh <contact@kjuulh.io>
|
||||||
|
Co-committed-by: kjuulh <contact@kjuulh.io>
|
||||||
|
|
||||||
|
- add support for raw reading of variables
|
||||||
|
- add replicas
|
||||||
|
- set interval down to a minute
|
||||||
|
- with native roots
|
||||||
|
- with explicit install
|
||||||
|
- with install
|
||||||
|
- with ring
|
||||||
|
- enable stuff
|
||||||
|
- update packages
|
||||||
|
- update packages
|
||||||
|
- add actual grpc service
|
||||||
|
- fix typo
|
||||||
|
- revert to actual label
|
||||||
|
- fix scheme
|
||||||
|
- trying with h2c service
|
||||||
|
- add grpc ports as well
|
||||||
|
- fix name
|
||||||
|
- rename
|
||||||
|
- remove suffix
|
||||||
|
- add ingress
|
||||||
|
- add ingress wip
|
||||||
|
- use lower case
|
||||||
|
- use actual service name
|
||||||
|
- cannot use lowe case
|
||||||
|
- update filename
|
||||||
|
- add namespace
|
||||||
|
- update env
|
||||||
|
- add crdb
|
||||||
|
- add webroots
|
||||||
|
- with tls
|
||||||
|
- use new tokio stream
|
||||||
|
- update client
|
||||||
|
- more debugging
|
||||||
|
- update
|
||||||
|
- update flux releaser
|
||||||
|
- actually set registry
|
||||||
|
- add option for releaser
|
||||||
|
- add slot for upload strategy
|
||||||
|
- add user variables to input
|
||||||
|
- without remove all
|
||||||
|
- plan -> base
|
||||||
|
- add sync
|
||||||
|
- add send
|
||||||
|
- use arc instead
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- *(deps)* update rust crate serde to v1.0.215
|
||||||
|
- *(deps)* update rust crate futures to v0.3.31
|
||||||
|
- *(deps)* update rust crate minijinja to v2.3.1
|
||||||
|
- *(deps)* update rust crate minijinja to v2.3.0
|
||||||
|
- *(deps)* update rust crate serde to v1.0.203
|
||||||
|
- only create vault secret template if actual secret found
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- *(deps)* update rust crate tracing to v0.1.41
|
||||||
|
- *(deps)* update all dependencies
|
||||||
|
- *(deps)* update rust crate clap to v4.5.20
|
||||||
|
- *(deps)* update rust crate clap to v4.5.19
|
||||||
|
- *(deps)* update rust crate clap to v4.5.18
|
||||||
|
- *(deps)* update rust crate anyhow to v1.0.89
|
||||||
|
- *(deps)* update rust crate anyhow to v1.0.88
|
||||||
|
- *(deps)* update all dependencies
|
||||||
|
- move crdb to own file
|
||||||
|
|
||||||
|
## [0.1.1] - 2024-05-25
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- remove deps
|
||||||
|
|
||||||
## [0.1.0] - 2024-05-25
|
## [0.1.0] - 2024-05-25
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
3285
Cargo.lock
generated
3285
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,8 @@ tracing = { version = "0.1", features = ["log"] }
|
|||||||
tracing-subscriber = { version = "0.3.18" }
|
tracing-subscriber = { version = "0.3.18" }
|
||||||
clap = { version = "4", features = ["derive", "env"] }
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
dotenv = { version = "0.15" }
|
dotenv = { version = "0.15" }
|
||||||
axum = { version = "0.7" }
|
|
||||||
|
flux-releaser = { git = "https://git.front.kjuulh.io/kjuulh/flux-releaser", branch = "main" }
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
|
@@ -11,24 +11,17 @@ tracing.workspace = true
|
|||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
dotenv.workspace = true
|
dotenv.workspace = true
|
||||||
axum.workspace = true
|
|
||||||
|
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
sqlx = { version = "0.7.3", features = [
|
|
||||||
"runtime-tokio",
|
|
||||||
"tls-rustls",
|
|
||||||
"postgres",
|
|
||||||
"uuid",
|
|
||||||
"time",
|
|
||||||
] }
|
|
||||||
uuid = { version = "1.7.0", features = ["v4"] }
|
uuid = { version = "1.7.0", features = ["v4"] }
|
||||||
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
|
|
||||||
serde_yaml = "0.9.34"
|
serde_yaml = "0.9.34"
|
||||||
tokio-stream = "0.1.15"
|
tokio-stream = { version = "0.1.15", features = ["full"] }
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
minijinja = "2.0.1"
|
minijinja = { version = "2.0.1", features = ["custom_syntax"] }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
|
||||||
|
flux-releaser.workspace = true
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "integration"
|
name = "integration"
|
||||||
path = "tests/tests.rs"
|
path = "tests/tests.rs"
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
pub mod cluster_vars;
|
pub mod cluster_vars;
|
||||||
|
pub mod crdb_database;
|
||||||
pub mod cuddle_vars;
|
pub mod cuddle_vars;
|
||||||
|
pub mod ingress;
|
||||||
|
pub mod postgres_database;
|
||||||
pub mod vault_secret;
|
pub mod vault_secret;
|
||||||
|
@@ -1,7 +1,24 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
|
use serde_yaml::Value;
|
||||||
|
|
||||||
use crate::Component;
|
use crate::Component;
|
||||||
|
|
||||||
|
use super::cuddle_vars::CuddleVariables;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum RawClusterVariable {
|
||||||
|
Map(RawClusterVariables),
|
||||||
|
List(RawClusterVariableList),
|
||||||
|
String(String),
|
||||||
|
Bool(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Default)]
|
||||||
|
pub struct RawClusterVariables(BTreeMap<String, RawClusterVariable>);
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Default)]
|
||||||
|
pub struct RawClusterVariableList(Vec<RawClusterVariable>);
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
pub enum ClusterVariable {
|
pub enum ClusterVariable {
|
||||||
String(String),
|
String(String),
|
||||||
@@ -17,6 +34,10 @@ pub struct ClusterVariables {
|
|||||||
env: ClusterEnv,
|
env: ClusterEnv,
|
||||||
name: String,
|
name: String,
|
||||||
namespace: String,
|
namespace: String,
|
||||||
|
replicas: u64,
|
||||||
|
|
||||||
|
raw: RawClusterVariables,
|
||||||
|
// raw: CuddleVariables,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -36,9 +57,10 @@ impl Component for ClusterVars {
|
|||||||
environment: &str,
|
environment: &str,
|
||||||
value: &serde_yaml::Value,
|
value: &serde_yaml::Value,
|
||||||
) -> Option<anyhow::Result<minijinja::Value>> {
|
) -> Option<anyhow::Result<minijinja::Value>> {
|
||||||
let mut vars = ClusterVariables::default();
|
let mut vars = ClusterVariables {
|
||||||
|
replicas: 3,
|
||||||
// TODO: actually extract values
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(mapping) = value.as_mapping() {
|
if let Some(mapping) = value.as_mapping() {
|
||||||
if let Some(env) = mapping.get("env") {
|
if let Some(env) = mapping.get("env") {
|
||||||
@@ -52,7 +74,14 @@ impl Component for ClusterVars {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(replicas) = mapping.get("replicas") {
|
||||||
|
if let Some(replicas) = replicas.as_u64() {
|
||||||
|
vars.replicas = replicas;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
vars.raw = value.into();
|
||||||
|
|
||||||
vars.name = environment.into();
|
vars.name = environment.into();
|
||||||
vars.namespace = environment.into();
|
vars.namespace = environment.into();
|
||||||
@@ -67,6 +96,11 @@ impl minijinja::value::Object for ClusterVariables {
|
|||||||
"env" => minijinja::Value::from_object(self.env.clone()),
|
"env" => minijinja::Value::from_object(self.env.clone()),
|
||||||
"name" => minijinja::Value::from_safe_string(self.name.clone()),
|
"name" => minijinja::Value::from_safe_string(self.name.clone()),
|
||||||
"namespace" => minijinja::Value::from_safe_string(self.namespace.clone()),
|
"namespace" => minijinja::Value::from_safe_string(self.namespace.clone()),
|
||||||
|
"replicas" => minijinja::Value::from_safe_string(self.replicas.to_string()),
|
||||||
|
"raw" => {
|
||||||
|
tracing::info!("returning raw: {:?}", self.raw);
|
||||||
|
minijinja::Value::from_object(self.raw.clone())
|
||||||
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,3 +126,95 @@ impl minijinja::value::Object for ClusterEnv {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&Value> for RawClusterVariables {
|
||||||
|
fn from(value: &Value) -> Self {
|
||||||
|
match value {
|
||||||
|
Value::Mapping(mapping) => RawClusterVariables(
|
||||||
|
mapping
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
(
|
||||||
|
k.as_str()
|
||||||
|
.expect("keys to always be valid strings")
|
||||||
|
.to_string(),
|
||||||
|
v.into(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
Value::Null => RawClusterVariables::default(),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Value> for RawClusterVariable {
|
||||||
|
fn from(value: &Value) -> Self {
|
||||||
|
match value {
|
||||||
|
Value::Null => Self::Map(RawClusterVariables::default()),
|
||||||
|
Value::Bool(b) => Self::Bool(*b),
|
||||||
|
Value::Number(number) => Self::String(number.to_string()),
|
||||||
|
Value::String(s) => Self::String(s.into()),
|
||||||
|
Value::Sequence(vec) => Self::List(RawClusterVariableList(
|
||||||
|
vec.iter().map(|i| i.into()).collect(),
|
||||||
|
)),
|
||||||
|
Value::Mapping(mapping) => Self::Map(RawClusterVariables(
|
||||||
|
mapping
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
(
|
||||||
|
k.as_str()
|
||||||
|
.expect("keys to always be valid strings")
|
||||||
|
.to_string(),
|
||||||
|
v.into(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)),
|
||||||
|
Value::Tagged(_tagged_value) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl minijinja::value::Object for RawClusterVariables {
|
||||||
|
fn get_value(self: &std::sync::Arc<Self>, key: &minijinja::Value) -> Option<minijinja::Value> {
|
||||||
|
self.0.get(key.as_str()?).map(|o| match o {
|
||||||
|
RawClusterVariable::Map(raw_cluster_variables) => {
|
||||||
|
minijinja::Value::from_object(raw_cluster_variables.clone())
|
||||||
|
}
|
||||||
|
RawClusterVariable::List(list) => minijinja::Value::from_object(list.clone()),
|
||||||
|
RawClusterVariable::String(s) => minijinja::Value::from_safe_string(s.clone()),
|
||||||
|
RawClusterVariable::Bool(b) => minijinja::Value::from_safe_string(b.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enumerate(self: &std::sync::Arc<Self>) -> minijinja::value::Enumerator {
|
||||||
|
minijinja::value::Enumerator::Values(
|
||||||
|
self.0
|
||||||
|
.keys()
|
||||||
|
.map(|key| minijinja::Value::from_safe_string(key.clone()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl minijinja::value::Object for RawClusterVariableList {
|
||||||
|
fn enumerate(self: &std::sync::Arc<Self>) -> minijinja::value::Enumerator {
|
||||||
|
minijinja::value::Enumerator::Values(
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.map(|i| match i {
|
||||||
|
RawClusterVariable::Map(raw_cluster_variables) => {
|
||||||
|
minijinja::Value::from_object(raw_cluster_variables.clone())
|
||||||
|
}
|
||||||
|
RawClusterVariable::List(list) => minijinja::Value::from_object(list.clone()),
|
||||||
|
RawClusterVariable::String(s) => minijinja::Value::from_safe_string(s.clone()),
|
||||||
|
RawClusterVariable::Bool(b) => {
|
||||||
|
minijinja::Value::from_safe_string(b.to_string())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
131
crates/cuddle-clusters/src/catalog/crdb_database.rs
Normal file
131
crates/cuddle-clusters/src/catalog/crdb_database.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use minijinja::{value::Object, Value};
|
||||||
|
|
||||||
|
use crate::{catalog::cuddle_vars::CuddleVariable, Component};
|
||||||
|
|
||||||
|
use super::cuddle_vars::{load_cuddle_file, CuddleVariables};
|
||||||
|
|
||||||
|
pub struct CockroachDB {
|
||||||
|
variables: CuddleVariables,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CockroachDB {
|
||||||
|
pub async fn new(path: &Path) -> anyhow::Result<Self> {
|
||||||
|
let variables = load_cuddle_file(path).await?;
|
||||||
|
|
||||||
|
Ok(Self { variables })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for CockroachDB {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"cuddle/crdb".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_value(
|
||||||
|
&self,
|
||||||
|
_environment: &str,
|
||||||
|
_value: &serde_yaml::Value,
|
||||||
|
) -> Option<anyhow::Result<minijinja::Value>> {
|
||||||
|
if let Some(true) = self
|
||||||
|
.variables
|
||||||
|
.0
|
||||||
|
.get("database")
|
||||||
|
.and_then(|v| match v {
|
||||||
|
CuddleVariable::Object(o) => Some(o),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.and_then(|o| o.0.get("crdb"))
|
||||||
|
.and_then(|o| match o {
|
||||||
|
CuddleVariable::String(o) => {
|
||||||
|
if o == "true" {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Some(Ok(minijinja::Value::from_object(CockroachDBValues {
|
||||||
|
name: self.name(),
|
||||||
|
enabled: true,
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Ok(minijinja::Value::from_object(CockroachDBValues {
|
||||||
|
name: self.name(),
|
||||||
|
enabled: false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&self,
|
||||||
|
_environment: &str,
|
||||||
|
_value: &serde_yaml::Value,
|
||||||
|
) -> Option<anyhow::Result<(String, String)>> {
|
||||||
|
if let Some(true) = self
|
||||||
|
.variables
|
||||||
|
.0
|
||||||
|
.get("database")
|
||||||
|
.and_then(|v| match v {
|
||||||
|
CuddleVariable::Object(o) => Some(o),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.and_then(|o| o.0.get("crdb"))
|
||||||
|
.and_then(|o| match o {
|
||||||
|
CuddleVariable::String(o) => {
|
||||||
|
if o == "true" {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Some(Ok((
|
||||||
|
format!("{}.yaml", self.name().replace("/", "-")),
|
||||||
|
r#"
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ vars.cuddle_crdb.file_name(vars.cuddle_vars.service) }}
|
||||||
|
namespace: {{ vars.cluster_vars.namespace }}
|
||||||
|
data:
|
||||||
|
DATABASE_URL: postgresql://root@{{environment}}-cluster:26257/{{ vars.cuddle_vars.service | replace("-", "_") }}
|
||||||
|
"#
|
||||||
|
.into(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CockroachDBValues {
|
||||||
|
name: String,
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for CockroachDBValues {
|
||||||
|
fn get_value(self: &std::sync::Arc<Self>, key: &minijinja::Value) -> Option<minijinja::Value> {
|
||||||
|
let name = self.name.clone();
|
||||||
|
match key.as_str()? {
|
||||||
|
"has_values" => {
|
||||||
|
if self.enabled {
|
||||||
|
Some(minijinja::Value::from_serialize(true))
|
||||||
|
} else {
|
||||||
|
Some(minijinja::Value::from_serialize(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"file_name" => Some(Value::from_function(move |file_name: String| {
|
||||||
|
format!("{}-{}", file_name, name.replace("/", "-"))
|
||||||
|
})),
|
||||||
|
"env" => Some(Value::from_serialize("DATABASE_URL")),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -34,12 +34,13 @@ impl TryFrom<serde_yaml::Value> for CuddleVariable {
|
|||||||
serde_yaml::Value::String(s) => Ok(Self::String(s)),
|
serde_yaml::Value::String(s) => Ok(Self::String(s)),
|
||||||
serde_yaml::Value::Number(num) => Ok(Self::String(num.to_string())),
|
serde_yaml::Value::Number(num) => Ok(Self::String(num.to_string())),
|
||||||
serde_yaml::Value::Bool(bool) => Ok(Self::String(bool.to_string())),
|
serde_yaml::Value::Bool(bool) => Ok(Self::String(bool.to_string())),
|
||||||
|
serde_yaml::Value::Null => Ok(Self::Object(Box::default())),
|
||||||
_ => Err(anyhow::anyhow!("cannot handle type of serde value")),
|
_ => Err(anyhow::anyhow!("cannot handle type of serde value")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
#[derive(PartialEq, Eq, Debug, Clone, Default)]
|
||||||
pub struct CuddleVariables(pub HashMap<String, CuddleVariable>);
|
pub struct CuddleVariables(pub HashMap<String, CuddleVariable>);
|
||||||
|
|
||||||
impl TryFrom<serde_yaml::Mapping> for CuddleVariables {
|
impl TryFrom<serde_yaml::Mapping> for CuddleVariables {
|
||||||
@@ -68,7 +69,7 @@ impl TryFrom<serde_yaml::Value> for CuddleVariables {
|
|||||||
|
|
||||||
fn try_from(value: serde_yaml::Value) -> Result<Self, Self::Error> {
|
fn try_from(value: serde_yaml::Value) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
serde_yaml::Value::Null => anyhow::bail!("cannot handle null"),
|
serde_yaml::Value::Null => Ok(Self::default()),
|
||||||
serde_yaml::Value::Bool(_) => anyhow::bail!("cannot handle bool"),
|
serde_yaml::Value::Bool(_) => anyhow::bail!("cannot handle bool"),
|
||||||
serde_yaml::Value::Number(_) => anyhow::bail!("cannot handle number"),
|
serde_yaml::Value::Number(_) => anyhow::bail!("cannot handle number"),
|
||||||
serde_yaml::Value::String(_) => anyhow::bail!("cannot handle string"),
|
serde_yaml::Value::String(_) => anyhow::bail!("cannot handle string"),
|
||||||
@@ -84,7 +85,7 @@ pub struct CuddleVars {
|
|||||||
pub variables: CuddleVariables,
|
pub variables: CuddleVariables,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PARENT_PLAN_PREFIX: &str = ".cuddle/plan";
|
const PARENT_PLAN_PREFIX: &str = ".cuddle/base";
|
||||||
const CUDDLE_FILE: &str = "cuddle.yaml";
|
const CUDDLE_FILE: &str = "cuddle.yaml";
|
||||||
|
|
||||||
impl CuddleVars {
|
impl CuddleVars {
|
||||||
@@ -95,7 +96,7 @@ impl CuddleVars {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_cuddle_file(path: &Path) -> BoxFuture<'static, anyhow::Result<CuddleVariables>> {
|
pub fn load_cuddle_file(path: &Path) -> BoxFuture<'static, anyhow::Result<CuddleVariables>> {
|
||||||
let path = path.to_path_buf();
|
let path = path.to_path_buf();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
172
crates/cuddle-clusters/src/catalog/ingress.rs
Normal file
172
crates/cuddle-clusters/src/catalog/ingress.rs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use minijinja::{context, syntax::SyntaxConfig};
|
||||||
|
|
||||||
|
use crate::Component;
|
||||||
|
|
||||||
|
use super::cuddle_vars::{load_cuddle_file, CuddleVariable, CuddleVariables};
|
||||||
|
|
||||||
|
pub enum IngressType {
|
||||||
|
External,
|
||||||
|
Internal,
|
||||||
|
ExternalGrpc,
|
||||||
|
InternalGrpc,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Ingress {
|
||||||
|
variables: CuddleVariables,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ingress {
|
||||||
|
pub async fn new(path: &Path) -> anyhow::Result<Self> {
|
||||||
|
let variables = load_cuddle_file(path).await?;
|
||||||
|
|
||||||
|
Ok(Self { variables })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ingress_types(
|
||||||
|
&self,
|
||||||
|
ingress_types: Vec<IngressType>,
|
||||||
|
) -> anyhow::Result<(String, String)> {
|
||||||
|
let mut templates = Vec::new();
|
||||||
|
|
||||||
|
let internal_template = r#"
|
||||||
|
{%- set service_name = vars.cuddle_vars.service %}
|
||||||
|
{%- set host_name = vars.cuddle_vars.service | replace("_", "-") | replace(".", "-") %}
|
||||||
|
<%- macro host() -%>
|
||||||
|
<% if connection_type is defined %><<connection_type>>.<% endif %>{{ host_name }}.{{ environment }}.<< base_host >>
|
||||||
|
<%- endmacro %>
|
||||||
|
|
||||||
|
<%- macro k8s_service() -%>
|
||||||
|
<%- if connection_type == "grpc" -%>
|
||||||
|
{{ service_name }}-grpc
|
||||||
|
<%- else -%>
|
||||||
|
{{ service_name }}
|
||||||
|
<%- endif -%>
|
||||||
|
<%- endmacro %>
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/issuer: << issuer >>
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
labels:
|
||||||
|
app: {{ service_name }}
|
||||||
|
cluster: {{ vars.cluster_vars.name }}
|
||||||
|
name: {{ service_name }}-<< name >>
|
||||||
|
namespace: {{ vars.cluster_vars.namespace }}
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: << host() >>
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: << k8s_service() >>
|
||||||
|
port:
|
||||||
|
name: << name >>
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- << host() >>
|
||||||
|
secretName: tls-{{ service_name }}-<< issuer >>-<< name >>-ingress-dns
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let get_template = |name, base_host, connection_type| {
|
||||||
|
let mut env = minijinja::Environment::new();
|
||||||
|
env.set_syntax(
|
||||||
|
SyntaxConfig::builder()
|
||||||
|
.block_delimiters("<%", "%>")
|
||||||
|
.variable_delimiters("<<", ">>")
|
||||||
|
.comment_delimiters("<#", "#>")
|
||||||
|
.build()
|
||||||
|
.expect("to be able to build minijinja syntax"),
|
||||||
|
);
|
||||||
|
|
||||||
|
env.add_global("name", name);
|
||||||
|
env.add_global("base_host", base_host);
|
||||||
|
if let Some(connection_type) = connection_type {
|
||||||
|
env.add_global("connection_type", connection_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
env.add_global("issuer", "kjuulh-app");
|
||||||
|
|
||||||
|
env.render_named_str("ingress.yaml", internal_template, context! {})
|
||||||
|
};
|
||||||
|
|
||||||
|
for ingress_type in ingress_types {
|
||||||
|
match ingress_type {
|
||||||
|
IngressType::External => {
|
||||||
|
templates.push(get_template("external-http", "kjuulh.app", None)?)
|
||||||
|
}
|
||||||
|
IngressType::Internal => {
|
||||||
|
templates.push(get_template("internal-http", "internal.kjuulh.app", None)?)
|
||||||
|
}
|
||||||
|
IngressType::ExternalGrpc => {
|
||||||
|
templates.push(get_template("external-grpc", "kjuulh.app", Some("grpc"))?)
|
||||||
|
}
|
||||||
|
IngressType::InternalGrpc => templates.push(get_template(
|
||||||
|
"internal-grpc",
|
||||||
|
"internal.kjuulh.app",
|
||||||
|
Some("grpc"),
|
||||||
|
)?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(("ingress.yaml".into(), templates.join("\n")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Ingress {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"cuddle/ingress".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&self,
|
||||||
|
_environment: &str,
|
||||||
|
_value: &serde_yaml::Value,
|
||||||
|
) -> Option<anyhow::Result<(String, String)>> {
|
||||||
|
if let Some(ingress_types) = self
|
||||||
|
.variables
|
||||||
|
.0
|
||||||
|
.get("ingress")
|
||||||
|
.and_then(|v| match v {
|
||||||
|
CuddleVariable::Array(a) => Some(a),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(|o| {
|
||||||
|
let mut types = Vec::new();
|
||||||
|
|
||||||
|
for value in o {
|
||||||
|
match value {
|
||||||
|
CuddleVariable::Object(o) => {
|
||||||
|
if o.0.contains_key("external") {
|
||||||
|
types.push(IngressType::External)
|
||||||
|
} else if o.0.contains_key("internal") {
|
||||||
|
types.push(IngressType::Internal)
|
||||||
|
} else if o.0.contains_key("external_grpc") {
|
||||||
|
types.push(IngressType::ExternalGrpc)
|
||||||
|
} else if o.0.contains_key("internal_grpc") {
|
||||||
|
types.push(IngressType::InternalGrpc)
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
types
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Some(self.render_ingress_types(ingress_types));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
146
crates/cuddle-clusters/src/catalog/postgres_database.rs
Normal file
146
crates/cuddle-clusters/src/catalog/postgres_database.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use minijinja::{value::Object, Value};
|
||||||
|
|
||||||
|
use crate::Component;
|
||||||
|
|
||||||
|
use super::cuddle_vars::{load_cuddle_file, CuddleVariable, CuddleVariables};
|
||||||
|
|
||||||
|
pub struct PostgresDatabase {
|
||||||
|
variables: CuddleVariables,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostgresDatabase {
|
||||||
|
pub async fn new(path: &Path) -> anyhow::Result<Self> {
|
||||||
|
let variables = load_cuddle_file(path).await?;
|
||||||
|
|
||||||
|
Ok(Self { variables })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for PostgresDatabase {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"cuddle/postgres".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_value(
|
||||||
|
&self,
|
||||||
|
environment: &str,
|
||||||
|
_value: &serde_yaml::Value,
|
||||||
|
) -> Option<anyhow::Result<minijinja::Value>> {
|
||||||
|
if let Some(true) = self
|
||||||
|
.variables
|
||||||
|
.0
|
||||||
|
.get("database")
|
||||||
|
.and_then(|v| match v {
|
||||||
|
CuddleVariable::Object(o) => Some(o),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.and_then(|o| o.0.get("postgres"))
|
||||||
|
.and_then(|o| match o {
|
||||||
|
CuddleVariable::String(o) => {
|
||||||
|
if o == "true" {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Some(Ok(minijinja::Value::from_object(PostgresDatabaseValues {
|
||||||
|
name: self.name(),
|
||||||
|
enabled: true,
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Ok(minijinja::Value::from_object(PostgresDatabaseValues {
|
||||||
|
name: self.name(),
|
||||||
|
enabled: false,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&self,
|
||||||
|
environment: &str,
|
||||||
|
_value: &serde_yaml::Value,
|
||||||
|
) -> Option<anyhow::Result<(String, String)>> {
|
||||||
|
if let Some(true) = self
|
||||||
|
.variables
|
||||||
|
.0
|
||||||
|
.get("database")
|
||||||
|
.and_then(|v| match v {
|
||||||
|
CuddleVariable::Object(o) => Some(o),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.and_then(|o| o.0.get("postgres"))
|
||||||
|
.and_then(|o| match o {
|
||||||
|
CuddleVariable::String(o) => {
|
||||||
|
if o == "true" {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Some(Ok((
|
||||||
|
format!("{}.yaml", self.name().replace("/", "-")),
|
||||||
|
r#"
|
||||||
|
{%- if environment == "dev" %}
|
||||||
|
{%- set port = 5433 %}
|
||||||
|
{%- else %}
|
||||||
|
{%- set port = 5432 %}
|
||||||
|
{%- endif %}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ vars.cuddle_postgres.file_name(vars.cuddle_vars.service) }}
|
||||||
|
namespace: {{ vars.cluster_vars.namespace }}
|
||||||
|
data:
|
||||||
|
DATABASE_TYPE: postgresql
|
||||||
|
DATABASE_HOST: {{ environment }}.postgresql.kjuulh.app
|
||||||
|
DATABASE_PORT: "{{ port }}"
|
||||||
|
DATABASE_USER: {{ vars.cuddle_vars.service | replace("-", "_") }}
|
||||||
|
DATABASE_DB: {{ vars.cuddle_vars.service | replace("-", "_") }}
|
||||||
|
|
||||||
|
"#
|
||||||
|
.into(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PostgresDatabaseValues {
|
||||||
|
name: String,
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for PostgresDatabaseValues {
|
||||||
|
fn get_value(self: &std::sync::Arc<Self>, key: &minijinja::Value) -> Option<minijinja::Value> {
|
||||||
|
let name = self.name.clone();
|
||||||
|
match key.as_str()? {
|
||||||
|
"has_values" => {
|
||||||
|
if self.enabled {
|
||||||
|
Some(minijinja::Value::from_serialize(true))
|
||||||
|
} else {
|
||||||
|
Some(minijinja::Value::from_serialize(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"file_name" => Some(Value::from_function(move |file_name: String| {
|
||||||
|
format!("{}-{}", file_name, name.replace("/", "-"))
|
||||||
|
})),
|
||||||
|
"env" => Some(Value::from_serialize(vec![
|
||||||
|
"DATABASE_HOST",
|
||||||
|
"DATABASE_PORT",
|
||||||
|
"DATABASE_USER",
|
||||||
|
"DATABASE_DB",
|
||||||
|
])),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -68,17 +68,42 @@ impl Component for VaultSecret {
|
|||||||
return Some(Ok(minijinja::Value::from_object(vault_values)));
|
return Some(Ok(minijinja::Value::from_object(vault_values)));
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
Some(Ok(minijinja::Value::from_object(VaultSecretValues {
|
||||||
|
name: self.name().replace("/", "-"),
|
||||||
|
secrets: VaultSecretsLookup {
|
||||||
|
secrets: Vec::default(),
|
||||||
|
},
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
&self,
|
&self,
|
||||||
_environment: &str,
|
_environment: &str,
|
||||||
_value: &serde_yaml::Value,
|
value: &serde_yaml::Value,
|
||||||
) -> Option<anyhow::Result<(String, String)>> {
|
) -> Option<anyhow::Result<(String, String)>> {
|
||||||
Some(Ok((
|
value
|
||||||
format!("{}.yaml", self.name().replace("/", "_")),
|
.as_mapping()
|
||||||
r#"apiVersion: secrets.hashicorp.com/v1beta1
|
.and_then(|map| map.get("env"))
|
||||||
|
.and_then(|v| v.as_mapping())
|
||||||
|
.map(|v| {
|
||||||
|
v.iter()
|
||||||
|
.filter_map(|(k, v)| {
|
||||||
|
if v.as_mapping()
|
||||||
|
.map(|m| m.get("vault").filter(|v| v.as_bool() == Some(true)))
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
Some(k)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter_map(|k| k.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.map(|_| {
|
||||||
|
Ok((
|
||||||
|
format!("{}.yaml", self.name().replace("/", "_")),
|
||||||
|
r#"apiVersion: secrets.hashicorp.com/v1beta1
|
||||||
kind: VaultStaticSecret
|
kind: VaultStaticSecret
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ vars.vault_secret.file_name(vars.cuddle_vars.service) }}
|
name: {{ vars.vault_secret.file_name(vars.cuddle_vars.service) }}
|
||||||
@@ -92,8 +117,9 @@ spec:
|
|||||||
refreshAfter: 30s
|
refreshAfter: 30s
|
||||||
type: kv-v2
|
type: kv-v2
|
||||||
"#
|
"#
|
||||||
.into(),
|
.into(),
|
||||||
)))
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use std::rc::Rc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub trait Component {
|
pub trait Component {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
@@ -27,11 +27,11 @@ pub trait Component {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ConcreteComponent {
|
pub struct ConcreteComponent {
|
||||||
inner: Rc<dyn Component + 'static>,
|
inner: Arc<dyn Component + Sync + Send + 'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for ConcreteComponent {
|
impl std::ops::Deref for ConcreteComponent {
|
||||||
type Target = Rc<dyn Component + 'static>;
|
type Target = Arc<dyn Component + Sync + Send + 'static>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.inner
|
&self.inner
|
||||||
@@ -39,8 +39,8 @@ impl std::ops::Deref for ConcreteComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ConcreteComponent {
|
impl ConcreteComponent {
|
||||||
pub fn new<T: Component + 'static>(t: T) -> Self {
|
pub fn new<T: Component + Sync + Send + 'static>(t: T) -> Self {
|
||||||
Self { inner: Rc::new(t) }
|
Self { inner: Arc::new(t) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ impl IntoComponent for ConcreteComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Component + 'static> IntoComponent for T {
|
impl<T: Component + Sync + Send + 'static> IntoComponent for T {
|
||||||
fn into_component(self) -> ConcreteComponent {
|
fn into_component(self) -> ConcreteComponent {
|
||||||
ConcreteComponent::new(self)
|
ConcreteComponent::new(self)
|
||||||
}
|
}
|
||||||
|
@@ -7,3 +7,5 @@ pub mod catalog;
|
|||||||
|
|
||||||
pub mod process;
|
pub mod process;
|
||||||
pub use process::{process, process_opts};
|
pub use process::{process, process_opts};
|
||||||
|
|
||||||
|
pub mod releaser;
|
||||||
|
@@ -13,12 +13,32 @@ use tokio_stream::{wrappers::ReadDirStream, StreamExt};
|
|||||||
use crate::components::{ConcreteComponent, IntoComponent};
|
use crate::components::{ConcreteComponent, IntoComponent};
|
||||||
|
|
||||||
pub async fn process() -> anyhow::Result<()> {
|
pub async fn process() -> anyhow::Result<()> {
|
||||||
process_opts(Vec::<ConcreteComponent>::new(), ProcessOpts::default()).await
|
process_opts(
|
||||||
|
Vec::<ConcreteComponent>::new(),
|
||||||
|
ProcessOpts::default(),
|
||||||
|
None::<NoUploadStrategy>,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UploadStrategy {
|
||||||
|
fn upload(&self, input_path: &Path) -> BoxFuture<'_, anyhow::Result<()>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoUploadStrategy {}
|
||||||
|
|
||||||
|
impl UploadStrategy for NoUploadStrategy {
|
||||||
|
fn upload(&self, _input_path: &Path) -> BoxFuture<'_, anyhow::Result<()>> {
|
||||||
|
async move { Ok(()) }.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProcessOpts {
|
pub struct ProcessOpts {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub output: PathBuf,
|
pub output: PathBuf,
|
||||||
|
|
||||||
|
pub variables: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProcessOpts {
|
impl Default for ProcessOpts {
|
||||||
@@ -29,16 +49,18 @@ impl Default for ProcessOpts {
|
|||||||
.expect("to be able to get current dir")
|
.expect("to be able to get current dir")
|
||||||
.join("cuddle-clusters")
|
.join("cuddle-clusters")
|
||||||
.join("k8s"),
|
.join("k8s"),
|
||||||
|
variables: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TEMPLATES_PATH_PREFIX: &str = "templates/clusters";
|
const TEMPLATES_PATH_PREFIX: &str = "templates/clusters";
|
||||||
const CUDDLE_PLAN_PATH_PREFIX: &str = ".cuddle/plan";
|
const CUDDLE_PLAN_PATH_PREFIX: &str = ".cuddle/base";
|
||||||
|
|
||||||
pub async fn process_opts(
|
pub async fn process_opts(
|
||||||
components: impl IntoIterator<Item = impl IntoComponent>,
|
components: impl IntoIterator<Item = impl IntoComponent>,
|
||||||
opts: ProcessOpts,
|
opts: ProcessOpts,
|
||||||
|
upload_strategy: Option<impl UploadStrategy>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let components = components
|
let components = components
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -61,10 +83,21 @@ pub async fn process_opts(
|
|||||||
let template_files = load_template_files(&path).await?;
|
let template_files = load_template_files(&path).await?;
|
||||||
tracing::debug!("found files: {:?}", template_files);
|
tracing::debug!("found files: {:?}", template_files);
|
||||||
|
|
||||||
tokio::fs::remove_dir_all(&opts.output).await?;
|
let _ = tokio::fs::remove_dir_all(&opts.output).await;
|
||||||
tokio::fs::create_dir_all(&opts.output).await?;
|
tokio::fs::create_dir_all(&opts.output).await?;
|
||||||
|
|
||||||
process_templates(&components, &clusters, &template_files, &opts.output).await?;
|
process_templates(
|
||||||
|
&components,
|
||||||
|
&clusters,
|
||||||
|
&template_files,
|
||||||
|
&opts.output,
|
||||||
|
&opts.variables,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(upload_strategy) = upload_strategy {
|
||||||
|
upload_strategy.upload(&opts.output).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -184,6 +217,7 @@ async fn process_templates(
|
|||||||
clusters: &CuddleClusters,
|
clusters: &CuddleClusters,
|
||||||
template_files: &TemplateFiles,
|
template_files: &TemplateFiles,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
|
variables: &HashMap<String, String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
for (environment, value) in clusters.iter() {
|
for (environment, value) in clusters.iter() {
|
||||||
process_cluster(
|
process_cluster(
|
||||||
@@ -192,6 +226,7 @@ async fn process_templates(
|
|||||||
environment,
|
environment,
|
||||||
template_files,
|
template_files,
|
||||||
&dest.join(environment),
|
&dest.join(environment),
|
||||||
|
variables,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -205,9 +240,18 @@ async fn process_cluster(
|
|||||||
environment: &str,
|
environment: &str,
|
||||||
template_files: &TemplateFiles,
|
template_files: &TemplateFiles,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
|
variables: &HashMap<String, String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
for (_, template_file) in &template_files.templates {
|
for (_, template_file) in &template_files.templates {
|
||||||
process_template_file(components, value, environment, template_file, dest).await?;
|
process_template_file(
|
||||||
|
components,
|
||||||
|
value,
|
||||||
|
environment,
|
||||||
|
template_file,
|
||||||
|
dest,
|
||||||
|
variables,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (template_file_name, template_content) in components
|
for (template_file_name, template_content) in components
|
||||||
@@ -228,6 +272,7 @@ async fn process_cluster(
|
|||||||
&template_file_name,
|
&template_file_name,
|
||||||
&template_content,
|
&template_content,
|
||||||
dest,
|
dest,
|
||||||
|
variables,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -245,6 +290,7 @@ async fn process_template_file(
|
|||||||
environment: &str,
|
environment: &str,
|
||||||
template_file: &PathBuf,
|
template_file: &PathBuf,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
|
variables: &HashMap<String, String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let file = tokio::fs::read_to_string(template_file)
|
let file = tokio::fs::read_to_string(template_file)
|
||||||
.await
|
.await
|
||||||
@@ -260,6 +306,7 @@ async fn process_template_file(
|
|||||||
&file_name.to_string_lossy(),
|
&file_name.to_string_lossy(),
|
||||||
&file,
|
&file,
|
||||||
dest,
|
dest,
|
||||||
|
variables,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -273,6 +320,7 @@ async fn process_render_template(
|
|||||||
file_name: &str,
|
file_name: &str,
|
||||||
file_content: &str,
|
file_content: &str,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
|
user_vars: &HashMap<String, String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if !dest.exists() {
|
if !dest.exists() {
|
||||||
tokio::fs::create_dir_all(dest).await?;
|
tokio::fs::create_dir_all(dest).await?;
|
||||||
@@ -286,6 +334,7 @@ async fn process_render_template(
|
|||||||
env.add_global("environment", environment);
|
env.add_global("environment", environment);
|
||||||
|
|
||||||
let mut variables = HashMap::new();
|
let mut variables = HashMap::new();
|
||||||
|
|
||||||
for component in components {
|
for component in components {
|
||||||
let name = component.name();
|
let name = component.name();
|
||||||
|
|
||||||
@@ -295,12 +344,22 @@ async fn process_render_template(
|
|||||||
variables.insert(name.replace("/", "_"), value);
|
variables.insert(name.replace("/", "_"), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
variables.insert(
|
||||||
|
"user_vars".into(),
|
||||||
|
minijinja::Value::from_serialize(user_vars),
|
||||||
|
);
|
||||||
|
|
||||||
let tmpl = env.get_template(file_name)?;
|
let tmpl = env.get_template(file_name)?;
|
||||||
let rendered = tmpl.render(context! {
|
let rendered = tmpl.render(context! {
|
||||||
vars => variables
|
vars => variables
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let rendered = if rendered.is_empty() || rendered.ends_with("\n") {
|
||||||
|
rendered
|
||||||
|
} else {
|
||||||
|
format!("{rendered}\n")
|
||||||
|
};
|
||||||
|
|
||||||
dest_file.write_all(rendered.as_bytes()).await?;
|
dest_file.write_all(rendered.as_bytes()).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
108
crates/cuddle-clusters/src/releaser.rs
Normal file
108
crates/cuddle-clusters/src/releaser.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use flux_releaser::{
|
||||||
|
app::{LocalApp, SharedLocalApp},
|
||||||
|
services::flux_local_cluster::extensions::FluxLocalClusterManagerExt,
|
||||||
|
};
|
||||||
|
use futures::{future::BoxFuture, FutureExt};
|
||||||
|
|
||||||
|
use crate::process::UploadStrategy;
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct Releaser {
|
||||||
|
registry: String,
|
||||||
|
service: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Releaser {
|
||||||
|
pub fn with_registry(&mut self, registry: impl Into<String>) -> &mut Self {
|
||||||
|
self.registry = registry.into();
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_service(&mut self, service: impl Into<String>) -> &mut Self {
|
||||||
|
self.service = service.into();
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn release(&self, input_path: impl Into<PathBuf>) -> anyhow::Result<()> {
|
||||||
|
let input_path = input_path.into();
|
||||||
|
let branch = self.get_branch()?.ok_or(anyhow::anyhow!(
|
||||||
|
"failed to find branch, required for triggering release"
|
||||||
|
))?;
|
||||||
|
|
||||||
|
tracing::trace!("triggering release for: {}", input_path.display());
|
||||||
|
|
||||||
|
let local_app =
|
||||||
|
SharedLocalApp::new(LocalApp::new(&self.registry).await?).flux_local_cluster_manager();
|
||||||
|
|
||||||
|
let upload_id = local_app
|
||||||
|
.package_clusters(input_path)
|
||||||
|
.await
|
||||||
|
.context("failed to package clusters")?;
|
||||||
|
|
||||||
|
local_app
|
||||||
|
.commit_artifact(&self.service, &branch, upload_id)
|
||||||
|
.await
|
||||||
|
.context("failed to commit artifact")?;
|
||||||
|
|
||||||
|
local_app
|
||||||
|
.trigger_release(&self.service, &branch)
|
||||||
|
.await
|
||||||
|
.context("failed to trigger release")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_branch(&self) -> anyhow::Result<Option<String>> {
|
||||||
|
let output = Command::new("git")
|
||||||
|
.args(["rev-parse", "--abbrev-ref", "HEAD"])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
let branch = std::str::from_utf8(&output.stdout)?.trim().to_string();
|
||||||
|
Ok(Some(branch))
|
||||||
|
} else {
|
||||||
|
let err = std::str::from_utf8(&output.stderr)?;
|
||||||
|
anyhow::bail!("Failed to get branch name: {}", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UploadStrategy for Releaser {
|
||||||
|
fn upload(&self, input_path: &Path) -> BoxFuture<'_, anyhow::Result<()>> {
|
||||||
|
let input_path = input_path.to_path_buf();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
self.release(input_path).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::releaser::Releaser;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_upload_test() -> anyhow::Result<()> {
|
||||||
|
return Ok(());
|
||||||
|
let releaser = Releaser::default();
|
||||||
|
|
||||||
|
releaser
|
||||||
|
.release(
|
||||||
|
"/home/kjuulh/git/git.front.kjuulh.io/kjuulh/cuddle-rust-service-plan/.cuddle/tmp/cuddle-clusters",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,9 @@
|
|||||||
use std::{cmp::Ordering, path::Path};
|
use std::{cmp::Ordering, collections::HashMap, path::Path};
|
||||||
|
|
||||||
use cuddle_clusters::{process::ProcessOpts, ConcreteComponent, IntoComponent};
|
use cuddle_clusters::{
|
||||||
|
process::{NoUploadStrategy, ProcessOpts},
|
||||||
|
ConcreteComponent, IntoComponent,
|
||||||
|
};
|
||||||
use walkdir::DirEntry;
|
use walkdir::DirEntry;
|
||||||
|
|
||||||
pub(crate) async fn run_test_with_components(
|
pub(crate) async fn run_test_with_components(
|
||||||
@@ -27,7 +30,9 @@ pub(crate) async fn run_test_with_components(
|
|||||||
ProcessOpts {
|
ProcessOpts {
|
||||||
path: test_folder.clone(),
|
path: test_folder.clone(),
|
||||||
output: actual.clone(),
|
output: actual.clone(),
|
||||||
|
variables: HashMap::default(),
|
||||||
},
|
},
|
||||||
|
None::<NoUploadStrategy>,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -37,7 +42,9 @@ pub(crate) async fn run_test_with_components(
|
|||||||
ProcessOpts {
|
ProcessOpts {
|
||||||
path: test_folder,
|
path: test_folder,
|
||||||
output: expected.clone(),
|
output: expected.clone(),
|
||||||
|
variables: HashMap::default(),
|
||||||
},
|
},
|
||||||
|
None::<NoUploadStrategy>,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,10 @@ mod can_run_for_env;
|
|||||||
mod cuddle_vars;
|
mod cuddle_vars;
|
||||||
|
|
||||||
use cuddle_clusters::{
|
use cuddle_clusters::{
|
||||||
catalog::{cluster_vars::ClusterVars, cuddle_vars::CuddleVars, vault_secret::VaultSecret},
|
catalog::{
|
||||||
|
cluster_vars::ClusterVars, crdb_database::CockroachDB, cuddle_vars::CuddleVars,
|
||||||
|
ingress::Ingress, postgres_database::PostgresDatabase, vault_secret::VaultSecret,
|
||||||
|
},
|
||||||
IntoComponent,
|
IntoComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,17 +68,8 @@ async fn with_cuddle_vars() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn with_actual_deployment() -> anyhow::Result<()> {
|
async fn with_cluster_vars() -> anyhow::Result<()> {
|
||||||
let current_dir = std::env::current_dir()?.join("tests/with_cuddle_vars");
|
run_test_with_components("with_cluster_vars", vec![ClusterVars::default()]).await?;
|
||||||
|
|
||||||
run_test_with_components(
|
|
||||||
"with_actual_deployment",
|
|
||||||
vec![
|
|
||||||
CuddleVars::new(¤t_dir).await?.into_component(),
|
|
||||||
ClusterVars::default().into_component(),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -96,3 +90,56 @@ async fn with_vault_secrets() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn with_crdb() -> anyhow::Result<()> {
|
||||||
|
let current_dir = std::env::current_dir()?.join("tests/with_crdb");
|
||||||
|
|
||||||
|
run_test_with_components(
|
||||||
|
"with_crdb",
|
||||||
|
vec![
|
||||||
|
CuddleVars::new(¤t_dir).await?.into_component(),
|
||||||
|
ClusterVars::default().into_component(),
|
||||||
|
VaultSecret::default().into_component(),
|
||||||
|
CockroachDB::new(¤t_dir).await?.into_component(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn with_ingress() -> anyhow::Result<()> {
|
||||||
|
let current_dir = std::env::current_dir()?.join("tests/with_ingress");
|
||||||
|
|
||||||
|
run_test_with_components(
|
||||||
|
"with_ingress",
|
||||||
|
vec![
|
||||||
|
CuddleVars::new(¤t_dir).await?.into_component(),
|
||||||
|
ClusterVars::default().into_component(),
|
||||||
|
Ingress::new(¤t_dir).await?.into_component(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn with_postgres_databse() -> anyhow::Result<()> {
|
||||||
|
let current_dir = std::env::current_dir()?.join("tests/with_postgres_database");
|
||||||
|
|
||||||
|
run_test_with_components(
|
||||||
|
"with_postgres_database",
|
||||||
|
vec![
|
||||||
|
CuddleVars::new(¤t_dir).await?.into_component(),
|
||||||
|
ClusterVars::default().into_component(),
|
||||||
|
VaultSecret::default().into_component(),
|
||||||
|
PostgresDatabase::new(¤t_dir).await?.into_component(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@@ -1,11 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: {{ vars.cuddle_vars.service }}-config
|
|
||||||
data:
|
|
||||||
{%- if (vars.cluster_vars.env | items | length) > 0 %}
|
|
||||||
environment:
|
|
||||||
{%- for (name, value) in vars.cluster_vars.env | dictsort %}
|
|
||||||
{{name | upper | replace(".", "_") | replace("-", "_") }}: {{value}}
|
|
||||||
{%- endfor %}
|
|
||||||
{%- endif %}
|
|
@@ -1,15 +0,0 @@
|
|||||||
vars:
|
|
||||||
service: service
|
|
||||||
some:
|
|
||||||
nested:
|
|
||||||
item: something
|
|
||||||
array:
|
|
||||||
- item: item
|
|
||||||
|
|
||||||
|
|
||||||
cuddle/clusters:
|
|
||||||
dev:
|
|
||||||
env:
|
|
||||||
something: thing
|
|
||||||
nested.item: item
|
|
||||||
|
|
@@ -1,8 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: service-config
|
|
||||||
data:
|
|
||||||
environment:
|
|
||||||
NESTED_ITEM: item
|
|
||||||
SOMETHING: thing
|
|
16
crates/cuddle-clusters/tests/with_cluster_vars/cuddle.yaml
Normal file
16
crates/cuddle-clusters/tests/with_cluster_vars/cuddle.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
vars:
|
||||||
|
|
||||||
|
cuddle/clusters:
|
||||||
|
dev:
|
||||||
|
replicas: 1
|
||||||
|
list:
|
||||||
|
- listItem: listValue
|
||||||
|
env:
|
||||||
|
vault: true
|
||||||
|
something.something: something
|
||||||
|
something:
|
||||||
|
something: "something"
|
||||||
|
vault: true
|
||||||
|
prod:
|
||||||
|
env:
|
||||||
|
|
@@ -0,0 +1,9 @@
|
|||||||
|
name: dev
|
||||||
|
namespace: dev
|
||||||
|
replicas: 1
|
||||||
|
items:
|
||||||
|
- something.something
|
||||||
|
listValue
|
||||||
|
|
||||||
|
|
||||||
|
something: something
|
@@ -0,0 +1,5 @@
|
|||||||
|
name: prod
|
||||||
|
namespace: prod
|
||||||
|
replicas: 3
|
||||||
|
items:
|
||||||
|
|
@@ -0,0 +1,17 @@
|
|||||||
|
name: {{ vars.cluster_vars.name }}
|
||||||
|
namespace: {{ vars.cluster_vars.namespace }}
|
||||||
|
replicas: {{ vars.cluster_vars.replicas }}
|
||||||
|
items:
|
||||||
|
{%- for val in vars.cluster_vars.env %}
|
||||||
|
- {{val}}
|
||||||
|
{%- endfor %}
|
||||||
|
|
||||||
|
{%- if vars.cluster_vars.raw.list -%}
|
||||||
|
{%- for val in vars.cluster_vars.raw.list %}
|
||||||
|
{{ val.listItem }}
|
||||||
|
{%- endfor -%}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{% if vars.cluster_vars.raw.env.something %}
|
||||||
|
something: {{ vars.cluster_vars.raw.env.something.something }}
|
||||||
|
{% endif %}
|
7
crates/cuddle-clusters/tests/with_crdb/cuddle.yaml
Normal file
7
crates/cuddle-clusters/tests/with_crdb/cuddle.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
vars:
|
||||||
|
service: service
|
||||||
|
database:
|
||||||
|
crdb: "true"
|
||||||
|
|
||||||
|
cuddle/clusters:
|
||||||
|
dev:
|
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: service-cuddle-crdb
|
||||||
|
namespace: dev
|
||||||
|
data:
|
||||||
|
DATABASE_URL: postgresql://root@dev-cluster:26257/service
|
||||||
|
|
@@ -3,20 +3,16 @@ kind: Deployment
|
|||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: service
|
app: service
|
||||||
cluster: dev
|
|
||||||
name: service
|
name: service
|
||||||
namespace: dev
|
|
||||||
spec:
|
spec:
|
||||||
replicas: 3
|
replicas: 3
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: service
|
app: service
|
||||||
cluster: dev
|
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: service
|
app: service
|
||||||
cluster: dev
|
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- args:
|
- args:
|
||||||
@@ -28,6 +24,12 @@ spec:
|
|||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: service-config
|
name: service-config
|
||||||
|
env:
|
||||||
|
- name: DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: service-cuddle-crdb
|
||||||
|
key: DATABASE_URL
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
name: external-http
|
name: external-http
|
@@ -0,0 +1,54 @@
|
|||||||
|
{%- set service_name = vars.cuddle_vars.service -%}
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ service_name }}
|
||||||
|
name: {{ service_name }}
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ service_name }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ service_name }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- serve
|
||||||
|
command:
|
||||||
|
- {{ service_name }}
|
||||||
|
image: kasperhermansen/{{ service_name }}:main-1715336504
|
||||||
|
name: {{ service_name }}
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: {{service_name}}-config
|
||||||
|
{%- if vars.vault_secret.has_values or vars.cuddle_crdb.has_values %}
|
||||||
|
env:
|
||||||
|
{%- if vars.vault_secret.has_values %}
|
||||||
|
{%- for secret in vars.vault_secret.secrets %}
|
||||||
|
- name: {{secret | upper | replace(".", "_") | replace("-", "_") }}
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ vars.vault_secret.file_name(service_name) }}
|
||||||
|
key: {{ secret }}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- if vars.cuddle_crdb.has_values %}
|
||||||
|
- name: {{vars.cuddle_crdb.env }}
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ vars.cuddle_crdb.file_name(service_name) }}
|
||||||
|
key: {{ vars.cuddle_crdb.env }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
name: external-http
|
||||||
|
- containerPort: 3001
|
||||||
|
name: internal-http
|
||||||
|
- containerPort: 3002
|
||||||
|
name: internal-grpc
|
11
crates/cuddle-clusters/tests/with_ingress/cuddle.yaml
Normal file
11
crates/cuddle-clusters/tests/with_ingress/cuddle.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
vars:
|
||||||
|
service: service
|
||||||
|
ingress:
|
||||||
|
- external: "true"
|
||||||
|
- internal: "true"
|
||||||
|
- external_grpc: "true"
|
||||||
|
- internal_grpc: "true"
|
||||||
|
|
||||||
|
cuddle/clusters:
|
||||||
|
dev:
|
||||||
|
prod:
|
@@ -0,0 +1,121 @@
|
|||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/issuer: kjuulh-app
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
cluster: dev
|
||||||
|
name: service-external-http
|
||||||
|
namespace: dev
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: service.dev.kjuulh.app
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: service
|
||||||
|
port:
|
||||||
|
name: external-http
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- service.dev.kjuulh.app
|
||||||
|
secretName: tls-service-kjuulh-app-external-http-ingress-dns
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/issuer: kjuulh-app
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
cluster: dev
|
||||||
|
name: service-internal-http
|
||||||
|
namespace: dev
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: service.dev.internal.kjuulh.app
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: service
|
||||||
|
port:
|
||||||
|
name: internal-http
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- service.dev.internal.kjuulh.app
|
||||||
|
secretName: tls-service-kjuulh-app-internal-http-ingress-dns
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/issuer: kjuulh-app
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
cluster: dev
|
||||||
|
name: service-external-grpc
|
||||||
|
namespace: dev
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: grpc.service.dev.kjuulh.app
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: service-grpc
|
||||||
|
port:
|
||||||
|
name: external-grpc
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- grpc.service.dev.kjuulh.app
|
||||||
|
secretName: tls-service-kjuulh-app-external-grpc-ingress-dns
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/issuer: kjuulh-app
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
cluster: dev
|
||||||
|
name: service-internal-grpc
|
||||||
|
namespace: dev
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: grpc.service.dev.internal.kjuulh.app
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: service-grpc
|
||||||
|
port:
|
||||||
|
name: internal-grpc
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- grpc.service.dev.internal.kjuulh.app
|
||||||
|
secretName: tls-service-kjuulh-app-internal-grpc-ingress-dns
|
@@ -0,0 +1,30 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
name: service
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- serve
|
||||||
|
command:
|
||||||
|
- service
|
||||||
|
image: kasperhermansen/service:main-1715336504
|
||||||
|
name: service
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
name: external-http
|
||||||
|
- containerPort: 3001
|
||||||
|
name: internal-http
|
||||||
|
- containerPort: 3002
|
||||||
|
name: internal-grpc
|
@@ -0,0 +1,121 @@
|
|||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/issuer: kjuulh-app
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
cluster: prod
|
||||||
|
name: service-external-http
|
||||||
|
namespace: prod
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: service.prod.kjuulh.app
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: service
|
||||||
|
port:
|
||||||
|
name: external-http
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- service.prod.kjuulh.app
|
||||||
|
secretName: tls-service-kjuulh-app-external-http-ingress-dns
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/issuer: kjuulh-app
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
cluster: prod
|
||||||
|
name: service-internal-http
|
||||||
|
namespace: prod
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: service.prod.internal.kjuulh.app
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: service
|
||||||
|
port:
|
||||||
|
name: internal-http
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- service.prod.internal.kjuulh.app
|
||||||
|
secretName: tls-service-kjuulh-app-internal-http-ingress-dns
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/issuer: kjuulh-app
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
cluster: prod
|
||||||
|
name: service-external-grpc
|
||||||
|
namespace: prod
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: grpc.service.prod.kjuulh.app
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: service-grpc
|
||||||
|
port:
|
||||||
|
name: external-grpc
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- grpc.service.prod.kjuulh.app
|
||||||
|
secretName: tls-service-kjuulh-app-external-grpc-ingress-dns
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/issuer: kjuulh-app
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
cluster: prod
|
||||||
|
name: service-internal-grpc
|
||||||
|
namespace: prod
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: grpc.service.prod.internal.kjuulh.app
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: service-grpc
|
||||||
|
port:
|
||||||
|
name: internal-grpc
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- grpc.service.prod.internal.kjuulh.app
|
||||||
|
secretName: tls-service-kjuulh-app-internal-grpc-ingress-dns
|
@@ -0,0 +1,30 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
name: service
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- serve
|
||||||
|
command:
|
||||||
|
- service
|
||||||
|
image: kasperhermansen/service:main-1715336504
|
||||||
|
name: service
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
name: external-http
|
||||||
|
- containerPort: 3001
|
||||||
|
name: internal-http
|
||||||
|
- containerPort: 3002
|
||||||
|
name: internal-grpc
|
@@ -1,26 +1,20 @@
|
|||||||
{%- set service_name = vars.cuddle_vars.service -%}
|
{%- set service_name = vars.cuddle_vars.service -%}
|
||||||
{%- set cluster_name = vars.cluster_vars.name -%}
|
|
||||||
{%- set cluster_namespace = vars.cluster_vars.namespace -%}
|
|
||||||
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: {{ service_name }}
|
app: {{ service_name }}
|
||||||
cluster: {{ cluster_name }}
|
|
||||||
name: {{ service_name }}
|
name: {{ service_name }}
|
||||||
namespace: {{ cluster_namespace }}
|
|
||||||
spec:
|
spec:
|
||||||
replicas: 3
|
replicas: 3
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: {{ service_name }}
|
app: {{ service_name }}
|
||||||
cluster: {{ cluster_name }}
|
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: {{ service_name }}
|
app: {{ service_name }}
|
||||||
cluster: {{ cluster_name }}
|
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- args:
|
- args:
|
||||||
@@ -29,9 +23,6 @@ spec:
|
|||||||
- {{ service_name }}
|
- {{ service_name }}
|
||||||
image: kasperhermansen/{{ service_name }}:main-1715336504
|
image: kasperhermansen/{{ service_name }}:main-1715336504
|
||||||
name: {{ service_name }}
|
name: {{ service_name }}
|
||||||
envFrom:
|
|
||||||
- configMapRef:
|
|
||||||
name: {{service_name}}-config
|
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
name: external-http
|
name: external-http
|
@@ -0,0 +1,7 @@
|
|||||||
|
vars:
|
||||||
|
service: service
|
||||||
|
database:
|
||||||
|
postgres: "true"
|
||||||
|
|
||||||
|
cuddle/clusters:
|
||||||
|
dev:
|
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: service-cuddle-postgres
|
||||||
|
namespace: dev
|
||||||
|
data:
|
||||||
|
DATABASE_TYPE: postgresql
|
||||||
|
DATABASE_HOST: dev.postgresql.kjuulh.app
|
||||||
|
DATABASE_PORT: "5433"
|
||||||
|
DATABASE_USER: service
|
||||||
|
DATABASE_DB: service
|
@@ -0,0 +1,54 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
name: service
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: service
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: service
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- serve
|
||||||
|
command:
|
||||||
|
- service
|
||||||
|
image: kasperhermansen/service:main-1715336504
|
||||||
|
name: service
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: service-config
|
||||||
|
env:
|
||||||
|
- name: DATABASE_HOST
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: service-cuddle-postgres
|
||||||
|
key: DATABASE_HOST
|
||||||
|
- name: DATABASE_PORT
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: service-cuddle-postgres
|
||||||
|
key: DATABASE_PORT
|
||||||
|
- name: DATABASE_USER
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: service-cuddle-postgres
|
||||||
|
key: DATABASE_USER
|
||||||
|
- name: DATABASE_DB
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: service-cuddle-postgres
|
||||||
|
key: DATABASE_DB
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
name: external-http
|
||||||
|
- containerPort: 3001
|
||||||
|
name: internal-http
|
||||||
|
- containerPort: 3002
|
||||||
|
name: internal-grpc
|
@@ -0,0 +1,56 @@
|
|||||||
|
{%- set service_name = vars.cuddle_vars.service -%}
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ service_name }}
|
||||||
|
name: {{ service_name }}
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ service_name }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ service_name }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- args:
|
||||||
|
- serve
|
||||||
|
command:
|
||||||
|
- {{ service_name }}
|
||||||
|
image: kasperhermansen/{{ service_name }}:main-1715336504
|
||||||
|
name: {{ service_name }}
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: {{service_name}}-config
|
||||||
|
{%- if vars.vault_secret.has_values or vars.cuddle_postgres.has_values %}
|
||||||
|
env:
|
||||||
|
{%- if vars.vault_secret.has_values %}
|
||||||
|
{%- for secret in vars.vault_secret.secrets %}
|
||||||
|
- name: {{secret | upper | replace(".", "_") | replace("-", "_") }}
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ vars.vault_secret.file_name(service_name) }}
|
||||||
|
key: {{ secret }}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- if vars.cuddle_postgres.has_values %}
|
||||||
|
{%- for env in vars.cuddle_postgres.env %}
|
||||||
|
- name: {{ env }}
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ vars.cuddle_postgres.file_name(service_name) }}
|
||||||
|
key: {{ env }}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
name: external-http
|
||||||
|
- containerPort: 3001
|
||||||
|
name: internal-http
|
||||||
|
- containerPort: 3002
|
||||||
|
name: internal-grpc
|
Reference in New Issue
Block a user