Skip to content
This repository was archived by the owner on Apr 5, 2024. It is now read-only.

Commit a45a956

Browse files
author
Lukas Knuth
committed
Added first draft of CLI.
This will find and scale all k8s resources currently mounting the PVC to be backed up.
1 parent a7abdbc commit a45a956

File tree

7 files changed

+917
-0
lines changed

7 files changed

+917
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# compield executable
2+
EzBackup

.tool-versions

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
golang 1.17

cmd/root.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
// rootCmd represents the base command when called without any subcommands
8+
var rootCmd = &cobra.Command{
9+
Use: "EzBackup",
10+
Short: "Easy backups of Kubernetes Persistent Volume Claim contents.",
11+
}
12+
13+
// Execute adds all child commands to the root command and sets flags appropriately.
14+
// This is called by main.main(). It only needs to happen once to the rootCmd.
15+
func Execute() {
16+
cobra.CheckErr(rootCmd.Execute())
17+
}
18+
19+
func init() {
20+
// Cobra also supports local flags, which will only run
21+
// when this action is called directly.
22+
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
23+
}

cmd/scale.go

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/spf13/cobra"
9+
10+
//appsv1 "k8s.io/api/apps/v1"
11+
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/client-go/kubernetes"
14+
"k8s.io/client-go/tools/clientcmd"
15+
)
16+
17+
// scaleCmd represents the scale command
18+
var scaleCmd = &cobra.Command{
19+
Use: "scale [PVC]",
20+
Short: "Finds any Pods accessing the given PVC and sacles them down through their Deployments/ReplicationSets",
21+
Long: `Depending on the data that is written to the Persistent Volume Claim, it can be required to stop the Pod
22+
writing to it first. An example is a database, which keeps data in-memory. A relyable way to flush this
23+
data to disc is to shut the application down.
24+
25+
For other use-cases like static asset hosting, this might not be required.`,
26+
Args: cobra.MinimumNArgs(1),
27+
Run: func(cmd *cobra.Command, args []string) {
28+
fmt.Printf("Attempting to scale down anything that uses %s\n", args[0])
29+
30+
config, err := clientcmd.BuildConfigFromFlags("", "/Users/lukasknuth/k3sup/kubeconfig")
31+
if err != nil {
32+
panic(err.Error())
33+
}
34+
35+
clientset, err := kubernetes.NewForConfig(config)
36+
if err != nil {
37+
panic(err.Error())
38+
}
39+
40+
// todo add namespace flag!
41+
// todo use another "context"!?
42+
43+
// ######
44+
pods, err := clientset.CoreV1().Pods("infrastructure").List(context.TODO(), metav1.ListOptions{})
45+
if err != nil {
46+
panic(err.Error())
47+
}
48+
49+
filtered := filterMountingPods(pods.Items, args[0])
50+
tree := make([]metav1.ObjectMeta, 0)
51+
for _, pod := range filtered {
52+
fmt.Printf("Mounted by %s: %s\n", "Pod", pod.Name)
53+
options := RequestOptions{Namespace: "infrastructure", Context: context.TODO(), Clientset: clientset}
54+
tree, err = dependencyTree(pod.ObjectMeta, tree, 1, &options)
55+
if err != nil {
56+
panic(err.Error())
57+
}
58+
}
59+
fmt.Printf("Should scale %d resources\n", len(tree))
60+
for _, res := range tree {
61+
fmt.Printf(" %s\n", res.Name)
62+
}
63+
},
64+
}
65+
66+
func init() {
67+
rootCmd.AddCommand(scaleCmd)
68+
69+
// Here you will define your flags and configuration settings.
70+
71+
// Cobra supports Persistent Flags which will work for this command
72+
// and all subcommands, e.g.:
73+
// scaleCmd.PersistentFlags().String("foo", "", "A help for foo")
74+
75+
// Cobra supports local flags which will only run when this command
76+
// is called directly, e.g.:
77+
//scaleCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
78+
}
79+
80+
func filterMountingPods(pods []corev1.Pod, pvcName string) []corev1.Pod {
81+
var filtered []corev1.Pod
82+
for _, pod := range pods {
83+
for _, vol := range pod.Spec.Volumes { // todo also filter by state + filter yourself (by hostname)
84+
if vol.VolumeSource.PersistentVolumeClaim != nil && vol.VolumeSource.PersistentVolumeClaim.ClaimName == pvcName {
85+
filtered = append(filtered, pod)
86+
}
87+
}
88+
}
89+
return filtered
90+
}
91+
92+
type RequestOptions struct {
93+
Clientset *kubernetes.Clientset
94+
Context context.Context
95+
Namespace string
96+
}
97+
98+
func dependencyTree(resource metav1.ObjectMeta, toScale []metav1.ObjectMeta, level int, options *RequestOptions) ([]metav1.ObjectMeta, error) {
99+
if len(resource.OwnerReferences) > 0 {
100+
for _, owner := range resource.OwnerReferences {
101+
ownerRes, err := fetchResource(&owner, options)
102+
if err != nil {
103+
return nil, err
104+
} else if ownerRes != nil {
105+
fmt.Printf("%s-> %s (%s)\n", strings.Repeat(" ", level), ownerRes.Name, "todo") // todo use wrappers instead?
106+
toScale, err = dependencyTree(*ownerRes, toScale, level + 1, options)
107+
if err != nil {
108+
return nil, err
109+
}
110+
} else {
111+
fmt.Printf("%s-> Unsupported owner %s of type %s\n", strings.Repeat(" ", level), owner.Name, owner.Kind)
112+
}
113+
}
114+
return toScale, nil
115+
} else {
116+
return append(toScale, resource), nil
117+
}
118+
}
119+
120+
func fetchResource(ref *metav1.OwnerReference, options *RequestOptions) (*metav1.ObjectMeta, error) {
121+
switch ref.Kind {
122+
case "ReplicaSet":
123+
rs, err := options.Clientset.AppsV1().ReplicaSets(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
124+
if err != nil {
125+
return nil, err
126+
} else {
127+
return &rs.ObjectMeta, nil
128+
}
129+
case "Deployment":
130+
d, err := options.Clientset.AppsV1().Deployments(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
131+
if err != nil {
132+
return nil, err
133+
} else {
134+
return &d.ObjectMeta, nil
135+
}
136+
case "Job":
137+
fallthrough
138+
case "CronJob":
139+
fallthrough
140+
case "DaemonSet":
141+
fallthrough
142+
case "StatefulSet":
143+
fallthrough
144+
default:
145+
return nil, nil
146+
}
147+
}

go.mod

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
module github.com/LukasKnuth/EzBackup
2+
3+
go 1.17
4+
5+
require (
6+
github.com/spf13/cobra v1.2.1
7+
k8s.io/apimachinery v0.22.1
8+
k8s.io/client-go v0.22.1
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/go-logr/logr v0.4.0 // indirect
14+
github.com/gogo/protobuf v1.3.2 // indirect
15+
github.com/golang/protobuf v1.5.2 // indirect
16+
github.com/google/go-cmp v0.5.5 // indirect
17+
github.com/google/gofuzz v1.1.0 // indirect
18+
github.com/googleapis/gnostic v0.5.5 // indirect
19+
github.com/imdario/mergo v0.3.5 // indirect
20+
github.com/inconshreveable/mousetrap v1.0.0 // indirect
21+
github.com/json-iterator/go v1.1.11 // indirect
22+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
23+
github.com/modern-go/reflect2 v1.0.1 // indirect
24+
github.com/spf13/pflag v1.0.5 // indirect
25+
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect
26+
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
27+
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
28+
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
29+
golang.org/x/text v0.3.6 // indirect
30+
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
31+
google.golang.org/appengine v1.6.7 // indirect
32+
google.golang.org/protobuf v1.26.0 // indirect
33+
gopkg.in/inf.v0 v0.9.1 // indirect
34+
gopkg.in/yaml.v2 v2.4.0 // indirect
35+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
36+
k8s.io/api v0.22.1 // indirect
37+
k8s.io/klog/v2 v2.9.0 // indirect
38+
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9 // indirect
39+
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
40+
sigs.k8s.io/yaml v1.2.0 // indirect
41+
)

0 commit comments

Comments
 (0)