use bson::{Document, doc};
use derive_builder::Builder;
use derive_default_builder::DefaultBuilder;
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use strum::Display;
use typeshare::typeshare;
use crate::deserializers::{
file_contents_deserializer, option_file_contents_deserializer,
option_string_list_deserializer, string_list_deserializer,
};
use super::{
I64, ResourceTarget,
resource::{Resource, ResourceListItem, ResourceQuery},
};
#[typeshare]
pub type ResourceSyncListItem =
ResourceListItem<ResourceSyncListItemInfo>;
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ResourceSyncListItemInfo {
pub last_sync_ts: I64,
pub files_on_host: bool,
pub file_contents: bool,
pub managed: bool,
pub resource_path: Vec<String>,
pub linked_repo: String,
pub git_provider: String,
pub repo: String,
pub branch: String,
pub repo_link: String,
pub last_sync_hash: Option<String>,
pub last_sync_message: Option<String>,
pub state: ResourceSyncState,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Display,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum ResourceSyncState {
Syncing,
Pending,
Ok,
Failed,
#[default]
Unknown,
}
#[cfg(feature = "utoipa")]
#[derive(utoipa::ToSchema)]
#[schema(as = ResourceSync)]
pub struct ResourceSyncSchema(
#[schema(inline)] pub Resource<ResourceSyncConfig, ResourceSyncInfo>,
);
#[typeshare]
pub type ResourceSync =
Resource<ResourceSyncConfig, ResourceSyncInfo>;
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ResourceSyncInfo {
#[serde(default)]
pub last_sync_ts: I64,
pub last_sync_hash: Option<String>,
pub last_sync_message: Option<String>,
#[serde(default)]
pub resource_updates: Vec<ResourceDiff>,
#[serde(default)]
pub variable_updates: Vec<DiffData>,
#[serde(default)]
pub user_group_updates: Vec<DiffData>,
#[serde(default)]
pub pending_deploys: Vec<SyncDeployTarget>,
pub pending_error: Option<String>,
pub pending_deploy_error: Option<String>,
pub pending_hash: Option<String>,
pub pending_message: Option<String>,
#[serde(default)]
pub remote_contents: Vec<SyncFileContents>,
#[serde(default)]
pub remote_errors: Vec<SyncFileContents>,
}
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ResourceDiff {
pub target: ResourceTarget,
pub data: DiffData,
}
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "type", content = "data")]
pub enum DiffData {
Create {
#[serde(default)]
name: String,
proposed: String,
},
Update {
proposed: String,
current: String,
},
Delete {
current: String,
},
}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct SyncDeployTarget {
pub target: ResourceTarget,
pub reason: String,
pub after: Vec<ResourceTarget>,
}
#[typeshare(serialized_as = "Partial<ResourceSyncConfig>")]
pub type _PartialResourceSyncConfig = PartialResourceSyncConfig;
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize, Builder, Partial)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[partial_derive(Debug, Clone, Default, Serialize, Deserialize)]
#[diff_derive(Debug, Clone, Default, Serialize, Deserialize)]
#[partial(skip_serializing_none, from, diff)]
pub struct ResourceSyncConfig {
#[serde(default)]
#[builder(default)]
pub linked_repo: 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 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)]
#[builder(default)]
pub git_account: 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 files_on_host: bool,
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_string_list_deserializer"
))]
#[builder(default)]
pub resource_path: Vec<String>,
#[serde(default)]
#[builder(default)]
pub managed: bool,
#[serde(default)]
#[builder(default)]
pub delete: bool,
#[serde(default = "default_include_resources")]
#[builder(default = "default_include_resources()")]
#[partial_default(default_include_resources())]
pub include_resources: bool,
#[serde(default, deserialize_with = "string_list_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_string_list_deserializer"
))]
#[builder(default)]
pub match_tags: Vec<String>,
#[serde(default)]
#[builder(default)]
pub include_variables: bool,
#[serde(default)]
#[builder(default)]
pub include_user_groups: bool,
#[serde(default = "default_pending_alert")]
#[builder(default = "default_pending_alert()")]
#[partial_default(default_pending_alert())]
pub pending_alert: bool,
#[serde(default, deserialize_with = "file_contents_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_file_contents_deserializer"
))]
#[builder(default)]
pub file_contents: String,
}
impl ResourceSyncConfig {
pub fn builder() -> ResourceSyncConfigBuilder {
ResourceSyncConfigBuilder::default()
}
pub fn file_contents_empty(&self) -> bool {
self
.file_contents
.split('\n')
.map(str::trim)
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.count()
== 0
}
}
fn default_git_provider() -> String {
String::from("github.com")
}
fn default_git_https() -> bool {
true
}
fn default_branch() -> String {
String::from("main")
}
fn default_webhook_enabled() -> bool {
true
}
fn default_include_resources() -> bool {
true
}
fn default_pending_alert() -> bool {
true
}
impl Default for ResourceSyncConfig {
fn default() -> Self {
Self {
linked_repo: 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(),
resource_path: Default::default(),
files_on_host: Default::default(),
file_contents: Default::default(),
managed: Default::default(),
include_resources: default_include_resources(),
match_tags: Default::default(),
include_variables: Default::default(),
include_user_groups: Default::default(),
delete: Default::default(),
webhook_enabled: default_webhook_enabled(),
webhook_secret: Default::default(),
pending_alert: default_pending_alert(),
}
}
}
#[cfg(feature = "utoipa")]
impl utoipa::PartialSchema for PartialResourceSyncConfig {
fn schema()
-> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
utoipa::schema!(#[inline] std::collections::HashMap<String, serde_json::Value>).into()
}
}
#[cfg(feature = "utoipa")]
impl utoipa::ToSchema for PartialResourceSyncConfig {}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct SyncFileContents {
#[serde(default)]
pub resource_path: String,
pub path: String,
pub contents: String,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ResourceSyncActionState {
pub syncing: bool,
}
#[typeshare]
pub type ResourceSyncQuery =
ResourceQuery<ResourceSyncQuerySpecifics>;
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Default, DefaultBuilder,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ResourceSyncQuerySpecifics {
pub repos: Vec<String>,
}
impl super::resource::AddFilters for ResourceSyncQuerySpecifics {
fn add_filters(&self, filters: &mut Document) {
if !self.repos.is_empty() {
filters.insert("config.repo", doc! { "$in": &self.repos });
}
}
}