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

feat(namespaces): add empty namespace detection and removal #249

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ada2093
rebase on main
isindir Mar 8, 2025
e3e7c42
Fix typo
isindir Jul 7, 2024
3b6f330
Fix typo
isindir Nov 26, 2024
458dee4
rebase to main
isindir Nov 26, 2024
e7e67e7
Fix typo
isindir Jan 23, 2025
dafe0d6
refactor filtering
isindir Feb 9, 2025
3a432cc
remove custom filters
isindir Feb 10, 2025
c06edee
Fix typo
isindir Feb 10, 2025
e4d9775
Add extra default namespaces to exceptions list
isindir Feb 17, 2025
5881fa6
Squeze all params to one line
isindir Feb 17, 2025
6f1f976
Rename GenericResource to NamespacedResource, inner NamespacedName to…
isindir Feb 17, 2025
0f32635
Process kor/used label before isErrorOrNamespaceContainsResources
isindir Feb 17, 2025
367b4f7
Change error message for GroupVersion slice length
isindir Feb 17, 2025
b859c8d
Rename isErrorOrNamespaceContainsResources func to isNamespaceUsed
isindir Feb 17, 2025
5d09d32
result of go mod tidy
isindir Feb 17, 2025
4e5afc8
Remove ignorePredefinedResource() function
isindir Feb 17, 2025
5e6bf61
Add group-by for namespaces
isindir Feb 17, 2025
6150e2e
moving splitting GroupVersion into getGV function
isindir Mar 8, 2025
95d4c33
fixing names of unstructuredList & unstructuredObj - resourcesInNames…
isindir Mar 8, 2025
6ea0812
Moving unwanted function isNamespaceNotEmpty logic to function IsName…
isindir Mar 8, 2025
ec08455
removing _namespace_ from function names to match Test<FuncUnderTestN…
isindir Mar 8, 2025
8071420
rename namespace test file
isindir Mar 8, 2025
39d81b6
add TestGetUnusedNamespaces
isindir Mar 9, 2025
97c6935
adding new tests and fixing namespace test suite - bumped in an unexp…
isindir Mar 9, 2025
3d64999
Fix typo
isindir Mar 9, 2025
8f97cba
Fix typo
isindir Mar 9, 2025
04c198f
Fix typo
isindir Mar 9, 2025
20a50bc
adding edge case test for kube-system kor unused
isindir Mar 10, 2025
8eb17b5
Fix typo
isindir Mar 10, 2025
04306ab
Fix typo
isindir Mar 10, 2025
199343d
remove TODO for tests - filtering already tested elsewhere, small ref…
isindir Mar 12, 2025
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
Prev Previous commit
Next Next commit
Fix typo
  • Loading branch information
isindir committed Mar 12, 2025
commit 3d64999f0fba03fb7313bbc73e4dc336913bca49
13 changes: 5 additions & 8 deletions pkg/kor/namespaces.go
Original file line number Diff line number Diff line change
@@ -148,14 +148,11 @@ func isNamespaceUsed(ctx context.Context, clientset kubernetes.Interface, dynami
continue
}

// TODO: ignore resources within exception list (any resource type from what kor supports at any given time)
// exceptionFound, err := isResourceException(resource.Identifier.Name, resource.Identifier.Namespace, config.ExceptionNamespaces)
// if err != nil {
// return nil, err
// }
// if exceptionFound {
// continue
// }
// TODO: decide if this is sufficient
// ignore default ServiceAccount
if resource.GVR.Resource == "serviceaccounts" && resource.Identifier.Name == "default" {
continue
}

return true, nil
}
128 changes: 127 additions & 1 deletion pkg/kor/namespaces_GetUnusedNamespaces_test.go
Original file line number Diff line number Diff line change
@@ -134,7 +134,106 @@ func createEmptyNamespace(ctx context.Context, t *testing.T) (kubernetes.Interfa
{Group: "events.k8s.io", Version: "v1", Resource: "events"}: "EventList",
{Group: "", Version: "v1", Resource: "events"}: "EventList",
}
dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, namespace1, namespace2)
dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, namespace1, namespace2, newEventType)

return clientset, dynamicClient
}

func createNonEmptyNamespaceLabeledAsUnused(ctx context.Context, t *testing.T) (kubernetes.Interface, *dynamicfake.FakeDynamicClient) {
realClientset := fake.NewSimpleClientset()
fakeDisc := &fakeHappyDiscovery{discoveryfake.FakeDiscovery{Fake: &realClientset.Fake}}
clientset := &fakeClientset{Interface: realClientset, discovery: fakeDisc}
scheme := getNamespaceTestSchema(t)

ns1 := "nonempty-namespace-labeled"
namespace1 := defineNamespaceObject(ns1)
namespace1.Labels = map[string]string{
"kor/used": "false",
}
_, err := clientset.CoreV1().Namespaces().Create(ctx, namespace1, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Failed to create test namespace: %v", err)
}

sa1 := "my-app"
serviceAccount1 := defineServiceAccountObject(ns1, sa1)
_, err = clientset.CoreV1().ServiceAccounts(ns1).Create(ctx, serviceAccount1, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Failed to create test namespace: %v", err)
}

ns2 := "test-namespace"
namespace2 := defineNamespaceObject(ns2)
_, err = clientset.CoreV1().Namespaces().Create(ctx, namespace2, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Failed to create test namespace: %v", err)
}

sa2 := "another-app"
serviceAccount2 := defineServiceAccountObject(ns2, sa2)
_, err = clientset.CoreV1().ServiceAccounts(ns2).Create(ctx, serviceAccount2, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Failed to create test namespace: %v", err)
}

listKinds := map[schema.GroupVersionResource]string{
{Group: "apps", Version: "v1", Resource: "deployments"}: "DeploymentList",
{Group: "", Version: "v1", Resource: "namespaces"}: "NamespaceList",
{Group: "events.k8s.io", Version: "v1", Resource: "events"}: "EventList",
{Group: "", Version: "v1", Resource: "events"}: "EventList",
}
dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, namespace1, namespace2, serviceAccount1, serviceAccount2)

return clientset, dynamicClient
}

func createEmptyNamespaceLabeledAsUsed(ctx context.Context, t *testing.T) (kubernetes.Interface, *dynamicfake.FakeDynamicClient) {
realClientset := fake.NewSimpleClientset()
fakeDisc := &fakeHappyDiscovery{discoveryfake.FakeDiscovery{Fake: &realClientset.Fake}}
clientset := &fakeClientset{Interface: realClientset, discovery: fakeDisc}
scheme := getNamespaceTestSchema(t)

ns1 := "empty-namespace-labeled"
namespace1 := defineNamespaceObject(ns1)
namespace1.Labels = map[string]string{
"kor/used": "true",
}
_, err := clientset.CoreV1().Namespaces().Create(ctx, namespace1, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Failed to create test namespace: %v", err)
}

listKinds := map[schema.GroupVersionResource]string{
{Group: "apps", Version: "v1", Resource: "deployments"}: "DeploymentList",
{Group: "", Version: "v1", Resource: "namespaces"}: "NamespaceList",
{Group: "events.k8s.io", Version: "v1", Resource: "events"}: "EventList",
{Group: "", Version: "v1", Resource: "events"}: "EventList",
}
dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, namespace1)

return clientset, dynamicClient
}

func createKubeSystemNamespace(ctx context.Context, t *testing.T) (kubernetes.Interface, *dynamicfake.FakeDynamicClient) {
realClientset := fake.NewSimpleClientset()
fakeDisc := &fakeHappyDiscovery{discoveryfake.FakeDiscovery{Fake: &realClientset.Fake}}
clientset := &fakeClientset{Interface: realClientset, discovery: fakeDisc}
scheme := getNamespaceTestSchema(t)

ns1 := "kube-system"
namespace1 := defineNamespaceObject(ns1)
_, err := clientset.CoreV1().Namespaces().Create(ctx, namespace1, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Failed to create test namespace: %v", err)
}

listKinds := map[schema.GroupVersionResource]string{
{Group: "apps", Version: "v1", Resource: "deployments"}: "DeploymentList",
{Group: "", Version: "v1", Resource: "namespaces"}: "NamespaceList",
{Group: "events.k8s.io", Version: "v1", Resource: "events"}: "EventList",
{Group: "", Version: "v1", Resource: "events"}: "EventList",
}
dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, namespace1)

return clientset, dynamicClient
}
@@ -184,6 +283,33 @@ func TestGetUnusedNamespaces(t *testing.T) {
expectedOutput: `{}`,
expectedError: false,
},
{
name: "Nonempty Namespace contains kor/used=false label",
getClientsFunc: createNonEmptyNamespaceLabeledAsUnused,
filterOpts: &filters.Options{},
expectedOutput: `{
"": {
"Namespace": [
"nonempty-namespace-labeled"
]
}
}`,
expectedError: false,
},
{
name: "Empty Namespace contains kor/used=true label",
getClientsFunc: createEmptyNamespaceLabeledAsUsed,
filterOpts: &filters.Options{},
expectedOutput: `{}`,
expectedError: false,
},
{
name: "kube-system special Namespace",
getClientsFunc: createKubeSystemNamespace,
filterOpts: &filters.Options{},
expectedOutput: `{}`,
expectedError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {