use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::{HumanDuration, StringOrBool, deserialize_string_or_bool_opt, evaluate_if_condition};
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct NotarizeConfig {
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
pub skip: Option<StringOrBool>,
pub macos: Option<Vec<MacOSSignNotarizeConfig>>,
pub macos_native: Option<Vec<MacOSNativeSignNotarizeConfig>>,
}
#[derive(Debug, Clone, Serialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct MacOSSignNotarizeConfig {
pub ids: Option<Vec<String>>,
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
pub skip: Option<StringOrBool>,
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
pub enabled: Option<StringOrBool>,
#[serde(skip)]
pub skip_inverts_enabled: bool,
pub sign: Option<MacOSSignConfig>,
pub notarize: Option<MacOSNotarizeApiConfig>,
}
#[derive(Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct MacOSSignNotarizeConfigWire {
ids: Option<Vec<String>>,
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
skip: Option<StringOrBool>,
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
enabled: Option<StringOrBool>,
sign: Option<MacOSSignConfig>,
notarize: Option<MacOSNotarizeApiConfig>,
}
impl<'de> Deserialize<'de> for MacOSSignNotarizeConfig {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let wire = MacOSSignNotarizeConfigWire::deserialize(deserializer)?;
let ResolvedSkip {
skip,
inverts_enabled,
} = resolve_skip_with_enabled_alias(wire.skip, wire.enabled)
.map_err(serde::de::Error::custom)?;
Ok(Self {
ids: wire.ids,
skip,
enabled: None,
skip_inverts_enabled: inverts_enabled,
sign: wire.sign,
notarize: wire.notarize,
})
}
}
impl MacOSSignNotarizeConfig {
pub fn should_skip(
&self,
render: impl Fn(&str) -> anyhow::Result<String>,
) -> anyhow::Result<bool> {
resolve_notarize_skip(&self.skip, self.skip_inverts_enabled, render)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct MacOSSignConfig {
pub certificate: Option<String>,
pub password: Option<String>,
pub entitlements: Option<String>,
pub timestamp_url: Option<String>,
}
impl MacOSSignConfig {
pub const DEFAULT_TIMESTAMP_URL: &'static str = "http://timestamp.apple.com/ts01";
pub fn resolved_timestamp_url(&self) -> &str {
self.timestamp_url
.as_deref()
.map(|u| u.trim())
.filter(|u| !u.is_empty())
.unwrap_or(Self::DEFAULT_TIMESTAMP_URL)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct MacOSNotarizeApiConfig {
pub issuer_id: Option<String>,
pub key: Option<String>,
pub key_id: Option<String>,
pub timeout: Option<HumanDuration>,
pub wait: Option<bool>,
}
impl MacOSNotarizeApiConfig {
pub const DEFAULT_TIMEOUT: &'static str = "10m";
pub fn resolved_wait(&self) -> bool {
self.wait.unwrap_or(false)
}
pub fn resolved_timeout(&self) -> String {
self.timeout
.map(|d| d.as_humantime_string())
.unwrap_or_else(|| Self::DEFAULT_TIMEOUT.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum MacOSNativeArtifactKind {
Dmg,
Pkg,
}
#[derive(Debug, Clone, Serialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct MacOSNativeSignNotarizeConfig {
pub ids: Option<Vec<String>>,
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
pub skip: Option<StringOrBool>,
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
pub enabled: Option<StringOrBool>,
#[serde(skip)]
pub skip_inverts_enabled: bool,
#[serde(rename = "use")]
pub use_: Option<MacOSNativeArtifactKind>,
pub sign: Option<MacOSNativeSignConfig>,
pub notarize: Option<MacOSNativeNotarizeConfig>,
}
#[derive(Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct MacOSNativeSignNotarizeConfigWire {
ids: Option<Vec<String>>,
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
skip: Option<StringOrBool>,
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
enabled: Option<StringOrBool>,
#[serde(rename = "use")]
use_: Option<MacOSNativeArtifactKind>,
sign: Option<MacOSNativeSignConfig>,
notarize: Option<MacOSNativeNotarizeConfig>,
}
impl<'de> Deserialize<'de> for MacOSNativeSignNotarizeConfig {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let wire = MacOSNativeSignNotarizeConfigWire::deserialize(deserializer)?;
let ResolvedSkip {
skip,
inverts_enabled,
} = resolve_skip_with_enabled_alias(wire.skip, wire.enabled)
.map_err(serde::de::Error::custom)?;
Ok(Self {
ids: wire.ids,
skip,
enabled: None,
skip_inverts_enabled: inverts_enabled,
use_: wire.use_,
sign: wire.sign,
notarize: wire.notarize,
})
}
}
impl MacOSNativeSignNotarizeConfig {
pub fn should_skip(
&self,
render: impl Fn(&str) -> anyhow::Result<String>,
) -> anyhow::Result<bool> {
resolve_notarize_skip(&self.skip, self.skip_inverts_enabled, render)
}
}
struct ResolvedSkip {
skip: Option<StringOrBool>,
inverts_enabled: bool,
}
fn resolve_skip_with_enabled_alias(
skip: Option<StringOrBool>,
enabled: Option<StringOrBool>,
) -> Result<ResolvedSkip, String> {
match (skip, enabled) {
(Some(s), None) => Ok(ResolvedSkip {
skip: Some(s),
inverts_enabled: false,
}),
(None, Some(e)) => Ok(invert_enabled(e)),
(None, None) => Ok(ResolvedSkip {
skip: None,
inverts_enabled: false,
}),
(Some(s), Some(e)) => {
let resolved = invert_enabled(e);
if !resolved.inverts_enabled && string_or_bool_eq(&s, resolved.skip.as_ref()) {
Ok(ResolvedSkip {
skip: Some(s),
inverts_enabled: false,
})
} else {
Err(format!(
"notarize: both `skip:` and `enabled:` are set and disagree (`skip={:?}` / inverted `enabled={:?}`); use one or the other",
s, resolved.skip
))
}
}
}
}
fn invert_enabled(v: StringOrBool) -> ResolvedSkip {
match v {
StringOrBool::Bool(b) => ResolvedSkip {
skip: Some(StringOrBool::Bool(!b)),
inverts_enabled: false,
},
StringOrBool::String(s) => match s.trim() {
"true" => ResolvedSkip {
skip: Some(StringOrBool::Bool(false)),
inverts_enabled: false,
},
"false" => ResolvedSkip {
skip: Some(StringOrBool::Bool(true)),
inverts_enabled: false,
},
_ => ResolvedSkip {
skip: Some(StringOrBool::String(s)),
inverts_enabled: true,
},
},
}
}
fn resolve_notarize_skip(
skip: &Option<StringOrBool>,
inverts_enabled: bool,
render: impl Fn(&str) -> anyhow::Result<String>,
) -> anyhow::Result<bool> {
let Some(value) = skip else {
return Ok(false);
};
if inverts_enabled {
let enabled = evaluate_if_condition(Some(value.as_str()), "notarize: enabled", render)?;
Ok(!enabled)
} else {
value.try_evaluates_to_true(render)
}
}
fn string_or_bool_eq(a: &StringOrBool, b: Option<&StringOrBool>) -> bool {
match (a, b) {
(StringOrBool::Bool(a), Some(StringOrBool::Bool(b))) => a == b,
(StringOrBool::String(a), Some(StringOrBool::String(b))) => a == b,
_ => false,
}
}
impl MacOSNativeSignNotarizeConfig {
pub const DEFAULT_USE: MacOSNativeArtifactKind = MacOSNativeArtifactKind::Dmg;
pub fn resolved_use(&self) -> MacOSNativeArtifactKind {
self.use_.unwrap_or(Self::DEFAULT_USE)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct MacOSNativeSignConfig {
pub identity: Option<String>,
pub keychain: Option<String>,
pub options: Option<Vec<String>>,
pub entitlements: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct MacOSNativeNotarizeConfig {
pub profile_name: Option<String>,
pub wait: Option<bool>,
pub timeout: Option<HumanDuration>,
}
impl MacOSNativeNotarizeConfig {
pub const DEFAULT_TIMEOUT: &'static str = "10m";
pub fn resolved_wait(&self) -> bool {
self.wait.unwrap_or(false)
}
pub fn resolved_timeout(&self) -> String {
self.timeout
.map(|d| d.as_humantime_string())
.unwrap_or_else(|| Self::DEFAULT_TIMEOUT.to_string())
}
}