Skip to content

Commit 817d0b9

Browse files
authoredApr 20, 2023
Add ignore flags (#9)
1 parent 942741d commit 817d0b9

File tree

3 files changed

+273
-35
lines changed

3 files changed

+273
-35
lines changed
 

‎README.md

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ Usage of kubedump:
3636
context from the kubeconfig, empty for default
3737
-dir string
3838
output directory for the dumps (default "dump")
39+
-ignore-namespaces string
40+
namespace to ignore (e.g. 'ns1,ns2')
41+
-ignore-resources string
42+
resource to ignore (e.g. 'configmaps,secrets')
3943
-namespaced
4044
dump namespaced resources (default true)
4145
-namespaces string

‎main.go

+66-35
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,26 @@ var (
2929
)
3030

3131
func main() {
32+
start := time.Now()
33+
3234
homeDir, err := os.UserHomeDir()
3335
if err != nil {
3436
log.Fatalf("failed getting user home dir: %v\n", err)
3537
}
3638

3739
var (
38-
start = time.Now()
39-
40-
kubeConfigPath = flag.String("config", filepath.Join(homeDir, ".kube", "config"), "path to the kubeconfig")
41-
kubeContext = flag.String("context", "", "context from the kubeconfig, empty for default")
42-
outdirFlag = flag.String("dir", "dump", "output directory for the dumps")
43-
resourcesFlag = flag.String("resources", "", "resource to dump (e.g. 'configmaps,secrets'), empty for all")
44-
namespacesFlag = flag.String("namespaces", "", "namespace to dump (e.g. 'ns1,ns2'), empty for all")
45-
clusterscopedFlag = flag.Bool("clusterscoped", true, "dump cluster-wide resources")
46-
namespacedFlag = flag.Bool("namespaced", true, "dump namespaced resources")
47-
statelessFlag = flag.Bool("stateless", true, "remove fields containing a state of the resource")
48-
verboseFlag = flag.Bool("verbose", false, "output the current progress")
49-
versionFlag = flag.Bool("version", false, fmt.Sprintf("print version information of this release (%v)", version))
40+
kubeConfigPath = flag.String("config", filepath.Join(homeDir, ".kube", "config"), "path to the kubeconfig")
41+
kubeContext = flag.String("context", "", "context from the kubeconfig, empty for default")
42+
outdirFlag = flag.String("dir", "dump", "output directory for the dumps")
43+
resourcesFlag = flag.String("resources", "", "resource to dump (e.g. 'configmaps,secrets'), empty for all")
44+
ignoreResourcesFlag = flag.String("ignore-resources", "", "resource to ignore (e.g. 'configmaps,secrets')")
45+
namespacesFlag = flag.String("namespaces", "", "namespace to dump (e.g. 'ns1,ns2'), empty for all")
46+
ignoreNamespacesFlag = flag.String("ignore-namespaces", "", "namespace to ignore (e.g. 'ns1,ns2')")
47+
clusterscopedFlag = flag.Bool("clusterscoped", true, "dump cluster-wide resources")
48+
namespacedFlag = flag.Bool("namespaced", true, "dump namespaced resources")
49+
statelessFlag = flag.Bool("stateless", true, "remove fields containing a state of the resource")
50+
verboseFlag = flag.Bool("verbose", false, "output the current progress")
51+
versionFlag = flag.Bool("version", false, fmt.Sprintf("print version information of this release (%v)", version))
5052
)
5153
flag.Parse()
5254

@@ -58,8 +60,10 @@ func main() {
5860
}
5961

6062
var (
61-
wantResources = strings.Split(strings.ToLower(*resourcesFlag), ",")
62-
wantNamespaces = strings.Split(strings.ToLower(*namespacesFlag), ",")
63+
wantResources = strings.Split(strings.ToLower(*resourcesFlag), ",")
64+
wantNamespaces = strings.Split(strings.ToLower(*namespacesFlag), ",")
65+
ignoreResources = strings.Split(strings.ToLower(*ignoreResourcesFlag), ",")
66+
ignoreNamespaces = strings.Split(strings.ToLower(*ignoreNamespacesFlag), ",")
6367
)
6468

6569
kubeConfig, err := buildConfigFromFlags(*kubeContext, *kubeConfigPath)
@@ -92,19 +96,7 @@ func main() {
9296
}
9397

9498
for _, res := range resources.APIResources {
95-
if !slices.Contains(res.Verbs, "get") {
96-
// we can't get the resource, so let's skip it
97-
continue
98-
}
99-
100-
if strings.Contains(res.Name, "/") {
101-
// skip subresources
102-
// TODO: probably there is a better way to not get them in the first place
103-
continue
104-
}
105-
106-
// check if we got the specified resources (if any resources were specified)
107-
if *resourcesFlag != "" && !slices.Contains(wantResources, res.Name) {
99+
if skipResource(res, wantResources, ignoreResources) {
108100
continue
109101
}
110102

@@ -125,14 +117,7 @@ func main() {
125117
}
126118

127119
for _, listItem := range unstrList.Items {
128-
// filter according to flags
129-
if listItem.GetNamespace() != "" && !*namespacedFlag {
130-
continue
131-
}
132-
if listItem.GetNamespace() == "" && !*clusterscopedFlag {
133-
continue
134-
}
135-
if *namespacesFlag != "" && !slices.Contains(wantNamespaces, listItem.GetNamespace()) {
120+
if skipItem(listItem, *namespacedFlag, *clusterscopedFlag, wantNamespaces, ignoreNamespaces) {
136121
continue
137122
}
138123

@@ -162,6 +147,52 @@ func main() {
162147
fmt.Printf("loaded %d manifests in %v\n", written, time.Since(start).Round(1*time.Millisecond))
163148
}
164149

150+
func skipResource(res metav1.APIResource, wantResources, ignoreResources []string) bool {
151+
// check if we can even 'get' the resource
152+
if !slices.Contains(res.Verbs, "get") {
153+
return true
154+
}
155+
156+
// skip subresources
157+
// TODO: maybe there is a better way to not get them in the first place
158+
if strings.Contains(res.Name, "/") {
159+
return true
160+
}
161+
162+
// check if we got the specified resources (if any resources were specified)
163+
if len(wantResources) > 0 && wantResources[0] != "" && !slices.Contains(wantResources, res.Name) {
164+
return true
165+
}
166+
167+
// check if we got a resource to ignore (if any resources were specified)
168+
if len(ignoreResources) > 0 && ignoreResources[0] != "" && slices.Contains(ignoreResources, res.Name) {
169+
return true
170+
}
171+
172+
return false
173+
}
174+
175+
func skipItem(item unstructured.Unstructured, namespaced, clusterscoped bool, wantNamespaces, ignoreNamespaces []string) bool {
176+
// item with namespace but we skip namespaced items
177+
if item.GetNamespace() != "" && !namespaced {
178+
return true
179+
}
180+
// item clusterscoped but we skip them
181+
if item.GetNamespace() == "" && !clusterscoped {
182+
return true
183+
}
184+
// specific namespaces specied but doesn't match
185+
if len(wantNamespaces) > 0 && wantNamespaces[0] != "" && !slices.Contains(wantNamespaces, item.GetNamespace()) {
186+
return true
187+
}
188+
// ignore specific namespaces and it matches
189+
if len(ignoreNamespaces) > 0 && ignoreNamespaces[0] != "" && slices.Contains(ignoreNamespaces, item.GetNamespace()) {
190+
return true
191+
}
192+
193+
return false
194+
}
195+
165196
func writeYAML(outDir, resourceAndGroup string, item unstructured.Unstructured, stateless bool) error {
166197
if stateless {
167198
cleanState(item)

‎main_test.go

+203
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package main
33
import (
44
"reflect"
55
"testing"
6+
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
69
)
710

811
func TestDeleteField(t *testing.T) {
@@ -53,3 +56,203 @@ func TestDeleteField(t *testing.T) {
5356
})
5457
}
5558
}
59+
60+
func TestSkipResource(t *testing.T) {
61+
type args struct {
62+
res metav1.APIResource
63+
wantResources []string
64+
ignoreResources []string
65+
}
66+
tests := []struct {
67+
name string
68+
args args
69+
skip bool
70+
}{
71+
{
72+
name: "empty",
73+
skip: true,
74+
},
75+
{
76+
name: "verb other",
77+
args: args{
78+
res: metav1.APIResource{Verbs: metav1.Verbs{"other"}},
79+
},
80+
skip: true,
81+
},
82+
{
83+
name: "verb get",
84+
args: args{
85+
res: metav1.APIResource{Verbs: metav1.Verbs{"get"}},
86+
},
87+
skip: false,
88+
},
89+
{
90+
name: "subresource",
91+
args: args{
92+
res: metav1.APIResource{Name: "resource/subresource", Verbs: metav1.Verbs{"get"}},
93+
},
94+
skip: true,
95+
},
96+
97+
{
98+
name: "empty string want/ignore",
99+
args: args{
100+
res: metav1.APIResource{
101+
Name: "myresource",
102+
Verbs: metav1.Verbs{"get"},
103+
},
104+
wantResources: []string{""},
105+
ignoreResources: []string{""},
106+
},
107+
skip: false,
108+
},
109+
{
110+
name: "want resource match",
111+
args: args{
112+
res: metav1.APIResource{
113+
Name: "myresource",
114+
Verbs: metav1.Verbs{"get"},
115+
},
116+
wantResources: []string{"myresource"},
117+
},
118+
skip: false,
119+
},
120+
{
121+
name: "want resource don't match",
122+
args: args{
123+
res: metav1.APIResource{
124+
Name: "not-myresource",
125+
Verbs: metav1.Verbs{"get"},
126+
},
127+
wantResources: []string{"myresource"},
128+
},
129+
skip: true,
130+
},
131+
{
132+
name: "ignore resource match",
133+
args: args{
134+
res: metav1.APIResource{
135+
Name: "myresource",
136+
Verbs: metav1.Verbs{"get"},
137+
},
138+
ignoreResources: []string{"myresource"},
139+
},
140+
skip: true,
141+
},
142+
{
143+
name: "ignore resource don't match",
144+
args: args{
145+
res: metav1.APIResource{
146+
Name: "not-myresource",
147+
Verbs: metav1.Verbs{"get"},
148+
},
149+
ignoreResources: []string{"myresource"},
150+
},
151+
skip: false,
152+
},
153+
}
154+
for _, tt := range tests {
155+
t.Run(tt.name, func(t *testing.T) {
156+
if got := skipResource(tt.args.res, tt.args.wantResources, tt.args.ignoreResources); got != tt.skip {
157+
t.Errorf("ignoreResource() = %v, want %v", got, tt.skip)
158+
}
159+
})
160+
}
161+
}
162+
163+
func TestSkipItem(t *testing.T) {
164+
type args struct {
165+
item unstructured.Unstructured
166+
namespaced bool
167+
clusterscoped bool
168+
wantNamespaces []string
169+
ignoreNamespaces []string
170+
}
171+
172+
namespacedTestItem := unstructured.Unstructured{}
173+
namespacedTestItem.SetNamespace("mynamespace")
174+
175+
tests := []struct {
176+
name string
177+
args args
178+
skip bool
179+
}{
180+
{
181+
name: "empty",
182+
skip: true,
183+
},
184+
{
185+
name: "clusterscoped happy",
186+
args: args{
187+
clusterscoped: true,
188+
},
189+
skip: false,
190+
},
191+
{
192+
name: "clusterscoped fail",
193+
args: args{
194+
item: namespacedTestItem,
195+
clusterscoped: true,
196+
},
197+
skip: true,
198+
},
199+
{
200+
name: "namespaced fail",
201+
args: args{
202+
namespaced: true,
203+
},
204+
skip: true,
205+
},
206+
{
207+
name: "namespaced happy",
208+
args: args{
209+
item: namespacedTestItem,
210+
namespaced: true,
211+
},
212+
skip: false,
213+
},
214+
{
215+
name: "want namespace happy",
216+
args: args{
217+
item: namespacedTestItem,
218+
namespaced: true,
219+
wantNamespaces: []string{namespacedTestItem.GetNamespace()},
220+
},
221+
skip: false,
222+
},
223+
{
224+
name: "want namespace fail",
225+
args: args{
226+
item: namespacedTestItem,
227+
namespaced: true,
228+
wantNamespaces: []string{"fail-namespace"},
229+
},
230+
skip: true,
231+
},
232+
{
233+
name: "ignore namespaces don't match",
234+
args: args{
235+
item: namespacedTestItem,
236+
namespaced: true,
237+
ignoreNamespaces: []string{"other-namespace"},
238+
},
239+
skip: false,
240+
},
241+
{
242+
name: "ignore namespaces match",
243+
args: args{
244+
item: namespacedTestItem,
245+
namespaced: true,
246+
ignoreNamespaces: []string{namespacedTestItem.GetNamespace()},
247+
},
248+
skip: true,
249+
},
250+
}
251+
for _, tt := range tests {
252+
t.Run(tt.name, func(t *testing.T) {
253+
if got := skipItem(tt.args.item, tt.args.namespaced, tt.args.clusterscoped, tt.args.wantNamespaces, tt.args.ignoreNamespaces); got != tt.skip {
254+
t.Errorf("ignoreItem() = %v, want %v", got, tt.skip)
255+
}
256+
})
257+
}
258+
}

0 commit comments

Comments
 (0)
Please sign in to comment.