Compare commits
No commits in common. "main" and "v0.1.1" have entirely different histories.
81
CHANGELOG.md
81
CHANGELOG.md
@ -6,87 +6,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [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
|
||||
|
3961
Cargo.lock
generated
3961
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,5 @@ tracing-subscriber = { version = "0.3.18" }
|
||||
clap = { version = "4", features = ["derive", "env"] }
|
||||
dotenv = { version = "0.15" }
|
||||
|
||||
flux-releaser = { git = "https://git.front.kjuulh.io/kjuulh/flux-releaser", branch = "main" }
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.0"
|
||||
version = "0.1.1"
|
||||
|
@ -17,11 +17,9 @@ uuid = { version = "1.7.0", features = ["v4"] }
|
||||
serde_yaml = "0.9.34"
|
||||
tokio-stream = { version = "0.1.15", features = ["full"] }
|
||||
walkdir = "2.5.0"
|
||||
minijinja = { version = "2.0.1", features = ["custom_syntax"] }
|
||||
minijinja = "2.0.1"
|
||||
futures = "0.3.30"
|
||||
|
||||
flux-releaser.workspace = true
|
||||
|
||||
[[test]]
|
||||
name = "integration"
|
||||
path = "tests/tests.rs"
|
||||
|
@ -1,6 +1,3 @@
|
||||
pub mod cluster_vars;
|
||||
pub mod crdb_database;
|
||||
pub mod cuddle_vars;
|
||||
pub mod ingress;
|
||||
pub mod postgres_database;
|
||||
pub mod vault_secret;
|
||||
|
@ -1,24 +1,7 @@
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use serde_yaml::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
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)]
|
||||
pub enum ClusterVariable {
|
||||
String(String),
|
||||
@ -34,10 +17,6 @@ pub struct ClusterVariables {
|
||||
env: ClusterEnv,
|
||||
name: String,
|
||||
namespace: String,
|
||||
replicas: u64,
|
||||
|
||||
raw: RawClusterVariables,
|
||||
// raw: CuddleVariables,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -57,10 +36,9 @@ impl Component for ClusterVars {
|
||||
environment: &str,
|
||||
value: &serde_yaml::Value,
|
||||
) -> Option<anyhow::Result<minijinja::Value>> {
|
||||
let mut vars = ClusterVariables {
|
||||
replicas: 3,
|
||||
..Default::default()
|
||||
};
|
||||
let mut vars = ClusterVariables::default();
|
||||
|
||||
// TODO: actually extract values
|
||||
|
||||
if let Some(mapping) = value.as_mapping() {
|
||||
if let Some(env) = mapping.get("env") {
|
||||
@ -74,14 +52,7 @@ 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.namespace = environment.into();
|
||||
@ -96,11 +67,6 @@ impl minijinja::value::Object for ClusterVariables {
|
||||
"env" => minijinja::Value::from_object(self.env.clone()),
|
||||
"name" => minijinja::Value::from_safe_string(self.name.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,
|
||||
};
|
||||
|
||||
@ -126,95 +92,3 @@ 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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,131 +0,0 @@
|
||||
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,13 +34,12 @@ impl TryFrom<serde_yaml::Value> for CuddleVariable {
|
||||
serde_yaml::Value::String(s) => Ok(Self::String(s)),
|
||||
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::Null => Ok(Self::Object(Box::default())),
|
||||
_ => Err(anyhow::anyhow!("cannot handle type of serde value")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Default)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct CuddleVariables(pub HashMap<String, CuddleVariable>);
|
||||
|
||||
impl TryFrom<serde_yaml::Mapping> for CuddleVariables {
|
||||
@ -69,7 +68,7 @@ impl TryFrom<serde_yaml::Value> for CuddleVariables {
|
||||
|
||||
fn try_from(value: serde_yaml::Value) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
serde_yaml::Value::Null => Ok(Self::default()),
|
||||
serde_yaml::Value::Null => anyhow::bail!("cannot handle null"),
|
||||
serde_yaml::Value::Bool(_) => anyhow::bail!("cannot handle bool"),
|
||||
serde_yaml::Value::Number(_) => anyhow::bail!("cannot handle number"),
|
||||
serde_yaml::Value::String(_) => anyhow::bail!("cannot handle string"),
|
||||
@ -85,7 +84,7 @@ pub struct CuddleVars {
|
||||
pub variables: CuddleVariables,
|
||||
}
|
||||
|
||||
const PARENT_PLAN_PREFIX: &str = ".cuddle/base";
|
||||
const PARENT_PLAN_PREFIX: &str = ".cuddle/plan";
|
||||
const CUDDLE_FILE: &str = "cuddle.yaml";
|
||||
|
||||
impl CuddleVars {
|
||||
@ -96,7 +95,7 @@ impl CuddleVars {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_cuddle_file(path: &Path) -> BoxFuture<'static, anyhow::Result<CuddleVariables>> {
|
||||
fn load_cuddle_file(path: &Path) -> BoxFuture<'static, anyhow::Result<CuddleVariables>> {
|
||||
let path = path.to_path_buf();
|
||||
|
||||
async move {
|
||||
|
@ -1,172 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
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,42 +68,17 @@ impl Component for VaultSecret {
|
||||
return Some(Ok(minijinja::Value::from_object(vault_values)));
|
||||
}
|
||||
|
||||
Some(Ok(minijinja::Value::from_object(VaultSecretValues {
|
||||
name: self.name().replace("/", "-"),
|
||||
secrets: VaultSecretsLookup {
|
||||
secrets: Vec::default(),
|
||||
},
|
||||
})))
|
||||
None
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
_environment: &str,
|
||||
value: &serde_yaml::Value,
|
||||
_value: &serde_yaml::Value,
|
||||
) -> Option<anyhow::Result<(String, String)>> {
|
||||
value
|
||||
.as_mapping()
|
||||
.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
|
||||
Some(Ok((
|
||||
format!("{}.yaml", self.name().replace("/", "_")),
|
||||
r#"apiVersion: secrets.hashicorp.com/v1beta1
|
||||
kind: VaultStaticSecret
|
||||
metadata:
|
||||
name: {{ vars.vault_secret.file_name(vars.cuddle_vars.service) }}
|
||||
@ -117,9 +92,8 @@ spec:
|
||||
refreshAfter: 30s
|
||||
type: kv-v2
|
||||
"#
|
||||
.into(),
|
||||
))
|
||||
})
|
||||
.into(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub trait Component {
|
||||
fn name(&self) -> String;
|
||||
@ -27,11 +27,11 @@ pub trait Component {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConcreteComponent {
|
||||
inner: Arc<dyn Component + Sync + Send + 'static>,
|
||||
inner: Rc<dyn Component + 'static>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ConcreteComponent {
|
||||
type Target = Arc<dyn Component + Sync + Send + 'static>;
|
||||
type Target = Rc<dyn Component + 'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
@ -39,8 +39,8 @@ impl std::ops::Deref for ConcreteComponent {
|
||||
}
|
||||
|
||||
impl ConcreteComponent {
|
||||
pub fn new<T: Component + Sync + Send + 'static>(t: T) -> Self {
|
||||
Self { inner: Arc::new(t) }
|
||||
pub fn new<T: Component + 'static>(t: T) -> Self {
|
||||
Self { inner: Rc::new(t) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ impl IntoComponent for ConcreteComponent {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component + Sync + Send + 'static> IntoComponent for T {
|
||||
impl<T: Component + 'static> IntoComponent for T {
|
||||
fn into_component(self) -> ConcreteComponent {
|
||||
ConcreteComponent::new(self)
|
||||
}
|
||||
|
@ -7,5 +7,3 @@ pub mod catalog;
|
||||
|
||||
pub mod process;
|
||||
pub use process::{process, process_opts};
|
||||
|
||||
pub mod releaser;
|
||||
|
@ -13,32 +13,12 @@ use tokio_stream::{wrappers::ReadDirStream, StreamExt};
|
||||
use crate::components::{ConcreteComponent, IntoComponent};
|
||||
|
||||
pub async fn process() -> anyhow::Result<()> {
|
||||
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()
|
||||
}
|
||||
process_opts(Vec::<ConcreteComponent>::new(), ProcessOpts::default()).await
|
||||
}
|
||||
|
||||
pub struct ProcessOpts {
|
||||
pub path: PathBuf,
|
||||
pub output: PathBuf,
|
||||
|
||||
pub variables: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Default for ProcessOpts {
|
||||
@ -49,18 +29,16 @@ impl Default for ProcessOpts {
|
||||
.expect("to be able to get current dir")
|
||||
.join("cuddle-clusters")
|
||||
.join("k8s"),
|
||||
variables: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TEMPLATES_PATH_PREFIX: &str = "templates/clusters";
|
||||
const CUDDLE_PLAN_PATH_PREFIX: &str = ".cuddle/base";
|
||||
const CUDDLE_PLAN_PATH_PREFIX: &str = ".cuddle/plan";
|
||||
|
||||
pub async fn process_opts(
|
||||
components: impl IntoIterator<Item = impl IntoComponent>,
|
||||
opts: ProcessOpts,
|
||||
upload_strategy: Option<impl UploadStrategy>,
|
||||
) -> anyhow::Result<()> {
|
||||
let components = components
|
||||
.into_iter()
|
||||
@ -83,21 +61,10 @@ pub async fn process_opts(
|
||||
let template_files = load_template_files(&path).await?;
|
||||
tracing::debug!("found files: {:?}", template_files);
|
||||
|
||||
let _ = tokio::fs::remove_dir_all(&opts.output).await;
|
||||
tokio::fs::remove_dir_all(&opts.output).await?;
|
||||
tokio::fs::create_dir_all(&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?;
|
||||
}
|
||||
process_templates(&components, &clusters, &template_files, &opts.output).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -217,7 +184,6 @@ async fn process_templates(
|
||||
clusters: &CuddleClusters,
|
||||
template_files: &TemplateFiles,
|
||||
dest: &Path,
|
||||
variables: &HashMap<String, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
for (environment, value) in clusters.iter() {
|
||||
process_cluster(
|
||||
@ -226,7 +192,6 @@ async fn process_templates(
|
||||
environment,
|
||||
template_files,
|
||||
&dest.join(environment),
|
||||
variables,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@ -240,18 +205,9 @@ async fn process_cluster(
|
||||
environment: &str,
|
||||
template_files: &TemplateFiles,
|
||||
dest: &Path,
|
||||
variables: &HashMap<String, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
for (_, template_file) in &template_files.templates {
|
||||
process_template_file(
|
||||
components,
|
||||
value,
|
||||
environment,
|
||||
template_file,
|
||||
dest,
|
||||
variables,
|
||||
)
|
||||
.await?;
|
||||
process_template_file(components, value, environment, template_file, dest).await?;
|
||||
}
|
||||
|
||||
for (template_file_name, template_content) in components
|
||||
@ -272,7 +228,6 @@ async fn process_cluster(
|
||||
&template_file_name,
|
||||
&template_content,
|
||||
dest,
|
||||
variables,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@ -290,7 +245,6 @@ async fn process_template_file(
|
||||
environment: &str,
|
||||
template_file: &PathBuf,
|
||||
dest: &Path,
|
||||
variables: &HashMap<String, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let file = tokio::fs::read_to_string(template_file)
|
||||
.await
|
||||
@ -306,7 +260,6 @@ async fn process_template_file(
|
||||
&file_name.to_string_lossy(),
|
||||
&file,
|
||||
dest,
|
||||
variables,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -320,7 +273,6 @@ async fn process_render_template(
|
||||
file_name: &str,
|
||||
file_content: &str,
|
||||
dest: &Path,
|
||||
user_vars: &HashMap<String, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
if !dest.exists() {
|
||||
tokio::fs::create_dir_all(dest).await?;
|
||||
@ -334,7 +286,6 @@ async fn process_render_template(
|
||||
env.add_global("environment", environment);
|
||||
|
||||
let mut variables = HashMap::new();
|
||||
|
||||
for component in components {
|
||||
let name = component.name();
|
||||
|
||||
@ -344,22 +295,12 @@ async fn process_render_template(
|
||||
variables.insert(name.replace("/", "_"), value);
|
||||
}
|
||||
}
|
||||
variables.insert(
|
||||
"user_vars".into(),
|
||||
minijinja::Value::from_serialize(user_vars),
|
||||
);
|
||||
|
||||
let tmpl = env.get_template(file_name)?;
|
||||
let rendered = tmpl.render(context! {
|
||||
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?;
|
||||
|
||||
Ok(())
|
||||
|
@ -1,108 +0,0 @@
|
||||
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(())
|
||||
}
|
||||
}
|
@ -3,4 +3,4 @@
|
||||
some = {
|
||||
thing = "some"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
use std::{cmp::Ordering, collections::HashMap, path::Path};
|
||||
use std::{cmp::Ordering, path::Path};
|
||||
|
||||
use cuddle_clusters::{
|
||||
process::{NoUploadStrategy, ProcessOpts},
|
||||
ConcreteComponent, IntoComponent,
|
||||
};
|
||||
use cuddle_clusters::{process::ProcessOpts, ConcreteComponent, IntoComponent};
|
||||
use walkdir::DirEntry;
|
||||
|
||||
pub(crate) async fn run_test_with_components(
|
||||
@ -30,9 +27,7 @@ pub(crate) async fn run_test_with_components(
|
||||
ProcessOpts {
|
||||
path: test_folder.clone(),
|
||||
output: actual.clone(),
|
||||
variables: HashMap::default(),
|
||||
},
|
||||
None::<NoUploadStrategy>,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -42,9 +37,7 @@ pub(crate) async fn run_test_with_components(
|
||||
ProcessOpts {
|
||||
path: test_folder,
|
||||
output: expected.clone(),
|
||||
variables: HashMap::default(),
|
||||
},
|
||||
None::<NoUploadStrategy>,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
env: dev
|
||||
env: dev
|
@ -1 +1 @@
|
||||
env: prod
|
||||
env: prod
|
@ -1 +1 @@
|
||||
some_file: 4
|
||||
some_file: 4
|
@ -1 +1 @@
|
||||
service
|
||||
service
|
@ -1 +1 @@
|
||||
service
|
||||
service
|
@ -1 +1 @@
|
||||
service
|
||||
service
|
@ -4,10 +4,7 @@ mod can_run_for_env;
|
||||
mod cuddle_vars;
|
||||
|
||||
use cuddle_clusters::{
|
||||
catalog::{
|
||||
cluster_vars::ClusterVars, crdb_database::CockroachDB, cuddle_vars::CuddleVars,
|
||||
ingress::Ingress, postgres_database::PostgresDatabase, vault_secret::VaultSecret,
|
||||
},
|
||||
catalog::{cluster_vars::ClusterVars, cuddle_vars::CuddleVars, vault_secret::VaultSecret},
|
||||
IntoComponent,
|
||||
};
|
||||
|
||||
@ -68,8 +65,17 @@ async fn with_cuddle_vars() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn with_cluster_vars() -> anyhow::Result<()> {
|
||||
run_test_with_components("with_cluster_vars", vec![ClusterVars::default()]).await?;
|
||||
async fn with_actual_deployment() -> anyhow::Result<()> {
|
||||
let current_dir = std::env::current_dir()?.join("tests/with_cuddle_vars");
|
||||
|
||||
run_test_with_components(
|
||||
"with_actual_deployment",
|
||||
vec![
|
||||
CuddleVars::new(¤t_dir).await?.into_component(),
|
||||
ClusterVars::default().into_component(),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -90,56 +96,3 @@ async fn with_vault_secrets() -> anyhow::Result<()> {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
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,20 +1,26 @@
|
||||
{%- set service_name = vars.cuddle_vars.service -%}
|
||||
{%- set cluster_name = vars.cluster_vars.name -%}
|
||||
{%- set cluster_namespace = vars.cluster_vars.namespace -%}
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ service_name }}
|
||||
cluster: {{ cluster_name }}
|
||||
name: {{ service_name }}
|
||||
namespace: {{ cluster_namespace }}
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ service_name }}
|
||||
cluster: {{ cluster_name }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ service_name }}
|
||||
cluster: {{ cluster_name }}
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
@ -23,6 +29,9 @@ spec:
|
||||
- {{ service_name }}
|
||||
image: kasperhermansen/{{ service_name }}:main-1715336504
|
||||
name: {{ service_name }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{service_name}}-config
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
name: external-http
|
@ -0,0 +1,15 @@
|
||||
vars:
|
||||
service: service
|
||||
some:
|
||||
nested:
|
||||
item: something
|
||||
array:
|
||||
- item: item
|
||||
|
||||
|
||||
cuddle/clusters:
|
||||
dev:
|
||||
env:
|
||||
something: thing
|
||||
nested.item: item
|
||||
|
@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: service-config
|
||||
data:
|
||||
environment:
|
||||
NESTED_ITEM: item
|
||||
SOMETHING: thing
|
@ -3,16 +3,20 @@ kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: service
|
||||
cluster: dev
|
||||
name: service
|
||||
namespace: dev
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: service
|
||||
cluster: dev
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: service
|
||||
cluster: dev
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
@ -24,16 +28,10 @@ spec:
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: service-config
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: service-cuddle-crdb
|
||||
key: DATABASE_URL
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
name: external-http
|
||||
- containerPort: 3001
|
||||
name: internal-http
|
||||
- containerPort: 3002
|
||||
name: internal-grpc
|
||||
name: internal-grpc
|
@ -1,16 +0,0 @@
|
||||
vars:
|
||||
|
||||
cuddle/clusters:
|
||||
dev:
|
||||
replicas: 1
|
||||
list:
|
||||
- listItem: listValue
|
||||
env:
|
||||
vault: true
|
||||
something.something: something
|
||||
something:
|
||||
something: "something"
|
||||
vault: true
|
||||
prod:
|
||||
env:
|
||||
|
@ -1,9 +0,0 @@
|
||||
name: dev
|
||||
namespace: dev
|
||||
replicas: 1
|
||||
items:
|
||||
- something.something
|
||||
listValue
|
||||
|
||||
|
||||
something: something
|
@ -1,5 +0,0 @@
|
||||
name: prod
|
||||
namespace: prod
|
||||
replicas: 3
|
||||
items:
|
||||
|
@ -1,17 +0,0 @@
|
||||
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 %}
|
@ -1,7 +0,0 @@
|
||||
vars:
|
||||
service: service
|
||||
database:
|
||||
crdb: "true"
|
||||
|
||||
cuddle/clusters:
|
||||
dev:
|
@ -1,9 +0,0 @@
|
||||
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: service-cuddle-crdb
|
||||
namespace: dev
|
||||
data:
|
||||
DATABASE_URL: postgresql://root@dev-cluster:26257/service
|
||||
|
@ -1,54 +0,0 @@
|
||||
{%- 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
|
@ -1,11 +0,0 @@
|
||||
vars:
|
||||
service: service
|
||||
ingress:
|
||||
- external: "true"
|
||||
- internal: "true"
|
||||
- external_grpc: "true"
|
||||
- internal_grpc: "true"
|
||||
|
||||
cuddle/clusters:
|
||||
dev:
|
||||
prod:
|
@ -1,121 +0,0 @@
|
||||
|
||||
|
||||
---
|
||||
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
|
@ -1,30 +0,0 @@
|
||||
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,121 +0,0 @@
|
||||
|
||||
|
||||
---
|
||||
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
|
@ -1,30 +0,0 @@
|
||||
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,7 +0,0 @@
|
||||
vars:
|
||||
service: service
|
||||
database:
|
||||
postgres: "true"
|
||||
|
||||
cuddle/clusters:
|
||||
dev:
|
@ -1,12 +0,0 @@
|
||||
|
||||
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
|
@ -1,54 +0,0 @@
|
||||
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
|
@ -1,56 +0,0 @@
|
||||
{%- 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
|
@ -36,4 +36,4 @@ spec:
|
||||
- containerPort: 3001
|
||||
name: internal-http
|
||||
- containerPort: 3002
|
||||
name: internal-grpc
|
||||
name: internal-grpc
|
@ -10,4 +10,4 @@ spec:
|
||||
mount: kvv2
|
||||
path: service/dev
|
||||
refreshAfter: 30s
|
||||
type: kv-v2
|
||||
type: kv-v2
|
Loading…
x
Reference in New Issue
Block a user