Workspace warning count: 408 → 105.
Three buckets cleared:
* Auto-fixable (`cargo fix` + `cargo clippy --fix`): unused imports
removed, unused variables prefixed with `_`, deprecated method
calls updated. Applied across harmony, harmony-k8s, harmony-agent,
harmony_inventory_agent, the fleet/ workspace, and ~15 examples.
* Generated code (opnsense-api/src/generated/): 269 snake_case
warnings + ~10 unreachable-pattern warnings come from
CamelCase-preserving bindings to OPNsense's HAProxy/Caddy XML
schemas. Scoped a single `#[allow(non_snake_case,
unreachable_patterns)]` at `pub mod generated;` rather than
fighting the codegen — renaming would break serde round-trips
and the codegen would regenerate them anyway.
* opnsense-codegen parser's defensive `let...else` guards on
`XmlNode` (currently single-variant): file-level
`#![allow(irrefutable_let_patterns)]` with a comment explaining
why we keep the `else` arms (they re-arm if the IR grows a
second variant).
`harmony_inventory_agent::local_presence::{DiscoveryEvent,
discover_agents}` re-exports were stripped twice by the auto-fix
passes (consumers live in another crate, so the local crate looks
"unused" to lint). Anchored with explicit `pub use` + an
`#[allow(unused_imports)]` annotation noting why.
All 151 harmony lib tests still pass. Remaining ~105 warnings are
mostly real dead code in non-fleet modules + a handful of
unused-imports/variables clippy couldn't auto-resolve; cleared in
the next pass.
140 lines
4.9 KiB
Rust
140 lines
4.9 KiB
Rust
//! Resource Bundle Pattern Implementation
|
|
//!
|
|
//! This module implements the Resource Bundle pattern for managing groups of
|
|
//! Kubernetes resources that form a logical unit of work.
|
|
//!
|
|
//! ## Purpose
|
|
//!
|
|
//! The ResourceBundle pattern addresses the need to manage ephemeral privileged
|
|
//! pods along with their platform-specific security requirements (e.g., OpenShift
|
|
//! Security Context Constraints).
|
|
//!
|
|
//! ## Use Cases
|
|
//!
|
|
//! - Writing files to node filesystems (e.g., NetworkManager configurations for
|
|
//! network bonding as described in ADR-019)
|
|
//! - Running privileged commands on nodes (e.g., reboots, system configuration)
|
|
//!
|
|
//! ## Benefits
|
|
//!
|
|
//! - **Separation of Concerns**: Client code doesn't need to know about
|
|
//! platform-specific RBAC requirements
|
|
//! - **Atomic Operations**: Resources are applied and deleted as a unit
|
|
//! - **Clean Abstractions**: Privileged operations are encapsulated in bundles
|
|
//! rather than scattered throughout client methods
|
|
//!
|
|
//! ## Example
|
|
//!
|
|
//! ```
|
|
//! use harmony_k8s::{K8sClient, helper};
|
|
//! use harmony_k8s::KubernetesDistribution;
|
|
//!
|
|
//! async fn write_network_config(client: &K8sClient, node: &str) {
|
|
//! // Create a bundle with platform-specific RBAC
|
|
//! let bundle = helper::build_privileged_bundle(
|
|
//! helper::PrivilegedPodConfig {
|
|
//! name: "network-config".to_string(),
|
|
//! namespace: "default".to_string(),
|
|
//! node_name: node.to_string(),
|
|
//! // ... other config
|
|
//! ..Default::default()
|
|
//! },
|
|
//! &KubernetesDistribution::OpenshiftFamily,
|
|
//! );
|
|
//!
|
|
//! // Apply all resources (RBAC + Pod) atomically
|
|
//! bundle.apply(client).await.unwrap();
|
|
//!
|
|
//! // ... wait for completion ...
|
|
//!
|
|
//! // Cleanup all resources
|
|
//! bundle.delete(client).await.unwrap();
|
|
//! }
|
|
//! ```
|
|
|
|
use kube::{Error, Resource, ResourceExt, api::DynamicObject};
|
|
use serde::Serialize;
|
|
use serde_json;
|
|
|
|
use crate::K8sClient;
|
|
|
|
/// A ResourceBundle represents a logical unit of work consisting of multiple
|
|
/// Kubernetes resources that should be applied or deleted together.
|
|
///
|
|
/// This pattern is useful for managing ephemeral privileged pods along with
|
|
/// their required RBAC bindings (e.g., OpenShift SCC bindings).
|
|
#[derive(Debug)]
|
|
pub struct ResourceBundle {
|
|
pub resources: Vec<DynamicObject>,
|
|
}
|
|
|
|
impl Default for ResourceBundle {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl ResourceBundle {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
resources: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Add a Kubernetes resource to this bundle.
|
|
/// The resource is converted to a DynamicObject for generic handling.
|
|
pub fn add<K>(&mut self, resource: K)
|
|
where
|
|
K: Resource + Serialize,
|
|
<K as Resource>::DynamicType: Default,
|
|
{
|
|
// Convert the typed resource to JSON, then to DynamicObject
|
|
let json = serde_json::to_value(&resource).expect("Failed to serialize resource");
|
|
let mut obj: DynamicObject =
|
|
serde_json::from_value(json).expect("Failed to convert to DynamicObject");
|
|
|
|
// Ensure type metadata is set
|
|
if obj.types.is_none() {
|
|
let api_version = Default::default();
|
|
let kind = Default::default();
|
|
let gvk = K::api_version(&api_version);
|
|
let kind = K::kind(&kind);
|
|
obj.types = Some(kube::api::TypeMeta {
|
|
api_version: gvk.to_string(),
|
|
kind: kind.to_string(),
|
|
});
|
|
}
|
|
|
|
self.resources.push(obj);
|
|
}
|
|
|
|
/// Apply all resources in this bundle to the cluster.
|
|
/// Resources are applied in the order they were added.
|
|
pub async fn apply(&self, client: &K8sClient) -> Result<(), Error> {
|
|
for res in &self.resources {
|
|
let namespace = res.namespace();
|
|
client
|
|
.apply_dynamic(res, namespace.as_deref(), true)
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Delete all resources in this bundle from the cluster.
|
|
/// Resources are deleted in reverse order to respect dependencies.
|
|
pub async fn delete(&self, client: &K8sClient) -> Result<(), Error> {
|
|
// FIXME delete all in parallel and retry using kube::client::retry::RetryPolicy
|
|
for res in self.resources.iter().rev() {
|
|
let api = client.get_api_for_dynamic_object(res, res.namespace().as_deref())?;
|
|
let name = res.name_any();
|
|
// FIXME this swallows all errors. Swallowing a 404 is ok but other errors must be
|
|
// handled properly (such as retrying). A normal error case is when we delete a
|
|
// resource bundle with dependencies between various resources. Such as a pod with a
|
|
// dependency on a ClusterRoleBinding. Trying to delete the ClusterRoleBinding first
|
|
// is expected to fail
|
|
let _ = api.delete(&name, &kube::api::DeleteParams::default()).await;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|