1use std::{collections::BTreeMap, fmt};
3
4use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value};
6
7use super::{Fleet, FleetPage, Item};
8use crate::{Scope, http::Method, pagination::paginated, request::endpoint};
9
10#[derive(Clone, Debug, Serialize, Deserialize)]
11#[non_exhaustive]
12pub struct PackageRef {
13 pub name: String,
14 pub version: String,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub title: Option<String>,
17}
18
19impl PackageRef {
20 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
21 Self {
22 name: name.into(),
23 version: version.into(),
24 title: None,
25 }
26 }
27}
28
29#[derive(Clone, Deserialize, Serialize)]
34#[non_exhaustive]
35pub struct PackagePolicy {
36 pub id: String,
37 pub name: String,
38 pub namespace: String,
39 #[serde(default)]
40 pub description: Option<String>,
41 #[serde(default)]
42 pub enabled: Option<bool>,
43 pub revision: u64,
45 pub package: PackageRef,
46 #[serde(default)]
47 pub policy_ids: Vec<String>,
48 #[serde(default)]
50 pub inputs: Value,
51 #[serde(default)]
53 pub vars: Option<Value>,
54 #[serde(default)]
56 pub version: Option<String>,
57 #[serde(flatten)]
58 pub extra: Map<String, Value>,
59}
60
61impl PackagePolicy {
62 pub fn edit(&self) -> PackagePolicyEdit {
90 PackagePolicyEdit {
91 id: self.id.clone(),
92 name: self.name.clone(),
93 namespace: self.namespace.clone(),
94 description: self.description.clone(),
95 enabled: self.enabled,
96 package: self.package.clone(),
97 policy_ids: self.policy_ids.clone(),
98 inputs: editable_inputs(self.inputs.clone()),
99 vars: self.vars.clone(),
100 version: self.version.clone(),
101 full_format: self.inputs.is_array(),
102 }
103 }
104}
105
106fn editable_inputs(mut inputs: Value) -> Value {
107 if let Some(inputs) = inputs.as_array_mut() {
108 for input in inputs {
109 if let Some(input) = input.as_object_mut() {
110 input.remove("compiled_input");
111 }
112 }
113 }
114 inputs
115}
116
117impl fmt::Debug for PackagePolicy {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 f.debug_struct("PackagePolicy")
120 .field("id", &self.id)
121 .field("name", &self.name)
122 .field("namespace", &self.namespace)
123 .field("enabled", &self.enabled)
124 .field("revision", &self.revision)
125 .field("package", &self.package)
126 .field("policy_ids", &self.policy_ids)
127 .finish_non_exhaustive()
128 }
129}
130
131#[derive(Clone, Serialize)]
137pub struct PackagePolicyEdit {
138 #[serde(skip)]
139 id: String,
140 name: String,
141 namespace: String,
142 #[serde(skip_serializing_if = "Option::is_none")]
143 description: Option<String>,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 enabled: Option<bool>,
146 package: PackageRef,
147 policy_ids: Vec<String>,
148 #[serde(skip_serializing_if = "Value::is_null")]
149 inputs: Value,
150 #[serde(skip_serializing_if = "Option::is_none")]
151 vars: Option<Value>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 version: Option<String>,
154 #[serde(skip)]
155 full_format: bool,
156}
157
158impl PackagePolicyEdit {
159 pub fn name(mut self, name: impl Into<String>) -> Self {
160 self.name = name.into();
161 self
162 }
163
164 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
165 self.namespace = namespace.into();
166 self
167 }
168
169 pub fn description(mut self, description: impl Into<String>) -> Self {
170 self.description = Some(description.into());
171 self
172 }
173
174 pub fn enabled(mut self, enabled: bool) -> Self {
175 self.enabled = Some(enabled);
176 self
177 }
178
179 pub fn policy_ids<I: IntoIterator<Item = S>, S: Into<String>>(mut self, ids: I) -> Self {
181 self.policy_ids = ids.into_iter().map(Into::into).collect();
182 self
183 }
184
185 pub fn inputs(mut self, inputs: Value) -> Self {
188 self.inputs = editable_inputs(inputs);
189 self
190 }
191
192 pub fn vars(mut self, vars: Value) -> Self {
194 self.vars = Some(vars);
195 self
196 }
197}
198
199impl fmt::Debug for PackagePolicyEdit {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.debug_struct("PackagePolicyEdit")
202 .field("id", &self.id)
203 .field("name", &self.name)
204 .field("namespace", &self.namespace)
205 .field("policy_ids", &self.policy_ids)
206 .finish_non_exhaustive()
207 }
208}
209
210#[derive(Clone, Copy, Debug)]
212#[non_exhaustive]
213pub enum PackagePolicyUpdate<'p> {
214 Edit(&'p PackagePolicyEdit),
217 Replace {
221 id: &'p str,
222 policy: &'p NewPackagePolicy,
223 },
224}
225
226impl<'p> From<&'p PackagePolicyEdit> for PackagePolicyUpdate<'p> {
227 fn from(edit: &'p PackagePolicyEdit) -> Self {
228 Self::Edit(edit)
229 }
230}
231
232struct VarNames<'v>(&'v BTreeMap<String, Value>);
234
235impl fmt::Debug for VarNames<'_> {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 f.debug_set().entries(self.0.keys()).finish()
238 }
239}
240
241#[derive(Clone, Default, Serialize)]
244pub struct PolicyInput {
245 #[serde(skip_serializing_if = "Option::is_none")]
246 enabled: Option<bool>,
247 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
248 vars: BTreeMap<String, Value>,
249 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
250 streams: BTreeMap<String, PolicyStream>,
251}
252
253impl PolicyInput {
254 pub fn new() -> Self {
255 Self::default()
256 }
257
258 pub fn enabled(mut self, enabled: bool) -> Self {
259 self.enabled = Some(enabled);
260 self
261 }
262
263 pub fn var(mut self, name: impl Into<String>, value: Value) -> Self {
264 self.vars.insert(name.into(), value);
265 self
266 }
267
268 pub fn stream(mut self, name: impl Into<String>, stream: PolicyStream) -> Self {
269 self.streams.insert(name.into(), stream);
270 self
271 }
272}
273
274impl fmt::Debug for PolicyInput {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 f.debug_struct("PolicyInput")
277 .field("enabled", &self.enabled)
278 .field("vars", &VarNames(&self.vars))
279 .field("streams", &self.streams)
280 .finish()
281 }
282}
283
284#[derive(Clone, Default, Serialize)]
286pub struct PolicyStream {
287 #[serde(skip_serializing_if = "Option::is_none")]
288 enabled: Option<bool>,
289 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
290 vars: BTreeMap<String, Value>,
291}
292
293impl PolicyStream {
294 pub fn new() -> Self {
295 Self::default()
296 }
297
298 pub fn enabled(mut self, enabled: bool) -> Self {
299 self.enabled = Some(enabled);
300 self
301 }
302
303 pub fn var(mut self, name: impl Into<String>, value: Value) -> Self {
304 self.vars.insert(name.into(), value);
305 self
306 }
307}
308
309impl fmt::Debug for PolicyStream {
310 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311 f.debug_struct("PolicyStream")
312 .field("enabled", &self.enabled)
313 .field("vars", &VarNames(&self.vars))
314 .finish()
315 }
316}
317
318#[derive(Clone, Debug, Serialize)]
322pub struct NewPackagePolicy {
323 name: String,
324 namespace: String,
325 policy_ids: Vec<String>,
326 package: PackageRef,
327 inputs: BTreeMap<String, PolicyInput>,
328 #[serde(skip_serializing_if = "Option::is_none")]
329 description: Option<String>,
330}
331
332impl NewPackagePolicy {
333 pub fn new(name: impl Into<String>, namespace: impl Into<String>, package: PackageRef) -> Self {
334 Self {
335 name: name.into(),
336 namespace: namespace.into(),
337 policy_ids: Vec::new(),
338 package,
339 inputs: BTreeMap::new(),
340 description: None,
341 }
342 }
343
344 pub fn policy_id(mut self, id: impl Into<String>) -> Self {
346 self.policy_ids.push(id.into());
347 self
348 }
349
350 pub fn input(mut self, name: impl Into<String>, input: PolicyInput) -> Self {
352 self.inputs.insert(name.into(), input);
353 self
354 }
355
356 pub fn description(mut self, description: impl Into<String>) -> Self {
357 self.description = Some(description.into());
358 self
359 }
360
361 pub fn replacing<'p>(&'p self, id: &'p str) -> PackagePolicyUpdate<'p> {
365 PackagePolicyUpdate::Replace { id, policy: self }
366 }
367}
368
369impl<'a> Fleet<'a> {
370 pub fn find_package_policies(&self) -> FindPackagePolicies<'a> {
371 FindPackagePolicies(self.0.request(
372 Method::GET,
373 Scope::Space,
374 &["api", "fleet", "package_policies"],
375 ))
376 }
377
378 pub fn get_package_policy(&self, id: &str) -> GetPackagePolicy<'a> {
379 GetPackagePolicy(self.0.request(
380 Method::GET,
381 Scope::Space,
382 &["api", "fleet", "package_policies", id],
383 ))
384 }
385
386 pub fn create_package_policy<B: Serialize + ?Sized>(
390 &self,
391 policy: &B,
392 ) -> CreatePackagePolicy<'a> {
393 CreatePackagePolicy(
394 self.0
395 .request(
396 Method::POST,
397 Scope::Space,
398 &["api", "fleet", "package_policies"],
399 )
400 .param("format", "simplified")
401 .json(policy),
402 )
403 }
404
405 pub fn update_package_policy<'p>(
411 &self,
412 update: impl Into<PackagePolicyUpdate<'p>>,
413 ) -> UpdatePackagePolicy<'a> {
414 let update = update.into();
415 let id = match update {
416 PackagePolicyUpdate::Edit(edit) => edit.id.as_str(),
417 PackagePolicyUpdate::Replace { id, .. } => id,
418 };
419 let request = self.0.request(
420 Method::PUT,
421 Scope::Space,
422 &["api", "fleet", "package_policies", id],
423 );
424 UpdatePackagePolicy(match update {
425 PackagePolicyUpdate::Edit(edit) => {
426 let request = if edit.full_format && edit.inputs.is_array() {
427 request
428 } else {
429 request.invalid(
430 "package-policy edits require full inputs; fetch the policy with get_package_policy before editing it".into(),
431 )
432 };
433 request
434 .nonempty("version", edit.version.as_deref().unwrap_or_default())
435 .json(edit)
436 }
437 PackagePolicyUpdate::Replace { policy, .. } => {
438 request.param("format", "simplified").json(policy)
439 }
440 })
441 }
442
443 pub fn delete_package_policy(&self, id: &str) -> DeletePackagePolicy<'a> {
444 DeletePackagePolicy(self.0.request(
445 Method::DELETE,
446 Scope::Space,
447 &["api", "fleet", "package_policies", id],
448 ))
449 }
450}
451
452endpoint! {
453 FindPackagePolicies => FleetPage<PackagePolicy>
455}
456
457endpoint! {
458 GetPackagePolicy => Item<PackagePolicy>
460}
461
462endpoint! {
463 CreatePackagePolicy => Item<PackagePolicy>
465}
466
467endpoint! {
468 UpdatePackagePolicy => Item<PackagePolicy>
470}
471
472endpoint! {
473 DeletePackagePolicy => Value
475}
476
477impl DeletePackagePolicy<'_> {
478 pub fn force(self, force: bool) -> Self {
480 Self(self.0.param("force", force))
481 }
482}
483
484page_setters!(FindPackagePolicies);
485sort_setters!(FindPackagePolicies);
486paginated!(FindPackagePolicies => FleetPage<PackagePolicy>);