mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-11 00:01:21 +00:00
custom Vec<EnvVar>, Vec<Conversion> deserializers to support config them as string
This commit is contained in:
@@ -122,12 +122,24 @@ pub struct BuildConfig {
|
||||
pub dockerfile_path: String,
|
||||
|
||||
/// Docker build arguments
|
||||
#[serde(default)]
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::env_vars_deserializer"
|
||||
)]
|
||||
#[partial_attr(serde(
|
||||
deserialize_with = "super::option_env_vars_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub build_args: Vec<EnvironmentVar>,
|
||||
|
||||
/// Docker labels
|
||||
#[serde(default)]
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::env_vars_deserializer"
|
||||
)]
|
||||
#[partial_attr(serde(
|
||||
deserialize_with = "super::option_env_vars_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub labels: Vec<EnvironmentVar>,
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use bson::{doc, Document};
|
||||
use derive_builder::Builder;
|
||||
use derive_default_builder::DefaultBuilder;
|
||||
use derive_variants::EnumVariants;
|
||||
use partial_derive2::Partial;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{
|
||||
de::{value::SeqAccessDeserializer, Visitor},
|
||||
Deserialize, Deserializer, Serialize,
|
||||
};
|
||||
use strum::{Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
@@ -86,7 +90,13 @@ pub struct DeploymentConfig {
|
||||
|
||||
/// Labels attached to various termination signal options.
|
||||
/// Used to specify different shutdown functionality depending on the termination signal.
|
||||
#[serde(default = "default_term_signal_labels")]
|
||||
#[serde(
|
||||
default = "default_term_signal_labels",
|
||||
deserialize_with = "term_labels_deserializer"
|
||||
)]
|
||||
#[partial_attr(serde(
|
||||
deserialize_with = "option_term_labels_deserializer"
|
||||
))]
|
||||
#[builder(default = "default_term_signal_labels()")]
|
||||
#[partial_default(default_term_signal_labels())]
|
||||
pub term_signal_labels: Vec<TerminationSignalLabel>,
|
||||
@@ -105,23 +115,41 @@ pub struct DeploymentConfig {
|
||||
/// The container port mapping.
|
||||
/// Irrelevant if container network is `host`.
|
||||
/// Maps ports on host to ports on container.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "conversions_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
deserialize_with = "option_conversions_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub ports: Vec<Conversion>,
|
||||
|
||||
/// The container volume mapping.
|
||||
/// Maps files / folders on host to files / folders in container.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "conversions_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
deserialize_with = "option_conversions_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub volumes: Vec<Conversion>,
|
||||
|
||||
/// The environment variables passed to the container.
|
||||
#[serde(default)]
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::env_vars_deserializer"
|
||||
)]
|
||||
#[partial_attr(serde(
|
||||
deserialize_with = "super::option_env_vars_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub environment: Vec<EnvironmentVar>,
|
||||
|
||||
/// The docker labels given to the container.
|
||||
#[serde(default)]
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::env_vars_deserializer"
|
||||
)]
|
||||
#[partial_attr(serde(
|
||||
deserialize_with = "super::option_env_vars_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub labels: Vec<EnvironmentVar>,
|
||||
|
||||
@@ -244,7 +272,7 @@ impl Default for DeploymentImage {
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, PartialEq,
|
||||
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct Conversion {
|
||||
/// reference on the server.
|
||||
@@ -253,6 +281,144 @@ pub struct Conversion {
|
||||
pub container: String,
|
||||
}
|
||||
|
||||
pub fn conversions_from_str(
|
||||
value: &str,
|
||||
) -> anyhow::Result<Vec<Conversion>> {
|
||||
let res = value
|
||||
.split('\n')
|
||||
.map(|line| line.trim())
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.starts_with('#'))
|
||||
.map(|(i, line)| {
|
||||
let mut split = line.split('=');
|
||||
let local = split
|
||||
.next()
|
||||
.with_context(|| {
|
||||
format!("line {i} does not have 'local' key")
|
||||
})?
|
||||
.trim()
|
||||
.to_string();
|
||||
// remove trailing comments
|
||||
let mut container_split = split
|
||||
.next()
|
||||
.with_context(|| {
|
||||
format!("line {i} does not have 'container' key")
|
||||
})?
|
||||
.split('#');
|
||||
let container = container_split
|
||||
.next()
|
||||
.with_context(|| {
|
||||
format!("line {i} does not have 'container' key")
|
||||
})?
|
||||
.trim()
|
||||
.to_string();
|
||||
anyhow::Ok(Conversion { local, container })
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn conversions_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<Conversion>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(ConversionVisitor)
|
||||
}
|
||||
|
||||
pub fn option_conversions_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Vec<Conversion>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionConversionVisitor)
|
||||
}
|
||||
|
||||
struct ConversionVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ConversionVisitor {
|
||||
type Value = Vec<Conversion>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<Conversion>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
conversions_from_str(v)
|
||||
.map_err(|e| serde::de::Error::custom(format!("{e:#}")))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct ConversionInner {
|
||||
local: String,
|
||||
container: String,
|
||||
}
|
||||
|
||||
impl From<ConversionInner> for Conversion {
|
||||
fn from(value: ConversionInner) -> Self {
|
||||
Self {
|
||||
local: value.local,
|
||||
container: value.container,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let res = Vec::<ConversionInner>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionConversionVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionConversionVisitor {
|
||||
type Value = Option<Vec<Conversion>>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<Conversion>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
ConversionVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
ConversionVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// A summary of a docker container on a server.
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@@ -401,6 +567,141 @@ pub struct TerminationSignalLabel {
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
pub fn term_signal_labels_from_str(
|
||||
value: &str,
|
||||
) -> anyhow::Result<Vec<TerminationSignalLabel>> {
|
||||
let res = value
|
||||
.split('\n')
|
||||
.map(|line| line.trim())
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.starts_with('#'))
|
||||
.map(|(i, line)| {
|
||||
let mut split = line.split('=');
|
||||
let signal = split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have signal"))?
|
||||
.trim()
|
||||
.parse::<TerminationSignal>()
|
||||
.with_context(|| {
|
||||
format!("line {i} does not have valid signal")
|
||||
})?;
|
||||
// remove trailing comments
|
||||
let mut label_split = split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have label"))?
|
||||
.split('#');
|
||||
let label = label_split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have label"))?
|
||||
.trim()
|
||||
.to_string();
|
||||
anyhow::Ok(TerminationSignalLabel { signal, label })
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn term_labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<TerminationSignalLabel>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(TermSignalLabelVisitor)
|
||||
}
|
||||
|
||||
pub fn option_term_labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Vec<TerminationSignalLabel>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionTermSignalLabelVisitor)
|
||||
}
|
||||
|
||||
struct TermSignalLabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for TermSignalLabelVisitor {
|
||||
type Value = Vec<TerminationSignalLabel>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<TerminationSignalLabel>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
term_signal_labels_from_str(v)
|
||||
.map_err(|e| serde::de::Error::custom(format!("{e:#}")))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct TermSignalLabelInner {
|
||||
signal: TerminationSignal,
|
||||
label: String,
|
||||
}
|
||||
|
||||
impl From<TermSignalLabelInner> for TerminationSignalLabel {
|
||||
fn from(value: TermSignalLabelInner) -> Self {
|
||||
Self {
|
||||
signal: value.signal,
|
||||
label: value.label,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let res = Vec::<TermSignalLabelInner>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionTermSignalLabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionTermSignalLabelVisitor {
|
||||
type Value = Option<Vec<TerminationSignalLabel>>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<TerminationSignalLabel>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
TermSignalLabelVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
TermSignalLabelVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct DeploymentActionState {
|
||||
|
||||
@@ -6,8 +6,11 @@ use build::CloudRegistryConfig;
|
||||
use clap::Parser;
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
use serde::{
|
||||
de::{value::MapAccessDeserializer, Visitor},
|
||||
Deserialize, Serialize,
|
||||
de::{
|
||||
value::{MapAccessDeserializer, SeqAccessDeserializer},
|
||||
Visitor,
|
||||
},
|
||||
Deserialize, Deserializer, Serialize,
|
||||
};
|
||||
use serror::Serror;
|
||||
use strum::{AsRefStr, Display, EnumString};
|
||||
@@ -324,13 +327,145 @@ impl Version {
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, PartialEq,
|
||||
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct EnvironmentVar {
|
||||
pub variable: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
pub fn environment_vars_from_str(
|
||||
value: &str,
|
||||
) -> anyhow::Result<Vec<EnvironmentVar>> {
|
||||
let res = value
|
||||
.split('\n')
|
||||
.map(|line| line.trim())
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.starts_with('#'))
|
||||
.map(|(i, line)| {
|
||||
let mut split = line.split('=');
|
||||
let variable = split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have variable"))?
|
||||
.trim()
|
||||
.to_string();
|
||||
// remove trailing comments
|
||||
let mut value_split = split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have value"))?
|
||||
.split('#');
|
||||
let value = value_split
|
||||
.next()
|
||||
.with_context(|| format!("line {i} does not have value"))?
|
||||
.trim()
|
||||
.to_string();
|
||||
anyhow::Ok(EnvironmentVar { variable, value })
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn env_vars_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<EnvironmentVar>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(EnvironmentVarVisitor)
|
||||
}
|
||||
|
||||
pub fn option_env_vars_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Vec<EnvironmentVar>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionEnvVarVisitor)
|
||||
}
|
||||
|
||||
struct EnvironmentVarVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for EnvironmentVarVisitor {
|
||||
type Value = Vec<EnvironmentVar>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
environment_vars_from_str(v)
|
||||
.map_err(|e| serde::de::Error::custom(format!("{e:#}")))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct EnvironmentVarInner {
|
||||
variable: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl From<EnvironmentVarInner> for EnvironmentVar {
|
||||
fn from(value: EnvironmentVarInner) -> Self {
|
||||
Self {
|
||||
variable: value.variable,
|
||||
value: value.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let res = Vec::<EnvironmentVarInner>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionEnvVarVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionEnvVarVisitor {
|
||||
type Value = Option<Vec<EnvironmentVar>>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
EnvironmentVarVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
EnvironmentVarVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LatestCommit {
|
||||
pub hash: String,
|
||||
|
||||
Reference in New Issue
Block a user