Skip to content

Commit b9bd656

Browse files
authored
Merge pull request #7 from KubeDeckio/add-helm-storage-backend-backup
Add helm storage backend backup
2 parents 552dfc1 + 43275f6 commit b9bd656

File tree

6 files changed

+245
-174
lines changed

6 files changed

+245
-174
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
snapshots/
1+
snapshots/
2+
helm_snapshots/

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.0.12] - 2024-12-08
8+
9+
### Added:
10+
- **Helm Backend Storage**: `$SnapshotHelm` now includes a backup of Helm backend storage (Secrets or ConfigMaps), ensuring that all metadata for Helm releases is captured in snapshots.
11+
- **Verbose Backend Logging**: Detailed logging of backend type (Secrets or ConfigMaps) and operations is now included when running in verbose mode.
12+
- **Helm Restore with History**: Added support for restoring Helm releases, including their full history and backend configuration (Secrets or ConfigMaps). This ensures a complete recovery of release state.
13+
- **Dynamic Resource Discovery**: Replaced hardcoded lists of resource kinds with dynamic discovery using `kubectl api-resources`. This ensures all available Kubernetes resource types, including CRDs and their objects, are automatically included in snapshots.
14+
- **Automatic CRD Object Backup**: CRD objects are now seamlessly backed up alongside other resources without requiring separate logic.
15+
16+
### Fixed:
17+
- **Empty Release Handling**: Improved handling of namespaces with no Helm releases, avoiding unnecessary warnings and providing clear messages instead.
18+
- **Error Messages**: Enhanced error messages for missing tools (`helm` or `kubectl`) to include actionable instructions for installation.
19+
20+
- **Changed**: Replaced `$AllNamespaces` with `$ClusterResources` for snapshotting cluster-scoped resources. `$AllNamespaces` remains available for Helm snapshotting purposes.
21+
722
## [0.0.11] - 2024-12-06
823

924
### Fixed:

KubeSnapIt.psm1

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ function Invoke-KubeSnapIt {
7575
[string]$OutputPath = "./snapshots",
7676
[string]$InputPath = "",
7777
[string]$ComparePath = "",
78+
[switch]$ClusterResources,
7879
[switch]$AllNamespaces,
7980
[switch]$AllNonSystemNamespaces,
8081
[string]$Labels = "",
@@ -87,6 +88,7 @@ function Invoke-KubeSnapIt {
8788
[switch]$UI,
8889
[switch]$SnapshotHelm,
8990
[switch]$SnapshotHelmUsedValues,
91+
[switch]$RestoreHelmSnapshot,
9092
[Alias("h")] [switch]$Help
9193
)
9294
# END PARAM BLOCK
@@ -99,6 +101,7 @@ function Invoke-KubeSnapIt {
99101
Write-Host " -OutputPath Path to save the snapshot files."
100102
Write-Host " -InputPath Path to restore snapshots from or the first snapshot for comparison."
101103
Write-Host " -ComparePath Path to the second snapshot for comparison (optional)."
104+
Write-Host " -ClusterResources Capture cluster-wide resources (e.g., crd's, namespaces)."
102105
Write-Host " -AllNamespaces Capture all namespaces. If this is provided, -Namespace will be ignored."
103106
Write-Host " -AllNonSystemNamespaces Capture all non-system namespaces. If this is provided, -Namespace and -AllNamespaces will be ignored."
104107
Write-Host " -Labels Specify labels to filter Kubernetes objects (e.g., app=nginx)."
@@ -110,6 +113,7 @@ function Invoke-KubeSnapIt {
110113
Write-Host " -Force Force the action without prompting for confirmation."
111114
Write-Host " -SnapshotHelm Backup Helm releases and their values."
112115
Write-Host " -SnapshotHelmUsedValues Backup Helm release values."
116+
write-Host " -RestoreHelmSnapshot Restore Helm release from a snapshot."
113117
Write-Host " -Help Display this help message."
114118
return
115119
}
@@ -262,7 +266,16 @@ function Invoke-KubeSnapIt {
262266
return
263267
}
264268

265-
{ $Namespace -or $AllNamespaces -or $AllNonSystemNamespaces } {
269+
{ $RestoreHelmSnapshot } {
270+
if (-not $InputPath) {
271+
Write-Host "Error: Input path required for restore." -ForegroundColor Red
272+
return
273+
}
274+
Restore-HelmBackup -ManifestFilePath:$InputPath -Namespace:$Namespace -DryRun:$DryRun -Verbose:$Verbose
275+
return
276+
}
277+
278+
{ $Namespace -or $ClusterResources -or $AllNonSystemNamespaces } {
266279
if (-not (Test-Path -Path $OutputPath)) {
267280
New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
268281
Write-Verbose "Output directory created: $OutputPath"
@@ -272,7 +285,7 @@ function Invoke-KubeSnapIt {
272285

273286
# Snapshot function call
274287
try {
275-
Save-KubeSnapshot -Namespace $Namespace -AllNamespaces:$AllNamespaces -AllNonSystemNamespaces:$AllNonSystemNamespaces -Labels $Labels -Objects $Objects -OutputPath $OutputPath -DryRun:$DryRun -Verbose:$Verbose
288+
Save-KubeSnapshot -Namespace $Namespace -ClusterResources:$ClusterResources -AllNonSystemNamespaces:$AllNonSystemNamespaces -Labels $Labels -Objects $Objects -OutputPath $OutputPath -DryRun:$DryRun -Verbose:$Verbose
276289
}
277290
catch {
278291
Write-Host "Error during snapshot: $_" -ForegroundColor Red
@@ -281,7 +294,7 @@ function Invoke-KubeSnapIt {
281294
}
282295

283296
default {
284-
Write-Host "Error: Specify -Restore, -CompareWithCluster, -CompareSnapshots, or -SnapshotHelm with necessary parameters." -ForegroundColor Red
297+
Write-Host "Error: Specify -Restore, -CompareWithCluster, -CompareSnapshots, or -SnapshotHelm, or -ClusterResources with necessary parameters." -ForegroundColor Red
285298
return
286299
}
287300
}

Private/Restore-HelmBackup.ps1

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
function Restore-HelmBackup {
2+
param (
3+
[string]$ManifestFilePath, # Path to the manifest file to restore
4+
[string]$Namespace, # Target namespace for the release
5+
[switch]$Verbose, # Enable verbose output
6+
[switch]$DryRun # Simulate the restore process without applying changes
7+
)
8+
9+
# Set verbose preference
10+
if ($Verbose) {
11+
$VerbosePreference = "Continue"
12+
}
13+
14+
# Check if kubectl is installed
15+
if (!(Get-Command "kubectl")) {
16+
Write-Host "'kubectl' is not installed or not found in your system's PATH." -ForegroundColor Red
17+
Write-Host "Please install 'kubectl' before running Restore-HelmBackup." -ForegroundColor Red
18+
Write-Host "You can install 'kubectl' from: https://kubernetes.io/docs/tasks/tools/install/" -ForegroundColor Yellow
19+
return
20+
}
21+
22+
# Validate the manifest file path
23+
if (-not (Test-Path $ManifestFilePath)) {
24+
Write-Host "Manifest file not found at path '$ManifestFilePath'." -ForegroundColor Red
25+
return
26+
}
27+
28+
# Extract release name and timestamp from manifest file name
29+
$manifestFileName = [System.IO.Path]::GetFileNameWithoutExtension($ManifestFilePath)
30+
if ($manifestFileName -notmatch "^(?<releaseName>.+)_manifest_(?<timestamp>\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})$") {
31+
Write-Host "Invalid manifest file naming convention: '$manifestFileName'." -ForegroundColor Red
32+
return
33+
}
34+
$releaseName = $matches['releaseName']
35+
$timestamp = $matches['timestamp']
36+
37+
# Locate corresponding backend files based on timestamp
38+
$backupDir = [System.IO.Path]::GetDirectoryName($ManifestFilePath)
39+
$backendFile = Get-ChildItem -Path $backupDir -Filter "${releaseName}_backend_${timestamp}.yaml" | Select-Object -First 1
40+
41+
# Log the files being used for restoration
42+
Write-Host "Using manifest file: $ManifestFilePath" -ForegroundColor Green
43+
if ($backendFile) {
44+
Write-Host "Using backend file: $($backendFile.FullName)" -ForegroundColor Green
45+
} else {
46+
Write-Host "No matching backend file found for release '$releaseName'." -ForegroundColor Yellow
47+
}
48+
49+
# Check for namespace and create if needed
50+
if (-not $DryRun) {
51+
$namespaceCheck = kubectl get namespace $Namespace -o name
52+
if (-not $namespaceCheck) {
53+
Write-Verbose "Creating namespace '$Namespace'."
54+
kubectl create namespace $Namespace
55+
Write-Host "Namespace '$Namespace' created." -ForegroundColor Green
56+
} else {
57+
Write-Host "Namespace '$Namespace' exists." -ForegroundColor Green
58+
}
59+
}
60+
61+
# Restore the manifest
62+
Write-Host "Restoring manifest for Helm release '$releaseName'..." -ForegroundColor Green
63+
if ($DryRun) {
64+
Write-Host "Dry run: Would apply manifest from file $ManifestFilePath." -ForegroundColor Yellow
65+
} else {
66+
$kubectlApplyCmd = "apply -f $ManifestFilePath"
67+
Write-Verbose "Running command: kubectl $kubectlApplyCmd"
68+
kubectl apply -f $ManifestFilePath
69+
Write-Host "Manifest for release '$releaseName' applied." -ForegroundColor Green
70+
}
71+
72+
# Restore backend storage (if available)
73+
if ($backendFile) {
74+
Write-Host "Restoring backend storage for Helm release '$releaseName'..." -ForegroundColor Green
75+
if ($DryRun) {
76+
Write-Host "Dry run: Would apply backend storage from file $($backendFile.FullName)." -ForegroundColor Yellow
77+
} else {
78+
$kubectlApplyCmd = "apply -f $($backendFile.FullName)"
79+
Write-Verbose "Running command: kubectl $kubectlApplyCmd"
80+
kubectl apply -f $backendFile.FullName
81+
Write-Host "Backend storage for release '$releaseName' applied." -ForegroundColor Green
82+
}
83+
}
84+
85+
Write-Host "Restore process for release '$releaseName' completed." -ForegroundColor Green
86+
}

Private/Save-HelmBackup.ps1

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ function Save-HelmBackup {
66
[switch]$Verbose,
77
[switch]$AllNamespaces,
88
[switch]$AllNonSystemNamespaces,
9-
[switch]$SnapshotHelm, # Perform a full Helm snapshot
9+
[switch]$SnapshotHelm, # Perform a full Helm snapshot (includes backend storage)
1010
[switch]$SnapshotHelmUsedValues # Perform only Helm used values snapshot
1111
)
1212

@@ -75,12 +75,48 @@ function Save-HelmBackup {
7575

7676
if ($usedValuesOutput) {
7777
return $usedValuesOutput
78-
} else {
78+
}
79+
else {
7980
Write-Host "No used values found for release '$ReleaseName'." -ForegroundColor Yellow
8081
return $null
8182
}
8283
}
8384

85+
# Function to backup backend storage
86+
function Backup-HelmBackend {
87+
param (
88+
[string]$Namespace
89+
)
90+
Write-Verbose "Backing up Helm storage backend for namespace: $Namespace"
91+
$secrets = kubectl get secrets -n $Namespace -o json | ConvertFrom-Json
92+
$backendType = $secrets.items | Where-Object { $_.type -eq 'helm.sh/release.v1' -and $_.metadata.name -like '*sh.helm.release.v1*' } | Select-Object -First 1
93+
94+
if ($backendType -match "helm.sh/release.v1") {
95+
Write-Verbose "Detected backend: Secrets"
96+
$releases = kubectl get secrets -n $Namespace -l "owner=helm" -o json | ConvertFrom-Json
97+
}
98+
else {
99+
Write-Verbose "Detected backend: ConfigMaps"
100+
$releases = kubectl get configmaps -n $Namespace -l "owner=helm" -o json | ConvertFrom-Json
101+
}
102+
103+
# Prepare YAML output file
104+
105+
$releaseName = $release.metadata.labels.name
106+
$safeName = $releaseName -replace "[:\\/]", "_"
107+
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
108+
$filePath = Join-Path -Path $OutputPath -ChildPath "${safeName}_backend_$timestamp.yaml"
109+
New-Item -Path $filePath -ItemType File -Force | Out-Null
110+
111+
# Convert and append each release to the YAML file
112+
foreach ($release in $releases.items) {
113+
$yamlContent = ConvertTo-Yaml -Data $release
114+
Add-Content -Path $filePath -Value $yamlContent
115+
Add-Content -Path $filePath -Value "---"
116+
}
117+
Write-Host "Helm release '$releaseName' backend saved: $filePath" -ForegroundColor Green
118+
}
119+
84120
# Function to process a namespace
85121
function Backup-HelmNamespace {
86122
param (
@@ -92,6 +128,11 @@ function Save-HelmBackup {
92128
$namespaceOption = "-n $Namespace"
93129
}
94130

131+
# Back up backend storage if SnapshotHelm is enabled
132+
if ($SnapshotHelm) {
133+
Backup-HelmBackend -Namespace $Namespace
134+
}
135+
95136
$helmListCmd = "list $namespaceOption --output json"
96137
Write-Verbose "Running command: helm $helmListCmd"
97138
$releasesOutput = Invoke-HelmCommand $helmListCmd
@@ -111,8 +152,9 @@ function Save-HelmBackup {
111152

112153
if ($DryRun) {
113154
Write-Host "Dry run: Found Helm release '$releaseName' in namespace '$releaseNamespace'."
114-
} else {
115-
# Full Helm snapshot (values, manifest, and history)
155+
}
156+
else {
157+
# Full Helm snapshot (values, and manifest)
116158
if ($SnapshotHelm) {
117159
# Fetch values
118160
$helmGetValuesCmd = "get values $releaseName $namespaceOption -o yaml"
@@ -134,16 +176,6 @@ function Save-HelmBackup {
134176
$manifestOutput | Out-File -FilePath $manifestFile -Force
135177
Write-Host "Helm release '$releaseName' manifest saved: $manifestFile" -ForegroundColor Green
136178
}
137-
138-
# Fetch history
139-
$helmHistoryCmd = "history $releaseName $namespaceOption --output yaml"
140-
Write-Verbose "Running command: helm $helmHistoryCmd"
141-
$historyOutput = Invoke-HelmCommand $helmHistoryCmd
142-
if ($historyOutput) {
143-
$historyFile = Join-Path -Path $OutputPath -ChildPath "${safeReleaseName}_history_$timestamp.yaml"
144-
$historyOutput | Out-File -FilePath $historyFile -Force
145-
Write-Host "Helm release '$releaseName' history saved: $historyFile" -ForegroundColor Green
146-
}
147179
}
148180

149181
# Only Helm used values snapshot
@@ -158,7 +190,8 @@ function Save-HelmBackup {
158190
}
159191
}
160192
}
161-
} else {
193+
}
194+
else {
162195
Write-Host "No Helm releases found in the namespace '$Namespace'." -ForegroundColor Yellow
163196
}
164197
}
@@ -168,12 +201,15 @@ function Save-HelmBackup {
168201
if ($AllNamespaces) {
169202
$namespaces = kubectl get namespaces -o json | ConvertFrom-Json
170203
$allClusterNamespaces = $namespaces.items | ForEach-Object { $_.metadata.name }
171-
} elseif ($AllNonSystemNamespaces) {
204+
}
205+
elseif ($AllNonSystemNamespaces) {
172206
$namespaces = kubectl get namespaces -o json | ConvertFrom-Json
173207
$allClusterNamespaces = $namespaces.items | Where-Object { $_.metadata.name -notmatch "^(kube-system|kube-public|kube-node-lease|default)$" } | ForEach-Object { $_.metadata.name }
174-
} elseif ($Namespace) {
208+
}
209+
elseif ($Namespace) {
175210
$allClusterNamespaces = @($Namespace)
176-
} else {
211+
}
212+
else {
177213
Write-Host "No namespace specified." -ForegroundColor Red
178214
return
179215
}
@@ -187,4 +223,4 @@ function Save-HelmBackup {
187223
catch {
188224
Write-Host "Error occurred while backing up Helm releases: $_" -ForegroundColor Red
189225
}
190-
}
226+
}

0 commit comments

Comments
 (0)