1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use std::collections::HashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::{StringOrBool, deserialize_string_or_bool_opt};
// ---------------------------------------------------------------------------
// CloudSmith publisher
// ---------------------------------------------------------------------------
/// Per-format distribution value. Accepts either a single distribution string
/// (`deb: "ubuntu/focal"`) or an array of distribution slugs
/// (`deb: ["ubuntu/focal", "ubuntu/jammy"]`) — the array form causes the
/// publisher to issue one upload per distribution slug.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum CloudSmithDistributions {
/// Single distribution slug (`"ubuntu/focal"`).
Single(String),
/// Multiple distribution slugs; the publisher uploads once per entry.
Multiple(Vec<String>),
}
impl CloudSmithDistributions {
/// Materialize as a `Vec<&str>` regardless of which YAML form the user
/// wrote. A `Single` value yields a one-element vec so the caller can
/// always iterate.
pub fn to_str_vec(&self) -> Vec<&str> {
match self {
Self::Single(s) => vec![s.as_str()],
Self::Multiple(v) => v.iter().map(String::as_str).collect(),
}
}
}
/// CloudSmith publisher configuration.
/// Pushes packages to CloudSmith repositories.
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct CloudSmithConfig {
/// CloudSmith organization slug.
pub organization: Option<String>,
/// CloudSmith repository slug.
pub repository: Option<String>,
/// Build IDs filter: only publish artifacts from builds whose `id` is in this list.
pub ids: Option<Vec<String>>,
/// Package format filter: only publish artifacts matching these formats.
pub formats: Option<Vec<String>>,
/// Distribution mapping per format. Each entry accepts either a single
/// slug (`deb: "ubuntu/focal"`) or an array of slugs
/// (`deb: ["ubuntu/focal", "ubuntu/jammy"]`); the array form issues one
/// upload per entry.
pub distributions: Option<HashMap<String, CloudSmithDistributions>>,
/// Debian component name (e.g. "main").
pub component: Option<String>,
/// Environment variable name containing the CloudSmith API key.
pub secret_name: Option<String>,
/// Template-conditional skip: if rendered result is `"true"`, skip this publisher.
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
pub skip: Option<StringOrBool>,
/// When true, allow republishing over existing package versions.
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
pub republish: Option<StringOrBool>,
/// Override whether this publisher failing should fail the overall release.
///
/// Default: `false` — a failure here is logged but does not abort the release.
/// Set to `true` to fail the release on any error.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
/// Template-conditional gate: when the rendered result is falsy
/// (`"false"` / `"0"` / `"no"` / empty), the CloudSmith publisher is
/// skipped. Render failure hard-errors. Config key: `cloudsmiths[].if:`.
#[serde(rename = "if")]
pub if_condition: Option<String>,
/// When `true`, a triggered rollback leaves this publisher's work in
/// place rather than attempting to undo it. Default `false`.
pub retain_on_rollback: Option<bool>,
/// Retain only the `N` most-recent release versions of each published
/// package, pruning older ones from the CloudSmith repository after a
/// successful upload.
///
/// This is **opt-in** and **destructive**: leaving it unset (the default)
/// prunes nothing. When set, after the just-uploaded artifacts are
/// confirmed present the publisher lists every version of *this* package
/// in the repository, ranks the distinct release versions by SemVer
/// (newest first), keeps the top `N` — which always includes the version
/// just published — and issues `DELETE` for every artifact (all formats
/// and architectures) belonging to versions ranked beyond `N`. Other
/// packages sharing the repository are never touched.
///
/// All package formats of one release are treated as the same version:
/// the deb/rpm epoch (`1:0.9.1-1`) and apk revision (`0.9.1-r1`) suffixes
/// are normalized to the base SemVer (`0.9.1`) before ranking, so
/// keeping `2` versions keeps every `.deb`/`.rpm`/`.apk` of the two newest
/// releases.
///
/// Pruning is **best-effort**: it runs only after the upload (the real
/// work) has already succeeded, is skipped entirely in dry-run and
/// snapshot mode, and a list/delete failure emits a prominent warning and
/// continues rather than failing the release or rolling anything back.
/// `keep_versions: 0` is rejected — anodizer never prunes every version.
///
/// Primarily a remedy for storage-capped repositories (e.g. the
/// CloudSmith free plan's 500 MB limit, which offers no server-side
/// retention policy).
///
/// ```yaml
/// cloudsmiths:
/// - organization: acme
/// repository: tools
/// keep_versions: 3 # keep the 3 newest releases, prune older ones
/// ```
#[serde(default, skip_serializing_if = "Option::is_none")]
pub keep_versions: Option<u32>,
}