custom Vec<EnvVar>, Vec<Conversion> deserializers to support config them as string

This commit is contained in:
mbecker20
2024-06-10 14:39:51 -07:00
parent 145d933e63
commit 28dc030e2b
3 changed files with 460 additions and 12 deletions
+14 -2
View File
@@ -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>,
+308 -7
View File
@@ -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 {
+138 -3
View File
@@ -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,