Skip to content

Commit e4417b0

Browse files
committed
feat: add current namespace retrieval and all-namespaces backup functionality
1 parent 085c7b7 commit e4417b0

File tree

4 files changed

+128
-25
lines changed

4 files changed

+128
-25
lines changed

README.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ A Go application that backs up Kubernetes resources from a specified namespace b
1515

1616
## Features
1717

18-
- Exports all standard Kubernetes resources from a namespace
18+
- Exports all standard Kubernetes resources from a namespace or all namespaces
19+
- Uses the current namespace from kubeconfig when no namespace is specified
1920
- Organizes backups by resource kind in separate directories
2021
- Thoroughly cleans manifests by removing server-side and cluster-specific fields
2122
- Timestamp-based backup directories
23+
- Colorful and descriptive console output with emojis
24+
- Resource type filtering for selective backups
2225

2326
## Installation
2427

@@ -58,9 +61,15 @@ docker run --rm -v ~/.kube:/root/.kube -v $(pwd)/backups:/backups kbak:latest --
5861
# Show version information
5962
./kbak --version
6063

61-
# Backup a namespace using the current kubeconfig
64+
# Backup resources from your current namespace (from kubeconfig)
65+
./kbak
66+
67+
# Backup a specific namespace
6268
./kbak --namespace your-namespace
6369

70+
# Backup all namespaces
71+
./kbak --all-namespaces
72+
6473
# Specify a custom kubeconfig file
6574
./kbak --namespace your-namespace --kubeconfig /path/to/kubeconfig
6675

@@ -72,6 +81,9 @@ docker run --rm -v ~/.kube:/root/.kube -v $(pwd)/backups:/backups kbak:latest --
7281

7382
# Backup only pods and deployments
7483
./kbak --namespace your-namespace --pod --deployment --all-resources=false
84+
85+
# Backup resources with verbose output
86+
./kbak --namespace your-namespace --verbose
7587
```
7688

7789
### Resource Type Filtering
@@ -111,8 +123,7 @@ The tool automatically backs up the following resource types:
111123

112124
## Output Structure
113125

114-
Backups are organized as follows:
115-
126+
### Single Namespace Backup
116127
```
117128
02Jan2006-15:04/
118129
└── namespace/
@@ -127,6 +138,24 @@ Backups are organized as follows:
127138
│ └── ...
128139
└── ...
129140
```
141+
142+
### All Namespaces Backup
143+
```
144+
02Jan2006-15:04/
145+
└── all-namespaces/
146+
├── namespace1/
147+
│ ├── Pod/
148+
│ │ ├── my-pod.yaml
149+
│ │ └── ...
150+
│ ├── Deployment/
151+
│ │ └── ...
152+
│ └── ...
153+
├── namespace2/
154+
│ ├── Pod/
155+
│ │ └── ...
156+
│ └── ...
157+
└── ...
158+
```
130159
## License
131160

132161
MIT License

cmd/kbak/main.go

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"flag"
56
"fmt"
67
"os"
@@ -10,6 +11,8 @@ import (
1011
"github.com/rogosprojects/kbak/pkg/backup"
1112
"github.com/rogosprojects/kbak/pkg/client"
1213
"github.com/rogosprojects/kbak/pkg/utils"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/client-go/tools/clientcmd"
1316
"k8s.io/client-go/util/homedir"
1417
)
1518

@@ -48,7 +51,7 @@ func main() {
4851
var resFlags resourceFlags
4952

5053
// Basic flags
51-
flag.StringVar(&namespace, "namespace", "", "Namespace to backup (required unless --all-namespaces is used)")
54+
flag.StringVar(&namespace, "namespace", "", "Namespace to backup (uses current namespace from kubeconfig if not specified)")
5255
flag.StringVar(&outputDir, "output", "backups", "Output directory for backup files")
5356
flag.BoolVar(&verbose, "verbose", false, "Show verbose output")
5457
flag.BoolVar(&showVersion, "version", false, "Show version information and exit")
@@ -80,19 +83,11 @@ func main() {
8083
flag.Parse()
8184

8285
if showVersion {
83-
fmt.Printf("%s %skbak%s version %s %s%s\n",
86+
fmt.Printf("%s %skbak%s version %s%s%s\n",
8487
utils.K8sEmoji, utils.Bold, utils.Reset, utils.Cyan, Version, utils.Reset)
8588
os.Exit(0)
8689
}
8790

88-
// Validate namespace requirements
89-
if namespace == "" && !allNamespaces {
90-
fmt.Printf("%s %s%sError: either --namespace or --all-namespaces flag is required%s\n",
91-
utils.ErrorEmoji, utils.Red, utils.Bold, utils.Reset)
92-
flag.Usage()
93-
os.Exit(1)
94-
}
95-
9691
if allNamespaces && namespace != "" {
9792
fmt.Printf("%s %s%sWarning: --namespace flag is ignored when --all-namespaces is used%s\n",
9893
utils.WarningEmoji, utils.Yellow, utils.Bold, utils.Reset)
@@ -107,13 +102,92 @@ func main() {
107102
os.Exit(1)
108103
}
109104

110-
// Handle all-namespaces case (not implemented yet, just a placeholder)
105+
// If namespace is not specified and not using all-namespaces, get the current namespace from kubeconfig
106+
if namespace == "" && !allNamespaces {
107+
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
108+
loadingRules.ExplicitPath = kubeconfig
109+
configOverrides := &clientcmd.ConfigOverrides{}
110+
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
111+
currentNamespace, _, err := kubeConfig.Namespace()
112+
if err != nil {
113+
fmt.Printf("%s %s%sError getting current namespace: %v%s\n",
114+
utils.ErrorEmoji, utils.Red, utils.Bold, err, utils.Reset)
115+
os.Exit(1)
116+
}
117+
namespace = currentNamespace
118+
if verbose {
119+
fmt.Printf(" %sUsing current namespace: %s%s\n",
120+
utils.Cyan, namespace, utils.Reset)
121+
}
122+
}
123+
124+
// Handle all-namespaces case
111125
if allNamespaces {
112-
fmt.Printf("%s %s%sError: --all-namespaces is not implemented yet%s\n",
113-
utils.ErrorEmoji, utils.Red, utils.Bold, utils.Reset)
114-
os.Exit(1)
115-
// Here we would get a list of all namespaces and iterate through them
116-
// This feature is left for future implementation
126+
// Get all namespaces
127+
namespaces, err := k8sClient.Clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
128+
if err != nil {
129+
fmt.Printf("%s %s%sError listing namespaces: %v%s\n",
130+
utils.ErrorEmoji, utils.Red, utils.Bold, err, utils.Reset)
131+
os.Exit(1)
132+
}
133+
134+
// Create parent backup directory
135+
timestamp := time.Now().Format("02Jan2006-15:04")
136+
parentBackupDir := filepath.Join(outputDir, timestamp, "all-namespaces")
137+
138+
if err := os.MkdirAll(parentBackupDir, 0755); err != nil {
139+
fmt.Printf("%s %s%sError creating output directory: %v%s\n",
140+
utils.ErrorEmoji, utils.Red, utils.Bold, err, utils.Reset)
141+
os.Exit(1)
142+
}
143+
144+
fmt.Printf("%s %s%sStarting backup of all namespaces to '%s'%s\n\n",
145+
utils.StartEmoji, utils.Blue, utils.Bold, parentBackupDir, utils.Reset)
146+
147+
totalResourceCount := 0
148+
totalErrorCount := 0
149+
150+
// Process each namespace
151+
for _, ns := range namespaces.Items {
152+
nsName := ns.Name
153+
nsBackupDir := filepath.Join(parentBackupDir, nsName)
154+
155+
if err := os.MkdirAll(nsBackupDir, 0755); err != nil {
156+
fmt.Printf("%s %s%sError creating directory for namespace %s: %v%s\n",
157+
utils.ErrorEmoji, utils.Red, utils.Bold, nsName, err, utils.Reset)
158+
totalErrorCount++
159+
continue
160+
}
161+
162+
// Prepare resource type filter
163+
selectedTypes := buildResourceTypeMap(resFlags)
164+
165+
fmt.Printf("%sProcessing namespace: %s%s\n",
166+
utils.Blue, nsName, utils.Reset)
167+
168+
// Perform backup for this namespace
169+
resourceCount, errorCount := backup.PerformBackup(k8sClient, nsName, nsBackupDir, selectedTypes, verbose)
170+
171+
totalResourceCount += resourceCount
172+
totalErrorCount += errorCount
173+
}
174+
175+
if totalResourceCount > 0 {
176+
fmt.Printf("\n%s %s%sBackup completed successfully to %s (%d resources total across all namespaces)%s\n",
177+
utils.SuccessEmoji, utils.Green, utils.Bold, parentBackupDir, totalResourceCount, utils.Reset)
178+
} else {
179+
fmt.Printf("\n%s %s%sNo resources found to backup in any namespace%s\n",
180+
utils.WarningEmoji, utils.Yellow, utils.Bold, utils.Reset)
181+
}
182+
183+
// Exit with error code if there were errors
184+
if totalErrorCount > 0 {
185+
fmt.Printf("%s %s%sCompleted with %d errors%s\n",
186+
utils.ErrorEmoji, utils.Red, utils.Bold, totalErrorCount, utils.Reset)
187+
os.Exit(1)
188+
}
189+
190+
os.Exit(0)
117191
}
118192

119193
// Create output directory with timestamp
@@ -145,7 +219,7 @@ func main() {
145219
utils.SuccessEmoji, utils.Green, utils.Bold, backupDir, resourceCount, utils.Reset)
146220
} else {
147221
fmt.Printf("\n%s %s%sNo resources found to backup in namespace '%s'%s\n",
148-
utils.InfoEmoji, utils.Yellow, utils.Bold, namespace, utils.Reset)
222+
utils.WarningEmoji, utils.Yellow, utils.Bold, namespace, utils.Reset)
149223
}
150224

151225
// Exit with error code if there were errors

pkg/backup/backup.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ func backupResourceType(k8sClient *client.K8sClient, namespace, backupDir string
173173
// Ensure the filename is valid for the filesystem
174174
safeName := ensureValidFilename(name)
175175
if safeName != name && verbose {
176-
fmt.Printf("%s%sResource name %q sanitized to %q for filesystem compatibility%s\n",
177-
utils.InfoEmoji, utils.BrightBlue, name, safeName, utils.Reset)
176+
fmt.Printf("%sResource name %q sanitized to %q for filesystem compatibility%s\n",
177+
utils.BrightBlue, name, safeName, utils.Reset)
178178
}
179179

180180
// Remove cluster-specific and runtime fields

pkg/utils/colors.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ const (
3030

3131
// Emojis for different message types
3232
const (
33-
InfoEmoji = "ℹ️"
34-
SuccessEmoji = ""
33+
InfoEmoji = ""
34+
SuccessEmoji = ""
3535
WarningEmoji = "⚠️"
3636
ErrorEmoji = "❌"
3737
BackupEmoji = "💾"

0 commit comments

Comments
 (0)