From 73ce2d3fa8bb51d97a9ad8d4bd257c41d0298847 Mon Sep 17 00:00:00 2001 From: nikoshet Date: Wed, 16 Oct 2024 19:21:50 +0300 Subject: [PATCH] Update --- Cargo.lock | 1 + Cargo.toml | 1 + src/backup/backup_operator.rs | 200 +++++++++--------- src/k8s_ops/mod.rs | 6 +- src/k8s_ops/persistent_volume_claims.rs | 77 ------- src/k8s_ops/pvc/mod.rs | 2 + src/k8s_ops/pvc/persistent_volume_claims.rs | 130 ++++++++++++ .../pvc/persistent_volume_claims_payload.rs | 66 ++++++ src/k8s_ops/volume_snapshot_contents.rs | 85 -------- src/k8s_ops/volume_snapshots.rs | 137 ------------ src/k8s_ops/vs/mod.rs | 1 + src/k8s_ops/vs/volume_snapshots.rs | 168 +++++++++++++++ src/k8s_ops/vsc/mod.rs | 2 + src/k8s_ops/vsc/retain_policy.rs | 30 +++ src/k8s_ops/vsc/volume_snapshot_contents.rs | 110 ++++++++++ src/main.rs | 78 ++++++- src/restore/restore_operator.rs | 181 ++++++++-------- 17 files changed, 771 insertions(+), 504 deletions(-) delete mode 100644 src/k8s_ops/persistent_volume_claims.rs create mode 100644 src/k8s_ops/pvc/mod.rs create mode 100644 src/k8s_ops/pvc/persistent_volume_claims.rs create mode 100644 src/k8s_ops/pvc/persistent_volume_claims_payload.rs delete mode 100644 src/k8s_ops/volume_snapshot_contents.rs delete mode 100644 src/k8s_ops/volume_snapshots.rs create mode 100644 src/k8s_ops/vs/mod.rs create mode 100644 src/k8s_ops/vs/volume_snapshots.rs create mode 100644 src/k8s_ops/vsc/mod.rs create mode 100644 src/k8s_ops/vsc/retain_policy.rs create mode 100644 src/k8s_ops/vsc/volume_snapshot_contents.rs diff --git a/Cargo.lock b/Cargo.lock index cf69239..a2fd999 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1747,6 +1747,7 @@ name = "pvc-snapshotter" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "aws-config", "aws-sdk-ec2", "clap", diff --git a/Cargo.toml b/Cargo.toml index b854da7..9ec7099 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1.0.89" +async-trait = "0.1.83" aws-config = "1.5.7" aws-sdk-ec2 = "1.75.0" clap = { version = "4.5.20", features = ["derive"] } diff --git a/src/backup/backup_operator.rs b/src/backup/backup_operator.rs index 45381fa..75c1e94 100644 --- a/src/backup/backup_operator.rs +++ b/src/backup/backup_operator.rs @@ -1,4 +1,14 @@ +use crate::{ + aws_ops::ebs::create_ebs_client, + k8s_ops::{ + pvc::persistent_volume_claims::{check_if_pvc_exists, get_pvcs_available}, + vs::volume_snapshots::{wait_untill_snapshot_is_ready, VolumeSnapshotOperator}, + vsc::retain_policy::VSCRetainPolicy, + }, +}; use anyhow::{bail, Result}; +use async_trait::async_trait; +use k8s_openapi::api::core::v1::PersistentVolumeClaim; use kube::{api::PostParams, Api, Client}; use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::{ volumesnapshotcontents::VolumeSnapshotContent, @@ -6,91 +16,55 @@ use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::{ }; use tracing::info; -use crate::{ - aws_ops::ebs::create_ebs_client, - k8s_ops::volume_snapshots::{ - construct_volume_snapshot_resource, wait_untill_snapshot_is_ready, - }, -}; - -/// A struct for holding the Kubernetes APIs for the backup operation -struct SourceKubernetesApisStruct { - source_vs_api: Api, - vsc_api: Api, -} - -/// A struct for backing up a PVC to a VolumeSnapshot -pub struct BackupOperator { - region: String, - source_ns: String, - volume_snapshot_class: String, - pvc_name: String, - volume_snapshot_name: String, -} - -impl BackupOperator { - /// Creates a new BackupOperator instance - /// - /// # Arguments - /// - /// * `region` - Region where the EBS volumes are stored - /// * `source_ns` - Source namespace - /// * `volume_snapshot_class` - VolumeSnapshotClass name - /// * `pvc_name` - PVC name - /// * `volume_snapshot_name` - VolumeSnapshot name +#[async_trait] +/// This trait represents the BackupOperator +pub trait BackupOperator { + /// Backs up a PVC to a VolumeSnapshot /// /// # Returns /// - /// BackupOperator instance + /// Result /// /// # Example /// /// ``` - /// use crate::backup::backup_operator::BackupOperator; - /// let backup_operator = BackupOperator::new( + /// use crate::backup::backup_operator::BackupOperatorImpl; + /// let backup_operator = BackupOperatorImpl::new( /// "eu-west-1".to_string(), /// "source-ns".to_string(), /// "volume-snapshot-class".to_string(), /// "pvc-name".to_string(), + /// false, /// "volume-snapshot-name".to_string(), /// ); + /// backup_operator.backup().await?; /// ``` + async fn backup(&self) -> Result<()>; +} + +impl BackupOperatorImpl { pub fn new( region: String, source_ns: String, volume_snapshot_class: String, pvc_name: String, + include_all: bool, volume_snapshot_name: String, ) -> Self { - BackupOperator { + Self { region, source_ns, volume_snapshot_class, pvc_name, + include_all, volume_snapshot_name, } } +} - /// Backs up a PVC to a VolumeSnapshot - /// - /// # Returns - /// - /// Result - /// - /// # Example - /// - /// ``` - /// use crate::backup::backup_operator::BackupOperator; - /// let backup_operator = BackupOperator::new( - /// "eu-west-1".to_string(), - /// "source-ns".to_string(), - /// "volume-snapshot-class".to_string(), - /// "pvc-name".to_string(), - /// "volume-snapshot-name".to_string(), - /// ); - /// backup_operator.backup().await?; - /// ``` - pub async fn backup(&self) -> Result<()> { +#[async_trait] +impl BackupOperator for BackupOperatorImpl { + async fn backup(&self) -> Result<()> { // Create a Kubernetes client let k8s_client = Client::try_default().await?; @@ -100,55 +74,83 @@ impl BackupOperator { // Define the VolumeSnapshot and VolumeSnapshotContent APIs let source_kubernetes_apis_struct = SourceKubernetesApisStruct { source_vs_api: Api::namespaced(k8s_client.clone(), &self.source_ns), + source_pvcs_api: Api::namespaced(k8s_client.clone(), &self.source_ns), vsc_api: Api::all(k8s_client.clone()), }; - // Create a VolumeSnapshot resource on Source Namespace - // let vs_creator_source_ns = VolumeSnapshotCreator::new( - // ... - // ); - // vs_creator_source_ns.create().await?; - let volume_snapshot = construct_volume_snapshot_resource( - self.volume_snapshot_name.to_string(), - self.source_ns.to_string(), - self.volume_snapshot_class.to_string(), - Some(self.pvc_name.to_string()), - None, - None, - None, - ); + // Check if we will backup all PVCs in the namespace + if self.include_all && self.pvc_name.is_empty() { + let pvcs = get_pvcs_available(&source_kubernetes_apis_struct.source_pvcs_api).await?; + info!("Available PVCs: {:?}", pvcs); + bail!("Include all PVCs is not implemented yet"); + } else { + // We will backup a single PVC + // Check if the PVC exists + check_if_pvc_exists( + &source_kubernetes_apis_struct.source_pvcs_api, + &self.pvc_name, + ) + .await?; - let pp = PostParams::default(); - let status: VolumeSnapshotStatus = match source_kubernetes_apis_struct - .source_vs_api - .create(&pp, &volume_snapshot) - .await - { - Ok(snapshot) => { - info!("Created VolumeSnapshot: {:?}", snapshot); - wait_untill_snapshot_is_ready( - source_kubernetes_apis_struct.source_vs_api, - source_kubernetes_apis_struct.vsc_api, - ebs_client.await.unwrap(), - &self.volume_snapshot_name, - ) - .await? - } - Err(e) => { - bail!("Failed to create VolumeSnapshot: {}", e); - } - }; + let vs_operator = VolumeSnapshotOperator::new( + self.volume_snapshot_name.to_string(), + self.source_ns.to_string(), + self.volume_snapshot_class.to_string(), + Some(self.pvc_name.to_string()), + None, + ); - let bound_vsc_name = status.bound_volume_snapshot_content_name.unwrap(); - let restore_size = status.restore_size.unwrap(); - info!( - "{}", - format!( - "VolumeSnapshot is ready! Bound vsc name: {}, restore_size: {}", - bound_vsc_name, restore_size - ) - ); + let volume_snapshot = + vs_operator.construct_volume_snapshot_resource(None, None, VSCRetainPolicy::Delete); + let pp = PostParams::default(); + let status: VolumeSnapshotStatus = match source_kubernetes_apis_struct + .source_vs_api + .create(&pp, &volume_snapshot) + .await + { + Ok(snapshot) => { + info!("Created VolumeSnapshot: {:?}", snapshot); + wait_untill_snapshot_is_ready( + source_kubernetes_apis_struct.source_vs_api, + source_kubernetes_apis_struct.vsc_api, + ebs_client.await.unwrap(), + &self.volume_snapshot_name, + ) + .await? + } + Err(e) => { + bail!("Failed to create VolumeSnapshot: {}", e); + } + }; + + let bound_vsc_name = status.bound_volume_snapshot_content_name.unwrap(); + let restore_size = status.restore_size.unwrap(); + info!( + "{}", + format!( + "VolumeSnapshot is ready! Bound vsc name: {}, restore_size: {}", + bound_vsc_name, restore_size + ) + ); + } Ok(()) } } + +/// A struct for holding the Kubernetes APIs for the backup operation +struct SourceKubernetesApisStruct { + source_vs_api: Api, + source_pvcs_api: Api, + vsc_api: Api, +} + +/// A struct for backing up a PVC to a VolumeSnapshot +pub struct BackupOperatorImpl { + region: String, + source_ns: String, + volume_snapshot_class: String, + pvc_name: String, + include_all: bool, + volume_snapshot_name: String, +} diff --git a/src/k8s_ops/mod.rs b/src/k8s_ops/mod.rs index 0b737aa..f738c13 100644 --- a/src/k8s_ops/mod.rs +++ b/src/k8s_ops/mod.rs @@ -1,3 +1,3 @@ -pub mod persistent_volume_claims; -pub mod volume_snapshot_contents; -pub mod volume_snapshots; +pub mod pvc; +pub mod vs; +pub mod vsc; diff --git a/src/k8s_ops/persistent_volume_claims.rs b/src/k8s_ops/persistent_volume_claims.rs deleted file mode 100644 index a8ca333..0000000 --- a/src/k8s_ops/persistent_volume_claims.rs +++ /dev/null @@ -1,77 +0,0 @@ -use k8s_openapi::{ - api::core::v1::{ - PersistentVolumeClaim, PersistentVolumeClaimSpec, TypedLocalObjectReference, - TypedObjectReference, VolumeResourceRequirements, - }, - apimachinery::pkg::api::resource::Quantity, -}; -use kube::api::ObjectMeta; -use std::collections::BTreeMap; - -/// Construct a PersistentVolumeClaim resource -/// -/// # Arguments -/// -/// * `pvc_name` - Name of the PersistentVolumeClaim resource -/// * `namespace` - Namespace of the PersistentVolumeClaim resource -/// * `storage_class` - Name of the StorageClass resource -/// * `access_modes` - Access modes for the PersistentVolumeClaim resource -/// * `volume_snapshot_name` - Name of the VolumeSnapshot resource -/// * `restore_size` - Size of the PersistentVolumeClaim resource -/// -/// # Returns -/// -/// PersistentVolumeClaim resource -pub fn construct_persistent_volume_claim_resource( - pvc_name: String, - namespace: String, - storage_class: Option, - access_modes: Option>, - volume_snapshot_name: String, - restore_size: String, -) -> PersistentVolumeClaim { - // Create a base labels map - let mut labels = BTreeMap::new(); - - // Always add the VSc name - labels.insert( - "pvc-snapshotter/volume-snapshot-name".to_string(), - volume_snapshot_name.to_string(), - ); - - PersistentVolumeClaim { - metadata: ObjectMeta { - name: Some(pvc_name), - namespace: Some(namespace.clone()), - labels: Some(labels), - ..Default::default() - }, - spec: Some(PersistentVolumeClaimSpec { - access_modes: Some(access_modes.unwrap_or(vec!["ReadWriteOnce".to_string()])), - storage_class_name: Some(storage_class.unwrap_or("gp3".to_string())), - data_source: Some(TypedLocalObjectReference { - name: volume_snapshot_name.to_string(), - kind: "VolumeSnapshot".to_string(), - api_group: Some("snapshot.storage.k8s.io".to_string()), - }), - data_source_ref: Some(TypedObjectReference { - name: volume_snapshot_name.to_string(), - kind: "VolumeSnapshot".to_string(), - api_group: Some("snapshot.storage.k8s.io".to_string()), - namespace: Some(namespace), - }), - volume_mode: Some("Filesystem".to_string()), - volume_name: Default::default(), - resources: Some(VolumeResourceRequirements { - requests: Some(BTreeMap::from([( - "storage".to_string(), - Quantity(restore_size.to_string()), - )])), - ..Default::default() - }), - selector: Default::default(), - volume_attributes_class_name: Default::default(), - }), - ..Default::default() - } -} diff --git a/src/k8s_ops/pvc/mod.rs b/src/k8s_ops/pvc/mod.rs new file mode 100644 index 0000000..588c837 --- /dev/null +++ b/src/k8s_ops/pvc/mod.rs @@ -0,0 +1,2 @@ +pub mod persistent_volume_claims; +pub mod persistent_volume_claims_payload; diff --git a/src/k8s_ops/pvc/persistent_volume_claims.rs b/src/k8s_ops/pvc/persistent_volume_claims.rs new file mode 100644 index 0000000..c92f19f --- /dev/null +++ b/src/k8s_ops/pvc/persistent_volume_claims.rs @@ -0,0 +1,130 @@ +use super::persistent_volume_claims_payload::PVCOperatorPayload; +use anyhow::Result; +use k8s_openapi::{ + api::core::v1::{ + PersistentVolumeClaim, PersistentVolumeClaimSpec, TypedLocalObjectReference, + TypedObjectReference, VolumeResourceRequirements, + }, + apimachinery::pkg::api::resource::Quantity, +}; +use kube::api::{ListParams, ObjectMeta}; +use std::collections::BTreeMap; +use tracing::info; + +pub struct PVCOperator { + pvc_operator_payload: PVCOperatorPayload, +} + +impl PVCOperator { + pub fn new(pvc_operator_payload: PVCOperatorPayload) -> Self { + Self { + pvc_operator_payload, + } + } + + /// Construct a PersistentVolumeClaim resource + /// + /// # Arguments + /// + /// * `pvc_name` - Name of the PersistentVolumeClaim resource + /// * `namespace` - Namespace of the PersistentVolumeClaim resource + /// * `storage_class` - Name of the StorageClass resource + /// * `access_modes` - Access modes for the PersistentVolumeClaim resource + /// * `volume_snapshot_name` - Name of the VolumeSnapshot resource + /// * `restore_size` - Size of the PersistentVolumeClaim resource + /// + /// # Returns + /// + /// PersistentVolumeClaim resource + pub fn construct_persistent_volume_claim_resource(&self) -> PersistentVolumeClaim { + // Create a base labels map + let mut labels = BTreeMap::new(); + + // Always add the VSc name + labels.insert( + "pvc-snapshotter/volume-snapshot-name".to_string(), + self.pvc_operator_payload.pvc_name().to_string(), + ); + + PersistentVolumeClaim { + metadata: ObjectMeta { + name: Some(String::from(self.pvc_operator_payload.pvc_name())), + namespace: Some(String::from(self.pvc_operator_payload.namespace())), + labels: Some(labels), + ..Default::default() + }, + spec: Some(PersistentVolumeClaimSpec { + access_modes: Some( + self.pvc_operator_payload + .access_modes() + .unwrap_or(vec!["ReadWriteOnce".to_string()]), + ), + storage_class_name: Some( + self.pvc_operator_payload + .storage_class() + .unwrap_or("gp3") + .to_string(), + ), + data_source: Some(TypedLocalObjectReference { + name: String::from(self.pvc_operator_payload.volume_snapshot_name()), + kind: "VolumeSnapshot".to_string(), + api_group: Some("snapshot.storage.k8s.io".to_string()), + }), + data_source_ref: Some(TypedObjectReference { + name: String::from(self.pvc_operator_payload.volume_snapshot_name()), + kind: "VolumeSnapshot".to_string(), + api_group: Some("snapshot.storage.k8s.io".to_string()), + namespace: Some(String::from(self.pvc_operator_payload.namespace())), + }), + volume_mode: Some("Filesystem".to_string()), + volume_name: Default::default(), + resources: Some(VolumeResourceRequirements { + requests: Some(BTreeMap::from([( + "storage".to_string(), + Quantity(String::from(self.pvc_operator_payload.restore_size())), + )])), + ..Default::default() + }), + selector: Default::default(), + volume_attributes_class_name: Default::default(), + }), + ..Default::default() + } + } +} + +/// Get the list of PersistentVolumeClaims available +pub async fn get_pvcs_available( + target_pvc_api: &kube::Api, +) -> Result> { + let lp = ListParams::default(); + let pvc_list: Vec<_> = match target_pvc_api.list(&lp).await { + Ok(pvc) => pvc, + Err(e) => panic!("Failed to list PVCs: {}", e), + } + .into_iter() + .map(|pvc| pvc.metadata.name.unwrap()) + .collect(); + + Ok(pvc_list) +} + +pub async fn check_if_pvc_exists( + target_pvc_api: &kube::Api, + pvc_name: &str, +) -> Result { + match target_pvc_api.get(pvc_name).await { + Ok(pvc) => { + info!( + "{}", + format!( + "PVC exists: {} on target namespace {:?}", + pvc.metadata.name.clone().unwrap(), + pvc.metadata.namespace.clone().unwrap() + ) + ); + Ok(pvc) + } + Err(e) => panic!("Failed to get PVC: {}", e), + } +} diff --git a/src/k8s_ops/pvc/persistent_volume_claims_payload.rs b/src/k8s_ops/pvc/persistent_volume_claims_payload.rs new file mode 100644 index 0000000..e50c909 --- /dev/null +++ b/src/k8s_ops/pvc/persistent_volume_claims_payload.rs @@ -0,0 +1,66 @@ +pub struct PVCOperatorPayload { + pub pvc_name: String, + pub namespace: String, + pub storage_class: Option, + pub access_modes: Option>, + pub volume_snapshot_name: String, + pub restore_size: String, +} + +impl PVCOperatorPayload { + /// Creates a new PVCOperatorPayload + /// + /// # Arguments + /// + /// * `pvc_name` - Name of the PersistentVolumeClaim resource + /// * `namespace` - Namespace of the PersistentVolumeClaim resource + /// * `storage_class` - Name of the StorageClass resource + /// * `access_modes` - Access modes for the PersistentVolumeClaim resource + /// * `volume_snapshot_name` - Name of the VolumeSnapshot resource + /// * `restore_size` - Size of the PersistentVolumeClaim resource + /// + /// # Returns + /// + /// A new PVCOperatorPayload instance + pub fn new( + pvc_name: impl Into, + namespace: impl Into, + storage_class: impl Into>, + access_modes: impl Into>>, + volume_snapshot_name: impl Into, + restore_size: impl Into, + ) -> Self { + Self { + pvc_name: pvc_name.into(), + namespace: namespace.into(), + storage_class: storage_class.into(), + access_modes: access_modes.into(), + volume_snapshot_name: volume_snapshot_name.into(), + restore_size: restore_size.into(), + } + } + + pub fn pvc_name(&self) -> &str { + &self.pvc_name + } + + pub fn namespace(&self) -> &str { + &self.namespace + } + + pub fn storage_class(&self) -> Option<&str> { + self.storage_class.as_deref() + } + + pub fn access_modes(&self) -> Option> { + self.access_modes.clone() + } + + pub fn volume_snapshot_name(&self) -> &str { + &self.volume_snapshot_name + } + + pub fn restore_size(&self) -> &str { + &self.restore_size + } +} diff --git a/src/k8s_ops/volume_snapshot_contents.rs b/src/k8s_ops/volume_snapshot_contents.rs deleted file mode 100644 index be243c3..0000000 --- a/src/k8s_ops/volume_snapshot_contents.rs +++ /dev/null @@ -1,85 +0,0 @@ -use anyhow::{bail, Result}; -use kube::{api::ObjectMeta, Api}; -use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshotcontents::{ - VolumeSnapshotContent, VolumeSnapshotContentDeletionPolicy, VolumeSnapshotContentSource, - VolumeSnapshotContentSpec, VolumeSnapshotContentStatus, VolumeSnapshotContentVolumeSnapshotRef, -}; - -/// Construct a VolumeSnapshotContent resource -/// -/// # Arguments -/// -/// * `name` - Name of the VolumeSnapshotContent resource -/// * `namespace` - Namespace of the VolumeSnapshotContent resource -/// * `volume_snapshot_name` - Name of the VolumeSnapshot resource -/// * `volume_snapshot_class` - Name of the VolumeSnapshotClass resource -/// * `source_volume_handle` - Handle - Snapshot ID of the source volume -/// -/// # Returns -/// -/// VolumeSnapshotContent resource -pub fn construct_volume_snapshot_content_resource( - name: String, - namespace: String, - volume_snapshot_name: String, - volume_snapshot_class: Option, - source_volume_handle: Option, -) -> VolumeSnapshotContent { - VolumeSnapshotContent { - metadata: ObjectMeta { - name: Some(name), - namespace: Some(namespace.clone()), - ..Default::default() - }, - spec: VolumeSnapshotContentSpec { - volume_snapshot_ref: VolumeSnapshotContentVolumeSnapshotRef { - api_version: Some("snapshot.storage.k8s.io/v1".to_string()), - kind: Some("VolumeSnapshot".to_string()), - name: Some(volume_snapshot_name), - namespace: Some(namespace), - field_path: Default::default(), - resource_version: Default::default(), - uid: Default::default(), - }, - deletion_policy: VolumeSnapshotContentDeletionPolicy::Retain, - driver: "ebs.csi.aws.com".to_string(), - source: VolumeSnapshotContentSource { - snapshot_handle: source_volume_handle.clone(), - ..Default::default() - }, - volume_snapshot_class_name: volume_snapshot_class, - source_volume_mode: Some("Filesystem".to_string()), - }, - status: Some(VolumeSnapshotContentStatus { - snapshot_handle: source_volume_handle, - creation_time: Default::default(), - ready_to_use: Default::default(), - restore_size: Default::default(), - error: Default::default(), - volume_group_snapshot_handle: Default::default(), - }), - } -} - -/// Get the snapshot handle from the VolumeSnapshotContent -/// -/// # Arguments -/// -/// * `vsc_api` - Api object for VolumeSnapshotContent -/// * `volume_snapshot_content_name` - Name of the VolumeSnapshotContent resource -/// -/// # Returns -/// -/// Snapshot handle -pub async fn get_snapshot_handle( - vsc_api: Api, - volume_snapshot_content_name: &str, -) -> Result { - let volume_snapshot_content = vsc_api.get(volume_snapshot_content_name).await?; - - if let Some(status) = volume_snapshot_content.status { - Ok(status.snapshot_handle.unwrap()) - } else { - bail!("Status of VolumeSnapshotContent is not available") - } -} diff --git a/src/k8s_ops/volume_snapshots.rs b/src/k8s_ops/volume_snapshots.rs deleted file mode 100644 index b68af6c..0000000 --- a/src/k8s_ops/volume_snapshots.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::aws_ops::ebs::get_ebs_snapshot_progress; -use crate::k8s_ops::volume_snapshot_contents::get_snapshot_handle; -use anyhow::Result; -use aws_sdk_ec2::Client as EbsClient; -use kube::api::ObjectMeta; -use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshotcontents::VolumeSnapshotContent; -use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshots::{ - VolumeSnapshot, VolumeSnapshotSource, VolumeSnapshotSpec, VolumeSnapshotStatus, -}; -use std::collections::BTreeMap; -use std::time::Duration; -use tokio::time::sleep; -use tracing::{error, info}; - -/// Construct a VolumeSnapshot resource -/// -/// # Arguments -/// -/// * `name` - Name of the VolumeSnapshot resource -/// * `namespace` - Namespace of the VolumeSnapshot resource -/// * `volume_snapshot_class` - Name of the VolumeSnapshotClass resource -/// * `source_pvc_name` - Name of the PersistentVolumeClaim resource -/// * `vsc_name` - Name of the VolumeSnapshotContent resource -/// -/// # Returns -/// -/// VolumeSnapshot resource -pub fn construct_volume_snapshot_resource( - name: String, - namespace: String, - volume_snapshot_class: String, - source_pvc_name: Option, - vsc_name: Option, - snapshot_handle: Option, - restore_size: Option, -) -> VolumeSnapshot { - // Create a base annotations map - let mut annotations = BTreeMap::new(); - // Always add the CSI driver annotation - annotations.insert( - "pvc-snapshotter/csi-driver-name".to_string(), - "ebs.csi.aws.com".to_string(), - ); - // Always add the VSC deletion policy annotation - annotations.insert( - "pvc-snapshotter/csi-vsc-deletion-policy".to_string(), - "Retain".to_string(), - ); - - // If a snapshot handle is provided, insert the corresponding annotation - if let Some(handle) = snapshot_handle { - annotations.insert( - "pvc-snapshotter/csi-volumesnapshot-handle".to_string(), - handle, - ); - } - // If a restore size is provided, insert the corresponding annotation - if let Some(size) = restore_size { - annotations.insert( - "pvc-snapshotter/csi-volumesnapshot-restore-size".to_string(), - size, - ); - } - - // Create a base labels map - let mut labels = BTreeMap::new(); - // Always add the namespace name - labels.insert("app.kubernetes.io/instance".to_string(), namespace.clone()); - - VolumeSnapshot { - metadata: ObjectMeta { - name: Some(name), - namespace: Some(namespace), - annotations: Some(annotations), - finalizers: Some(vec![ - "snapshot.storage.kubernetes.io/volumesnapshot-bound-protection".to_string(), - ]), - labels: Some(labels), - ..Default::default() - }, - spec: VolumeSnapshotSpec { - volume_snapshot_class_name: Some(volume_snapshot_class), - source: VolumeSnapshotSource { - persistent_volume_claim_name: source_pvc_name, - volume_snapshot_content_name: vsc_name, - }, - }, - ..Default::default() - } -} - -/// Wait untill the VolumeSnapshot is ready -/// -/// # Arguments -/// -/// * `vs_api` - Api object for VolumeSnapshot -/// * `volume_snapshot_name` - Name of the VolumeSnapshot resource -/// -/// # Returns -/// -/// VolumeSnapshotStatus -pub async fn wait_untill_snapshot_is_ready( - vs_api: kube::Api, - vsc_api: kube::Api, - ebs_client: EbsClient, - volume_snapshot_name: &str, -) -> Result { - loop { - let snapshot = vs_api.get(volume_snapshot_name).await?; - if let Some(status) = snapshot.status { - if status.ready_to_use.unwrap_or(false) { - info!("Snapshot is ready: {:?}", status); - return Ok(status); - } - info!("Waiting for VolumeSnapshot to be ready..."); - - let vsc_name = status.bound_volume_snapshot_content_name.unwrap(); - match get_snapshot_handle(vsc_api.clone(), &vsc_name).await { - Ok(snapshot_handle) => { - let progress = - get_ebs_snapshot_progress(&ebs_client, snapshot_handle.clone()).await?; - info!( - "{}", - format!( - "Progress for EBS snapshot {} is: {}", - snapshot_handle, progress - ) - ); - } - Err(e) => { - error!("Failed to get snapshot handle: {}", e); - } - } - sleep(Duration::from_secs(5)).await; - } - } -} diff --git a/src/k8s_ops/vs/mod.rs b/src/k8s_ops/vs/mod.rs new file mode 100644 index 0000000..2b46154 --- /dev/null +++ b/src/k8s_ops/vs/mod.rs @@ -0,0 +1 @@ +pub mod volume_snapshots; diff --git a/src/k8s_ops/vs/volume_snapshots.rs b/src/k8s_ops/vs/volume_snapshots.rs new file mode 100644 index 0000000..a1e8f35 --- /dev/null +++ b/src/k8s_ops/vs/volume_snapshots.rs @@ -0,0 +1,168 @@ +use crate::aws_ops::ebs::get_ebs_snapshot_progress; +use crate::k8s_ops::vsc::retain_policy::VSCRetainPolicy; +use crate::k8s_ops::vsc::volume_snapshot_contents::get_snapshot_handle; +use anyhow::Result; +use aws_sdk_ec2::Client as EbsClient; +use kube::api::ObjectMeta; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshotcontents::VolumeSnapshotContent; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshots::{ + VolumeSnapshot, VolumeSnapshotSource, VolumeSnapshotSpec, VolumeSnapshotStatus, +}; +use std::collections::BTreeMap; +use std::time::Duration; +use tokio::time::sleep; +use tracing::{info, warn}; + +pub struct VolumeSnapshotOperator { + pub name: String, + pub namespace: String, + pub volume_snapshot_class: String, + pub source_pvc_name: Option, + pub vsc_name: Option, +} + +impl VolumeSnapshotOperator { + pub fn new( + name: String, + namespace: String, + volume_snapshot_class: String, + source_pvc_name: Option, + vsc_name: Option, + ) -> Self { + Self { + name, + namespace, + volume_snapshot_class, + source_pvc_name, + vsc_name, + } + } + + /// Construct a VolumeSnapshot resource + /// + /// # Arguments + /// + /// * `name` - Name of the VolumeSnapshot resource + /// * `namespace` - Namespace of the VolumeSnapshot resource + /// * `volume_snapshot_class` - Name of the VolumeSnapshotClass resource + /// * `source_pvc_name` - Name of the PersistentVolumeClaim resource + /// * `vsc_name` - Name of the VolumeSnapshotContent resource + /// * `snapshot_handle` - Handle - Snapshot ID of the source volume + /// * `restore_size` - Size of the restored volume + /// + /// # Returns + /// + /// VolumeSnapshot resource + pub fn construct_volume_snapshot_resource( + &self, + snapshot_handle: Option, + restore_size: Option, + vsc_retain_policy: VSCRetainPolicy, + ) -> VolumeSnapshot { + // Create a base annotations map + let mut annotations = BTreeMap::new(); + // Always add the CSI driver annotation + annotations.insert( + "pvc-snapshotter/csi-driver-name".to_string(), + "ebs.csi.aws.com".to_string(), + ); + // Always add the VSC deletion policy annotation + annotations.insert( + "pvc-snapshotter/csi-vsc-deletion-policy".to_string(), + vsc_retain_policy.to_string(), + ); + + // If a snapshot handle is provided, insert the corresponding annotation + if let Some(handle) = snapshot_handle { + annotations.insert( + "pvc-snapshotter/csi-volumesnapshot-handle".to_string(), + handle, + ); + } + // If a restore size is provided, insert the corresponding annotation + if let Some(size) = restore_size { + annotations.insert( + "pvc-snapshotter/csi-volumesnapshot-restore-size".to_string(), + size, + ); + } + + // Create a base labels map + let mut labels = BTreeMap::new(); + // Always add the namespace name + labels.insert( + "app.kubernetes.io/instance".to_string(), + self.namespace.clone(), + ); + + VolumeSnapshot { + metadata: ObjectMeta { + name: Some(self.name.clone()), + namespace: Some(self.namespace.clone()), + annotations: Some(annotations), + finalizers: Some(vec![ + "snapshot.storage.kubernetes.io/volumesnapshot-bound-protection".to_string(), + ]), + labels: Some(labels), + ..Default::default() + }, + spec: VolumeSnapshotSpec { + volume_snapshot_class_name: Some(self.volume_snapshot_class.clone()), + source: VolumeSnapshotSource { + persistent_volume_claim_name: self.source_pvc_name.clone(), + volume_snapshot_content_name: self.vsc_name.clone(), + }, + }, + ..Default::default() + } + } +} + +/// Wait untill the VolumeSnapshot is ready +/// +/// # Arguments +/// +/// * `vs_api` - Api object for VolumeSnapshot +/// * `vsc_api` - Api object for VolumeSnapshotContent +/// * `ebs_client` - EBS Client object +/// * `volume_snapshot_name` - Name of the VolumeSnapshot resource +/// +/// # Returns +/// +/// VolumeSnapshotStatus +pub async fn wait_untill_snapshot_is_ready( + vs_api: kube::Api, + vsc_api: kube::Api, + ebs_client: EbsClient, + volume_snapshot_name: &str, +) -> Result { + loop { + let snapshot = vs_api.get(volume_snapshot_name).await?; + if let Some(status) = snapshot.status { + if status.ready_to_use.unwrap_or(false) { + info!("Snapshot is ready: {:?}", status); + return Ok(status); + } + info!("Waiting for VolumeSnapshot to be ready..."); + + let vsc_name = status.bound_volume_snapshot_content_name.unwrap(); + match get_snapshot_handle(vsc_api.clone(), &vsc_name).await { + Ok(snapshot_handle) => { + let progress = + get_ebs_snapshot_progress(&ebs_client, snapshot_handle.clone()).await?; + info!( + "{}", + format!( + "Progress for EBS snapshot {} is: {}", + snapshot_handle, progress + ) + ); + } + Err(e) => { + warn!("Failed to get snapshot handle: {}", e); + } + } + sleep(Duration::from_secs(5)).await; + } + } +} diff --git a/src/k8s_ops/vsc/mod.rs b/src/k8s_ops/vsc/mod.rs new file mode 100644 index 0000000..13bb532 --- /dev/null +++ b/src/k8s_ops/vsc/mod.rs @@ -0,0 +1,2 @@ +pub mod retain_policy; +pub mod volume_snapshot_contents; diff --git a/src/k8s_ops/vsc/retain_policy.rs b/src/k8s_ops/vsc/retain_policy.rs new file mode 100644 index 0000000..7089928 --- /dev/null +++ b/src/k8s_ops/vsc/retain_policy.rs @@ -0,0 +1,30 @@ +use clap::ValueEnum; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshotcontents::VolumeSnapshotContentDeletionPolicy; +use std::fmt::{self, Display, Formatter}; + +/// Represents the VolumeSnapshotContent Retain Policy +/// +/// It can be either Retain or Delete +#[derive(ValueEnum, Clone, Debug, Copy, PartialEq, Eq)] +pub enum VSCRetainPolicy { + Retain, + Delete, +} + +impl Display for VSCRetainPolicy { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + VSCRetainPolicy::Retain => write!(f, "Retain"), + VSCRetainPolicy::Delete => write!(f, "Delete"), + } + } +} + +impl From for VolumeSnapshotContentDeletionPolicy { + fn from(vsc_retain_policy: VSCRetainPolicy) -> Self { + match vsc_retain_policy { + VSCRetainPolicy::Retain => VolumeSnapshotContentDeletionPolicy::Retain, + VSCRetainPolicy::Delete => VolumeSnapshotContentDeletionPolicy::Delete, + } + } +} diff --git a/src/k8s_ops/vsc/volume_snapshot_contents.rs b/src/k8s_ops/vsc/volume_snapshot_contents.rs new file mode 100644 index 0000000..d732951 --- /dev/null +++ b/src/k8s_ops/vsc/volume_snapshot_contents.rs @@ -0,0 +1,110 @@ +use anyhow::{bail, Result}; +use kube::{api::ObjectMeta, Api}; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshotcontents::{ + VolumeSnapshotContent, VolumeSnapshotContentSource, VolumeSnapshotContentSpec, + VolumeSnapshotContentStatus, VolumeSnapshotContentVolumeSnapshotRef, +}; + +use super::retain_policy::VSCRetainPolicy; + +pub struct VolumeSnapshotContentOperator { + pub name: String, + pub namespace: String, + pub volume_snapshot_name: String, + pub volume_snapshot_class: Option, + pub source_volume_handle: Option, + pub vsc_retain_policy: VSCRetainPolicy, +} + +impl VolumeSnapshotContentOperator { + pub fn new( + name: String, + namespace: String, + volume_snapshot_name: String, + volume_snapshot_class: Option, + source_volume_handle: Option, + vsc_retain_policy: VSCRetainPolicy, + ) -> Self { + Self { + name, + namespace, + volume_snapshot_name, + volume_snapshot_class, + source_volume_handle, + vsc_retain_policy, + } + } + + /// Construct a VolumeSnapshotContent resource + /// + /// # Arguments + /// + /// * `name` - Name of the VolumeSnapshotContent resource + /// * `namespace` - Namespace of the VolumeSnapshotContent resource + /// * `volume_snapshot_name` - Name of the VolumeSnapshot resource + /// * `volume_snapshot_class` - Name of the VolumeSnapshotClass resource + /// * `source_volume_handle` - Handle - Snapshot ID of the source volume + /// + /// # Returns + /// + /// VolumeSnapshotContent resource + pub fn construct_volume_snapshot_content_resource(&self) -> VolumeSnapshotContent { + VolumeSnapshotContent { + metadata: ObjectMeta { + name: Some(self.name.clone()), + namespace: Some(self.namespace.clone()), + ..Default::default() + }, + spec: VolumeSnapshotContentSpec { + volume_snapshot_ref: VolumeSnapshotContentVolumeSnapshotRef { + api_version: Some("snapshot.storage.k8s.io/v1".to_string()), + kind: Some("VolumeSnapshot".to_string()), + name: Some(self.volume_snapshot_name.clone()), + namespace: Some(self.namespace.clone()), + field_path: Default::default(), + resource_version: Default::default(), + uid: Default::default(), + }, + deletion_policy: self.vsc_retain_policy.into(), + driver: "ebs.csi.aws.com".to_string(), + source: VolumeSnapshotContentSource { + snapshot_handle: self.source_volume_handle.clone(), + ..Default::default() + }, + volume_snapshot_class_name: self.volume_snapshot_class.clone(), + source_volume_mode: Some("Filesystem".to_string()), + }, + status: Some(VolumeSnapshotContentStatus { + snapshot_handle: self.source_volume_handle.clone(), + creation_time: Default::default(), + ready_to_use: Default::default(), + restore_size: Default::default(), + error: Default::default(), + volume_group_snapshot_handle: Default::default(), + }), + } + } +} + +/// Get the snapshot handle from the VolumeSnapshotContent +/// +/// # Arguments +/// +/// * `vsc_api` - Api object for VolumeSnapshotContent +/// * `volume_snapshot_content_name` - Name of the VolumeSnapshotContent resource +/// +/// # Returns +/// +/// Snapshot handle +pub async fn get_snapshot_handle( + vsc_api: Api, + volume_snapshot_content_name: &str, +) -> Result { + let volume_snapshot_content = vsc_api.get(volume_snapshot_content_name).await?; + + if let Some(status) = volume_snapshot_content.status { + Ok(status.snapshot_handle.unwrap()) + } else { + bail!("Status of VolumeSnapshotContent is not available") + } +} diff --git a/src/main.rs b/src/main.rs index 1ee74b0..49692a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,11 @@ mod backup; mod k8s_ops; mod restore; use anyhow::Result; -use backup::backup_operator::BackupOperator; +use backup::backup_operator::{BackupOperator, BackupOperatorImpl}; use clap::{Parser, Subcommand}; use colored::Colorize; -use restore::restore_operator::RestoreOperator; +use k8s_ops::vsc::retain_policy::VSCRetainPolicy; +use restore::restore_operator::{RestoreOperator, RestoreOperatorImpl}; use tracing::info; #[derive(Parser)] @@ -30,8 +31,16 @@ enum Commands { #[arg(long, required = true)] volume_snapshot_class: String, /// PVC name - #[arg(long, required = true)] + #[arg(long, required = false, conflicts_with = "include_all")] pvc_name: String, + /// Include all PVCs in the namespace + #[arg( + long, + required = false, + default_value = "true", + conflicts_with = "pvc_name" + )] + include_all: bool, /// VolumeSnapshot name #[arg(long, required = true)] volume_snapshot_name: String, @@ -47,8 +56,16 @@ enum Commands { #[arg(long, required = true)] volume_snapshot_class: String, /// PVC name - #[arg(long, required = true)] + #[arg(long, required = false, conflicts_with = "include_all")] pvc_name: String, + /// Include all PVCs in the namespace + #[arg( + long, + required = false, + default_value = "true", + conflicts_with = "pvc_name" + )] + include_all: bool, /// VolumeSnapshot name #[arg(long, required = true)] volume_snapshot_name: String, @@ -58,6 +75,10 @@ enum Commands { /// StorageClass name #[arg(long, required = true)] storage_class_name: String, + /// VSC Retain Policy + #[arg(long, required = false, default_value = "delete")] + #[clap(value_enum)] + vsc_retain_policy: VSCRetainPolicy, }, Full { /// Region where the EBS volumes are stored @@ -73,8 +94,16 @@ enum Commands { #[arg(long, required = true)] volume_snapshot_class: String, /// PVC name - #[arg(long, required = true)] + #[arg(long, required = false, conflicts_with = "include_all")] pvc_name: String, + /// Include all PVCs in the namespace + #[arg( + long, + required = false, + default_value = "true", + conflicts_with = "pvc_name" + )] + include_all: bool, /// VolumeSnapshot name #[arg(long, required = true)] volume_snapshot_name: String, @@ -84,6 +113,10 @@ enum Commands { /// StorageClass name #[arg(long, required = true)] storage_class_name: String, + /// VSC Retain Policy + #[arg(long, required = false, default_value = "delete")] + #[clap(value_enum)] + vsc_retain_policy: VSCRetainPolicy, }, } @@ -98,38 +131,52 @@ async fn main() -> Result<()> { source_ns, volume_snapshot_class, pvc_name, + include_all, volume_snapshot_name, } => { - let backup_operator = BackupOperator::new( + let backup_operator = BackupOperatorImpl::new( region, source_ns, volume_snapshot_class, pvc_name, + include_all, volume_snapshot_name, ); info!("{}", "Starting Backup process...".bold().blue()); backup_operator.backup().await?; + info!( + "{}", + "Backup process completed successfully!".bold().green() + ); } Commands::Restore { source_ns, target_ns, volume_snapshot_class, pvc_name, + include_all, volume_snapshot_name, target_snapshot_content_name, storage_class_name, + vsc_retain_policy, } => { - let restore_operator = RestoreOperator::new( + let restore_operator = RestoreOperatorImpl::new( source_ns, target_ns, volume_snapshot_class, pvc_name, + include_all, volume_snapshot_name, target_snapshot_content_name, storage_class_name, + vsc_retain_policy, ); info!("{}", "Starting Restore process...".bold().blue()); restore_operator.restore().await?; + info!( + "{}", + "Restore process completed successfully!".bold().green() + ); } Commands::Full { region, @@ -137,30 +184,43 @@ async fn main() -> Result<()> { target_ns, volume_snapshot_class, pvc_name, + include_all, volume_snapshot_name, target_snapshot_content_name, storage_class_name, + vsc_retain_policy, } => { - let backup_operator = BackupOperator::new( + let backup_operator = BackupOperatorImpl::new( region.clone(), source_ns.clone(), volume_snapshot_class.clone(), pvc_name.clone(), + include_all, volume_snapshot_name.clone(), ); - let restore_operator = RestoreOperator::new( + let restore_operator = RestoreOperatorImpl::new( source_ns, target_ns, volume_snapshot_class, pvc_name, + include_all, volume_snapshot_name, target_snapshot_content_name, storage_class_name, + vsc_retain_policy, ); info!("{}", "Starting Backup process...".bold().blue()); backup_operator.backup().await?; + info!( + "{}", + "Backup process completed successfully!".bold().green() + ); info!("{}", "Starting Restore process...".bold().blue()); restore_operator.restore().await?; + info!( + "{}", + "Restore process completed successfully!".bold().green() + ); } }; Ok(()) diff --git a/src/restore/restore_operator.rs b/src/restore/restore_operator.rs index 7db78fd..02f4cd8 100644 --- a/src/restore/restore_operator.rs +++ b/src/restore/restore_operator.rs @@ -1,9 +1,15 @@ use crate::k8s_ops::{ - persistent_volume_claims::construct_persistent_volume_claim_resource, - volume_snapshot_contents::{construct_volume_snapshot_content_resource, get_snapshot_handle}, - volume_snapshots::construct_volume_snapshot_resource, + pvc::{ + persistent_volume_claims::PVCOperator, persistent_volume_claims_payload::PVCOperatorPayload, + }, + vs::volume_snapshots::VolumeSnapshotOperator, + vsc::{ + retain_policy::VSCRetainPolicy, + volume_snapshot_contents::{get_snapshot_handle, VolumeSnapshotContentOperator}, + }, }; use anyhow::{bail, Result}; +use async_trait::async_trait; use k8s_openapi::api::core::v1::PersistentVolumeClaim; use kube::{api::PostParams, Api, Client}; use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::{ @@ -12,105 +18,72 @@ use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::{ }; use tracing::info; -/// A struct for holding the Kubernetes APIs for the restore operation -struct TargetKubernetesApisStruct { - source_vs_api: Api, - vsc_api: Api, - pvcs_api: Api, -} - -/// A struct for restoring a PVC from a VolumeSnapshot -pub struct RestoreOperator { - source_ns: String, - target_ns: String, - volume_snapshot_class: String, - pvc_name: String, - volume_snapshot_name: String, - target_snapshot_content_name: String, - storage_class_name: String, -} - -impl RestoreOperator { - /// Creates a new RestoreOperator instance - /// - /// # Arguments - /// - /// * `source_ns` - Source namespace - /// * `target_ns` - Target namespace - /// * `volume_snapshot_class` - VolumeSnapshotClass name - /// * `pvc_name` - PVC name - /// * `volume_snapshot_name` - VolumeSnapshot name - /// * `target_snapshot_content_name` - Target VolumeSnapshotContent name - /// * `storage_class_name` - StorageClass name +#[async_trait] +pub trait RestoreOperator { + /// Restores a PVC from a VolumeSnapshot /// /// # Returns /// - /// RestoreOperator instance + /// Result /// /// # Example /// /// ``` - /// use crate::restore::restore_operator::RestoreOperator; - /// let restore_operator = RestoreOperator::new( + /// use crate::restore::restore_operator::RestoreOperatorImpl; + /// let restore_operator = RestoreOperatorImpl::new( /// "source-ns".to_string(), /// "target-ns".to_string(), /// "volume-snapshot-class".to_string(), /// "pvc-name".to_string(), + /// false, /// "volume-snapshot-name".to_string(), /// "target-snapshot-content-name".to_string(), /// "storage-class-name".to_string(), /// ); + /// restore_operator.restore().await?; /// ``` + async fn restore(&self) -> Result<()>; +} + +impl RestoreOperatorImpl { + #[allow(clippy::too_many_arguments)] pub fn new( source_ns: String, target_ns: String, volume_snapshot_class: String, pvc_name: String, + include_all: bool, volume_snapshot_name: String, target_snapshot_content_name: String, storage_class_name: String, + vsc_retain_policy: VSCRetainPolicy, ) -> Self { - RestoreOperator { + Self { source_ns, target_ns, volume_snapshot_class, pvc_name, + include_all, volume_snapshot_name, target_snapshot_content_name, storage_class_name, + vsc_retain_policy, } } +} - /// Restores a PVC from a VolumeSnapshot - /// - /// # Returns - /// - /// Result - /// - /// # Example - /// - /// ``` - /// use crate::restore::restore_operator::RestoreOperator; - /// let restore_operator = RestoreOperator::new( - /// "source-ns".to_string(), - /// "target-ns".to_string(), - /// "volume-snapshot-class".to_string(), - /// "pvc-name".to_string(), - /// "volume-snapshot-name".to_string(), - /// "target-snapshot-content-name".to_string(), - /// "storage-class-name".to_string(), - /// ); - /// restore_operator.restore().await?; - /// ``` - pub async fn restore(&self) -> Result<()> { +#[async_trait] +impl RestoreOperator for RestoreOperatorImpl { + async fn restore(&self) -> Result<()> { // Create a Kubernetes client let k8s_client = Client::try_default().await?; // Define the VolumeSnapshot, VolumeSnapshotContent and PersistentVolumeClaim APIs let target_kubernetes_apis_struct = TargetKubernetesApisStruct { source_vs_api: Api::namespaced(k8s_client.clone(), &self.source_ns), + target_pvcs_api: Api::namespaced(k8s_client.clone(), &self.target_ns), + target_vs_api: Api::namespaced(k8s_client.clone(), &self.target_ns), vsc_api: Api::all(k8s_client.clone()), - pvcs_api: Api::namespaced(k8s_client.clone(), &self.target_ns), }; let status: VolumeSnapshotStatus = match target_kubernetes_apis_struct @@ -133,19 +106,17 @@ impl RestoreOperator { ) .await?; - // Create a VolumeSnapshotContent resource on Target Namespace - // let vs_creator_target_ns = VolumeSnapshotContentCreator::new( - // ... - // ); - // vs_creator_target_ns.create().await?; - let snapshot_content = construct_volume_snapshot_content_resource( - self.target_snapshot_content_name.to_string(), - self.target_ns.to_string(), - self.volume_snapshot_name.to_string(), - Some(self.volume_snapshot_class.to_string()), - Some(snapshot_handle.to_string()), + let vsc_operator = VolumeSnapshotContentOperator::new( + self.target_snapshot_content_name.clone(), + self.target_ns.clone(), + self.volume_snapshot_name.clone(), + Some(self.volume_snapshot_class.clone()), + Some(snapshot_handle.clone()), + self.vsc_retain_policy, ); + let snapshot_content = vsc_operator.construct_volume_snapshot_content_resource(); + let pp = PostParams::default(); match target_kubernetes_apis_struct .vsc_api @@ -156,49 +127,50 @@ impl RestoreOperator { Err(e) => panic!("Failed to create VolumeSnapshotContent: {}", e), } - // Create a VolumeSnapshot resource in the Target Namespace - // let vs_creator = VolumeSnapshotCreator::new( - // ... - // ); - // vs_creator.create().await?; - let vs_api_target_ns: Api = Api::namespaced(k8s_client, &self.target_ns); - let target_volume_snapshot = construct_volume_snapshot_resource( - self.volume_snapshot_name.to_string(), - self.target_ns.to_string(), - self.volume_snapshot_class.to_string(), + let vs_operator = VolumeSnapshotOperator::new( + self.volume_snapshot_name.clone(), + self.target_ns.clone(), + self.volume_snapshot_class.clone(), None, Some(self.target_snapshot_content_name.to_string()), + ); + + let target_volume_snapshot = vs_operator.construct_volume_snapshot_resource( Some(snapshot_handle.to_string()), Some(restore_size.to_string()), + self.vsc_retain_policy, ); info!("Creating VolumeSnapshot in the target namespace..."); let pp = PostParams::default(); - match vs_api_target_ns.create(&pp, &target_volume_snapshot).await { + match target_kubernetes_apis_struct + .target_vs_api + .create(&pp, &target_volume_snapshot) + .await + { Ok(target_volume_snapshot) => { info!("Created VolumeSnapshot: {:?}", target_volume_snapshot) } Err(e) => panic!("Failed to create VolumeSnapshot: {}", e), } - // Create the PVC from the VolumeSnapshot to Target Namespace - // let pvc_creator = PersistentVolumeClaimCreator::new( - // ... - // ); - // pvc_creator.create().await?; - let pvc: PersistentVolumeClaim = construct_persistent_volume_claim_resource( - self.pvc_name.to_string(), - self.target_ns.to_string(), - Some(self.storage_class_name.to_string()), + // Restore the PVC for each pvc available + let pvc_payload = PVCOperatorPayload::new( + self.pvc_name.clone(), + self.target_ns.clone(), + Some(self.storage_class_name.clone()), None, - self.volume_snapshot_name.to_string(), - restore_size.to_string(), + self.volume_snapshot_name.clone(), + restore_size, ); + let pvc_operator = PVCOperator::new(pvc_payload); + let pvc = pvc_operator.construct_persistent_volume_claim_resource(); + info!("Restoring PVC..."); let pp = PostParams::default(); match target_kubernetes_apis_struct - .pvcs_api + .target_pvcs_api .create(&pp, &pvc) .await { @@ -209,3 +181,24 @@ impl RestoreOperator { Ok(()) } } + +/// A struct for holding the Kubernetes APIs for the restore operation +struct TargetKubernetesApisStruct { + source_vs_api: Api, + target_pvcs_api: Api, + target_vs_api: Api, + vsc_api: Api, +} + +/// A struct for restoring a PVC from a VolumeSnapshot +pub struct RestoreOperatorImpl { + source_ns: String, + target_ns: String, + volume_snapshot_class: String, + pvc_name: String, + include_all: bool, + volume_snapshot_name: String, + target_snapshot_content_name: String, + storage_class_name: String, + vsc_retain_policy: VSCRetainPolicy, +}