use std::{collections::BTreeMap, fmt};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use super::{Fleet, FleetPage, Item};
use crate::{Scope, http::Method, pagination::paginated, request::endpoint};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PackageRef {
pub name: String,
pub version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
impl PackageRef {
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
version: version.into(),
title: None,
}
}
}
#[derive(Clone, Deserialize, Serialize)]
#[non_exhaustive]
pub struct PackagePolicy {
pub id: String,
pub name: String,
pub namespace: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub enabled: Option<bool>,
pub revision: u64,
pub package: PackageRef,
#[serde(default)]
pub policy_ids: Vec<String>,
#[serde(default)]
pub inputs: Value,
#[serde(default)]
pub vars: Option<Value>,
#[serde(default)]
pub version: Option<String>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl PackagePolicy {
pub fn edit(&self) -> PackagePolicyEdit {
PackagePolicyEdit {
id: self.id.clone(),
name: self.name.clone(),
namespace: self.namespace.clone(),
description: self.description.clone(),
enabled: self.enabled,
package: self.package.clone(),
policy_ids: self.policy_ids.clone(),
inputs: editable_inputs(self.inputs.clone()),
vars: self.vars.clone(),
version: self.version.clone(),
full_format: self.inputs.is_array(),
}
}
}
fn editable_inputs(mut inputs: Value) -> Value {
if let Some(inputs) = inputs.as_array_mut() {
for input in inputs {
if let Some(input) = input.as_object_mut() {
input.remove("compiled_input");
}
}
}
inputs
}
impl fmt::Debug for PackagePolicy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PackagePolicy")
.field("id", &self.id)
.field("name", &self.name)
.field("namespace", &self.namespace)
.field("enabled", &self.enabled)
.field("revision", &self.revision)
.field("package", &self.package)
.field("policy_ids", &self.policy_ids)
.finish_non_exhaustive()
}
}
#[derive(Clone, Serialize)]
pub struct PackagePolicyEdit {
#[serde(skip)]
id: String,
name: String,
namespace: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
enabled: Option<bool>,
package: PackageRef,
policy_ids: Vec<String>,
#[serde(skip_serializing_if = "Value::is_null")]
inputs: Value,
#[serde(skip_serializing_if = "Option::is_none")]
vars: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
version: Option<String>,
#[serde(skip)]
full_format: bool,
}
impl PackagePolicyEdit {
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
self.namespace = namespace.into();
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = Some(enabled);
self
}
pub fn policy_ids<I: IntoIterator<Item = S>, S: Into<String>>(mut self, ids: I) -> Self {
self.policy_ids = ids.into_iter().map(Into::into).collect();
self
}
pub fn inputs(mut self, inputs: Value) -> Self {
self.inputs = editable_inputs(inputs);
self
}
pub fn vars(mut self, vars: Value) -> Self {
self.vars = Some(vars);
self
}
}
impl fmt::Debug for PackagePolicyEdit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PackagePolicyEdit")
.field("id", &self.id)
.field("name", &self.name)
.field("namespace", &self.namespace)
.field("policy_ids", &self.policy_ids)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum PackagePolicyUpdate<'p> {
Edit(&'p PackagePolicyEdit),
Replace {
id: &'p str,
policy: &'p NewPackagePolicy,
},
}
impl<'p> From<&'p PackagePolicyEdit> for PackagePolicyUpdate<'p> {
fn from(edit: &'p PackagePolicyEdit) -> Self {
Self::Edit(edit)
}
}
struct VarNames<'v>(&'v BTreeMap<String, Value>);
impl fmt::Debug for VarNames<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self.0.keys()).finish()
}
}
#[derive(Clone, Default, Serialize)]
pub struct PolicyInput {
#[serde(skip_serializing_if = "Option::is_none")]
enabled: Option<bool>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
vars: BTreeMap<String, Value>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
streams: BTreeMap<String, PolicyStream>,
}
impl PolicyInput {
pub fn new() -> Self {
Self::default()
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = Some(enabled);
self
}
pub fn var(mut self, name: impl Into<String>, value: Value) -> Self {
self.vars.insert(name.into(), value);
self
}
pub fn stream(mut self, name: impl Into<String>, stream: PolicyStream) -> Self {
self.streams.insert(name.into(), stream);
self
}
}
impl fmt::Debug for PolicyInput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PolicyInput")
.field("enabled", &self.enabled)
.field("vars", &VarNames(&self.vars))
.field("streams", &self.streams)
.finish()
}
}
#[derive(Clone, Default, Serialize)]
pub struct PolicyStream {
#[serde(skip_serializing_if = "Option::is_none")]
enabled: Option<bool>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
vars: BTreeMap<String, Value>,
}
impl PolicyStream {
pub fn new() -> Self {
Self::default()
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = Some(enabled);
self
}
pub fn var(mut self, name: impl Into<String>, value: Value) -> Self {
self.vars.insert(name.into(), value);
self
}
}
impl fmt::Debug for PolicyStream {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PolicyStream")
.field("enabled", &self.enabled)
.field("vars", &VarNames(&self.vars))
.finish()
}
}
#[derive(Clone, Debug, Serialize)]
pub struct NewPackagePolicy {
name: String,
namespace: String,
policy_ids: Vec<String>,
package: PackageRef,
inputs: BTreeMap<String, PolicyInput>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
impl NewPackagePolicy {
pub fn new(name: impl Into<String>, namespace: impl Into<String>, package: PackageRef) -> Self {
Self {
name: name.into(),
namespace: namespace.into(),
policy_ids: Vec::new(),
package,
inputs: BTreeMap::new(),
description: None,
}
}
pub fn policy_id(mut self, id: impl Into<String>) -> Self {
self.policy_ids.push(id.into());
self
}
pub fn input(mut self, name: impl Into<String>, input: PolicyInput) -> Self {
self.inputs.insert(name.into(), input);
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn replacing<'p>(&'p self, id: &'p str) -> PackagePolicyUpdate<'p> {
PackagePolicyUpdate::Replace { id, policy: self }
}
}
impl<'a> Fleet<'a> {
pub fn find_package_policies(&self) -> FindPackagePolicies<'a> {
FindPackagePolicies(self.0.request(
Method::GET,
Scope::Space,
&["api", "fleet", "package_policies"],
))
}
pub fn get_package_policy(&self, id: &str) -> GetPackagePolicy<'a> {
GetPackagePolicy(self.0.request(
Method::GET,
Scope::Space,
&["api", "fleet", "package_policies", id],
))
}
pub fn create_package_policy<B: Serialize + ?Sized>(
&self,
policy: &B,
) -> CreatePackagePolicy<'a> {
CreatePackagePolicy(
self.0
.request(
Method::POST,
Scope::Space,
&["api", "fleet", "package_policies"],
)
.param("format", "simplified")
.json(policy),
)
}
pub fn update_package_policy<'p>(
&self,
update: impl Into<PackagePolicyUpdate<'p>>,
) -> UpdatePackagePolicy<'a> {
let update = update.into();
let id = match update {
PackagePolicyUpdate::Edit(edit) => edit.id.as_str(),
PackagePolicyUpdate::Replace { id, .. } => id,
};
let request = self.0.request(
Method::PUT,
Scope::Space,
&["api", "fleet", "package_policies", id],
);
UpdatePackagePolicy(match update {
PackagePolicyUpdate::Edit(edit) => {
let request = if edit.full_format && edit.inputs.is_array() {
request
} else {
request.invalid(
"package-policy edits require full inputs; fetch the policy with get_package_policy before editing it".into(),
)
};
request
.nonempty("version", edit.version.as_deref().unwrap_or_default())
.json(edit)
}
PackagePolicyUpdate::Replace { policy, .. } => {
request.param("format", "simplified").json(policy)
}
})
}
pub fn delete_package_policy(&self, id: &str) -> DeletePackagePolicy<'a> {
DeletePackagePolicy(self.0.request(
Method::DELETE,
Scope::Space,
&["api", "fleet", "package_policies", id],
))
}
}
endpoint! {
FindPackagePolicies => FleetPage<PackagePolicy>
}
endpoint! {
GetPackagePolicy => Item<PackagePolicy>
}
endpoint! {
CreatePackagePolicy => Item<PackagePolicy>
}
endpoint! {
UpdatePackagePolicy => Item<PackagePolicy>
}
endpoint! {
DeletePackagePolicy => Value
}
impl DeletePackagePolicy<'_> {
pub fn force(self, force: bool) -> Self {
Self(self.0.param("force", force))
}
}
page_setters!(FindPackagePolicies);
sort_setters!(FindPackagePolicies);
paginated!(FindPackagePolicies => FleetPage<PackagePolicy>);