Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new flag to remove state information #1

Merged
merged 1 commit into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Usage of kubedump:
namespace to dump (e.g. 'ns1,ns2'), empty for all
-resources string
resource to dump (e.g. 'configmaps,secrets'), empty for all
-stateless
remove fields containing a state of the resource (default true)
-version
print version information of this release
```
65 changes: 62 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func main() {
namespacesFlag = flag.String("namespaces", "", "namespace to dump (e.g. 'ns1,ns2'), empty for all")
clusterscopedFlag = flag.Bool("clusterscoped", true, "dump cluster-wide resources")
namespacedFlag = flag.Bool("namespaced", true, "dump namespaced resources")
statelessFlag = flag.Bool("stateless", true, "remove fields containing a state of the resource")
versionFlag = flag.Bool("version", false, fmt.Sprintf("print version information of this release (%v)", version))
)
flag.Parse()
Expand Down Expand Up @@ -129,7 +130,7 @@ func main() {
continue
}

if err := writeYAML(*outdirFlag, res.Name, *item); err != nil {
if err := writeYAML(*outdirFlag, res.Name, *item, *statelessFlag); err != nil {
log.Printf("failed writing %v: %v\n", namespacedName, err)
continue
}
Expand All @@ -140,8 +141,11 @@ func main() {
}

// TODO: check if we can get the resourceName from the item
// TODO: remove status etc. from the output?
func writeYAML(outDir, resourceName string, item unstructured.Unstructured) error {
func writeYAML(outDir, resourceName string, item unstructured.Unstructured, stateless bool) error {
if stateless {
cleanState(item)
}

yamlBytes, err := yaml.Marshal(item.Object)
if err != nil {
return fmt.Errorf("failed marshalling: %v", err)
Expand All @@ -166,6 +170,61 @@ func writeYAML(outDir, resourceName string, item unstructured.Unstructured) erro
return nil
}

func cleanState(item unstructured.Unstructured) {
// partially based on https://github.com/WoozyMasta/kube-dump/blob/f1ae560a8b9da8dba1c28619f38089d40d0d2357/kube-dump#L334

// cluster-scoped and namespaced
item.Object = deleteField(item.Object, "metadata", "annotations", "control-plane.alpha.kubernetes.io/leader")
item.Object = deleteField(item.Object, "metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")
item.Object = deleteField(item.Object, "metadata", "creationTimestamp")
item.Object = deleteField(item.Object, "metadata", "finalizers")
item.Object = deleteField(item.Object, "metadata", "generation")
item.Object = deleteField(item.Object, "metadata", "managedFields")
item.Object = deleteField(item.Object, "metadata", "resourceVersion")
item.Object = deleteField(item.Object, "metadata", "selfLink")
item.Object = deleteField(item.Object, "metadata", "ownerReferences")
item.Object = deleteField(item.Object, "metadata", "uid")
item.Object = deleteField(item.Object, "status")

if item.GetNamespace() == "" {
// cluster-scoped only
} else {
// namespaced only
item.Object = deleteField(item.Object, "metadata", "annotations", "autoscaling.alpha.kubernetes.io/conditions")
item.Object = deleteField(item.Object, "metadata", "annotations", "autoscaling.alpha.kubernetes.io/current-metrics")
item.Object = deleteField(item.Object, "metadata", "annotations", "deployment.kubernetes.io/revision")
item.Object = deleteField(item.Object, "metadata", "annotations", "kubernetes.io/config.seen")
item.Object = deleteField(item.Object, "metadata", "annotations", "kubernetes.io/service-account.uid")
item.Object = deleteField(item.Object, "metadata", "annotations", "pv.kubernetes.io/bind-completed")
item.Object = deleteField(item.Object, "metadata", "annotations", "pv.kubernetes.io/bound-by-controller")
item.Object = deleteField(item.Object, "metadata", "clusterIP")
item.Object = deleteField(item.Object, "metadata", "progressDeadlineSeconds")
item.Object = deleteField(item.Object, "metadata", "revisionHistoryLimit")
item.Object = deleteField(item.Object, "metadata", "spec", "metadata", "annotations", "kubectl.kubernetes.io/restartedAt")
item.Object = deleteField(item.Object, "metadata", "spec", "metadata", "creationTimestamp")
item.Object = deleteField(item.Object, "spec", "volumeName")
item.Object = deleteField(item.Object, "spec", "volumeMode")
}
}

func deleteField(object map[string]interface{}, path ...string) map[string]interface{} {
if len(path) == 0 {
return object
}
if len(path) == 1 {
delete(object, path[0])
return object
}

subObj, ok := object[path[0]].(map[string]interface{})
if !ok {
return object
}

object[path[0]] = deleteField(subObj, path[1:]...)
return object
}

// https://github.com/kubernetes/client-go/issues/192#issuecomment-349564767
func buildConfigFromFlags(context, kubeconfigPath string) (*rest.Config, error) {
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
Expand Down
55 changes: 55 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"reflect"
"testing"
)

func TestDeleteField(t *testing.T) {
type args struct {
object map[string]interface{}
path []string
}
tests := []struct {
name string
args args
want map[string]interface{}
}{
{
name: "nil",
want: nil,
},
{
name: "not found",
args: args{
object: map[string]interface{}{"Hello": "World"},
path: []string{"Bye"},
},
want: map[string]interface{}{"Hello": "World"},
},

{
name: "happy flat",
args: args{
object: map[string]interface{}{"Hello": "World"},
path: []string{"Hello"},
},
want: map[string]interface{}{},
},
{
name: "happy nested",
args: args{
object: map[string]interface{}{"Hello": map[string]interface{}{"My": "World"}},
path: []string{"Hello", "My"},
},
want: map[string]interface{}{"Hello": map[string]interface{}{}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := deleteField(tt.args.object, tt.args.path...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("deleteField() = %v, want %v", got, tt.want)
}
})
}
}