use bson::{doc, Document};
use derive_builder::Builder;
use derive_default_builder::DefaultBuilder;
use partial_derive2::Partial;
use serde::{
de::{value::MapAccessDeserializer, Visitor},
Deserialize, Deserializer, Serialize,
};
use strum::Display;
use typeshare::typeshare;
use crate::{
deserializers::{
env_vars_deserializer, labels_deserializer,
option_env_vars_deserializer, option_labels_deserializer,
option_string_list_deserializer, string_list_deserializer,
},
entities::I64,
};
use super::{
resource::{Resource, ResourceListItem, ResourceQuery},
NoData, SystemCommand, Version,
};
#[typeshare]
pub type Build = Resource<BuildConfig, BuildInfo>;
#[typeshare]
pub type BuildListItem = ResourceListItem<BuildListItemInfo>;
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildListItemInfo {
pub last_built_at: I64,
pub version: Version,
pub builder_id: String,
pub git_provider: String,
pub image_registry_domain: String,
pub repo: String,
pub branch: String,
pub state: BuildState,
pub built_hash: Option<String>,
pub latest_hash: Option<String>,
}
#[typeshare]
#[derive(
Debug, Clone, Copy, Default, Serialize, Deserialize, Display,
)]
pub enum BuildState {
Ok,
Failed,
Building,
#[default]
Unknown,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct BuildInfo {
pub last_built_at: I64,
pub built_hash: Option<String>,
pub built_message: Option<String>,
pub latest_hash: Option<String>,
pub latest_message: Option<String>,
}
#[typeshare(serialized_as = "Partial<BuildConfig>")]
pub type _PartialBuildConfig = PartialBuildConfig;
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize, Builder, Partial)]
#[partial_derive(Debug, Clone, Default, Serialize, Deserialize)]
#[partial(skip_serializing_none, from, diff)]
pub struct BuildConfig {
#[serde(default, alias = "builder")]
#[partial_attr(serde(alias = "builder"))]
#[builder(default)]
pub builder_id: String,
#[serde(default)]
#[builder(default)]
pub version: Version,
#[serde(default = "default_auto_increment_version")]
#[builder(default = "default_auto_increment_version()")]
#[partial_default(default_auto_increment_version())]
pub auto_increment_version: bool,
#[serde(default)]
#[builder(default)]
pub image_name: String,
#[serde(default)]
#[builder(default)]
pub image_tag: String,
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_string_list_deserializer"
))]
#[builder(default)]
pub links: Vec<String>,
#[serde(default = "default_git_provider")]
#[builder(default = "default_git_provider()")]
#[partial_default(default_git_provider())]
pub git_provider: String,
#[serde(default = "default_git_https")]
#[builder(default = "default_git_https()")]
#[partial_default(default_git_https())]
pub git_https: bool,
#[serde(default)]
#[builder(default)]
pub git_account: String,
#[serde(default)]
#[builder(default)]
pub repo: String,
#[serde(default = "default_branch")]
#[builder(default = "default_branch()")]
#[partial_default(default_branch())]
pub branch: String,
#[serde(default)]
#[builder(default)]
pub commit: String,
#[serde(default = "default_webhook_enabled")]
#[builder(default = "default_webhook_enabled()")]
#[partial_default(default_webhook_enabled())]
pub webhook_enabled: bool,
#[serde(default)]
#[builder(default)]
pub webhook_secret: String,
#[serde(default)]
#[builder(default)]
pub pre_build: SystemCommand,
#[serde(default = "default_build_path")]
#[builder(default = "default_build_path()")]
#[partial_default(default_build_path())]
pub build_path: String,
#[serde(default = "default_dockerfile_path")]
#[builder(default = "default_dockerfile_path()")]
#[partial_default(default_dockerfile_path())]
pub dockerfile_path: String,
#[serde(default, deserialize_with = "image_registry_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_image_registry_deserializer"
))]
#[builder(default)]
pub image_registry: ImageRegistryConfig,
#[serde(default)]
#[builder(default)]
pub skip_secret_interp: bool,
#[serde(default)]
#[builder(default)]
pub use_buildx: bool,
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_string_list_deserializer"
))]
#[builder(default)]
pub extra_args: Vec<String>,
#[serde(default, deserialize_with = "env_vars_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_env_vars_deserializer"
))]
#[builder(default)]
pub build_args: String,
#[serde(default, deserialize_with = "env_vars_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_env_vars_deserializer"
))]
#[builder(default)]
pub secret_args: String,
#[serde(default, deserialize_with = "labels_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_labels_deserializer"
))]
#[builder(default)]
pub labels: String,
}
impl BuildConfig {
pub fn builder() -> BuildConfigBuilder {
BuildConfigBuilder::default()
}
}
fn default_auto_increment_version() -> bool {
true
}
fn default_git_provider() -> String {
String::from("github.com")
}
fn default_git_https() -> bool {
true
}
fn default_branch() -> String {
String::from("main")
}
fn default_build_path() -> String {
String::from(".")
}
fn default_dockerfile_path() -> String {
String::from("Dockerfile")
}
fn default_webhook_enabled() -> bool {
true
}
impl Default for BuildConfig {
fn default() -> Self {
Self {
builder_id: Default::default(),
skip_secret_interp: Default::default(),
version: Default::default(),
auto_increment_version: default_auto_increment_version(),
image_name: Default::default(),
image_tag: Default::default(),
links: Default::default(),
git_provider: default_git_provider(),
git_https: default_git_https(),
repo: Default::default(),
branch: default_branch(),
commit: Default::default(),
git_account: Default::default(),
pre_build: Default::default(),
build_path: default_build_path(),
dockerfile_path: default_dockerfile_path(),
build_args: Default::default(),
secret_args: Default::default(),
labels: Default::default(),
extra_args: Default::default(),
use_buildx: Default::default(),
image_registry: Default::default(),
webhook_enabled: default_webhook_enabled(),
webhook_secret: Default::default(),
}
}
}
#[typeshare]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
)]
pub struct ImageRegistryConfig {
#[serde(default)]
pub domain: String,
#[serde(default)]
pub account: String,
#[serde(default)]
pub organization: String,
}
pub fn image_registry_deserializer<'de, D>(
deserializer: D,
) -> Result<ImageRegistryConfig, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(ImageRegistryVisitor)
}
pub fn option_image_registry_deserializer<'de, D>(
deserializer: D,
) -> Result<Option<ImageRegistryConfig>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(OptionImageRegistryVisitor)
}
struct ImageRegistryVisitor;
impl<'de> Visitor<'de> for ImageRegistryVisitor {
type Value = ImageRegistryConfig;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
write!(formatter, "{{ \"domain\": string, \"account\": string, \"organization\": string }}")
}
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let value = serde_json::Value::deserialize(
MapAccessDeserializer::new(map),
)?;
if let Ok(config) =
serde_json::from_value::<ImageRegistryLegacy1_14>(value.clone())
{
return Ok(config.into());
}
serde_json::from_value(value).map_err(serde::de::Error::custom)
}
}
struct OptionImageRegistryVisitor;
impl<'de> Visitor<'de> for OptionImageRegistryVisitor {
type Value = Option<ImageRegistryConfig>;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
write!(formatter, "null or {{ \"domain\": string, \"account\": string, \"organization\": string }}")
}
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
ImageRegistryVisitor.visit_map(map).map(Some)
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
}
#[typeshare]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "params")]
pub enum ImageRegistryLegacy1_14 {
None(NoData),
Standard(ImageRegistryConfig),
}
impl Default for ImageRegistryLegacy1_14 {
fn default() -> Self {
Self::None(NoData {})
}
}
impl From<ImageRegistryLegacy1_14> for ImageRegistryConfig {
fn from(value: ImageRegistryLegacy1_14) -> Self {
match value {
ImageRegistryLegacy1_14::None(_) => {
ImageRegistryConfig::default()
}
ImageRegistryLegacy1_14::Standard(mut config) => {
if config.domain.is_empty() {
config.domain = String::from("docker.io");
}
config
}
}
}
}
#[typeshare]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct BuildActionState {
pub building: bool,
}
#[typeshare]
pub type BuildQuery = ResourceQuery<BuildQuerySpecifics>;
#[typeshare]
#[derive(
Debug, Clone, Default, Serialize, Deserialize, DefaultBuilder,
)]
pub struct BuildQuerySpecifics {
#[serde(default)]
pub builder_ids: Vec<String>,
#[serde(default)]
pub repos: Vec<String>,
#[serde(default)]
pub built_since: I64,
}
impl super::resource::AddFilters for BuildQuerySpecifics {
fn add_filters(&self, filters: &mut Document) {
if !self.builder_ids.is_empty() {
filters.insert(
"config.builder_id",
doc! { "$in": &self.builder_ids },
);
}
if !self.repos.is_empty() {
filters.insert("config.repo", doc! { "$in": &self.repos });
}
if self.built_since > 0 {
filters.insert(
"info.last_built_at",
doc! { "$gte": self.built_since },
);
}
}
}