Skip to content

Commit

Permalink
Add PVC support (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
micnncim authored Sep 17, 2020
1 parent 405bf7e commit c23fe28
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 31 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Supported resources:
- [x] ConfigMaps (not used in any Pods)
- [x] Secrets (not used in any Pods or ServiceAccounts)
- [x] Pods (whose status is not `Running`)
- [x] PersistentVolumeClaim (not used in any Pods)
- [ ] PodDisruptionBudgets
- [ ] HorizontalPodAutoscalers

Expand Down
56 changes: 39 additions & 17 deletions pkg/cmd/determiner.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,31 @@ import (
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes"
)

const (
kindConfigMap = "ConfigMap"
kindSecret = "Secret"
kindPod = "Pod"
kindConfigMap = "ConfigMap"
kindSecret = "Secret"
kindPod = "Pod"
kindPersistentVolumeClaim = "PersistentVolumeClaim"
)

// determiner determines whether a resource should be pruned.
type determiner struct {
usedConfigMaps map[string]struct{} // key=ConfigMap.Name
usedSecrets map[string]struct{} // key=Secret.Name
usedConfigMaps map[string]struct{} // key=ConfigMap.Name
usedSecrets map[string]struct{} // key=Secret.Name
usedPersistentVolumeClaims map[string]struct{} // key=PersistentVolumeClaim.Name

pods []*corev1.Pod
}

func newDeterminer(clientset *kubernetes.Clientset, r *resource.Result, namespace string) (*determiner, error) {
var (
pruneConfigMaps bool
pruneSecrets bool
prunePods bool
pruneConfigMaps bool
pruneSecrets bool
prunePersistentVolumeClaims bool
)

if err := r.Visit(func(info *resource.Info, err error) error {
Expand All @@ -37,8 +38,8 @@ func newDeterminer(clientset *kubernetes.Clientset, r *resource.Result, namespac
pruneConfigMaps = true
case kindSecret:
pruneSecrets = true
case kindPod:
prunePods = true
case kindPersistentVolumeClaim:
prunePersistentVolumeClaims = true
}
return nil
}); err != nil {
Expand All @@ -49,7 +50,7 @@ func newDeterminer(clientset *kubernetes.Clientset, r *resource.Result, namespac

ctx := context.Background()

if pruneConfigMaps || pruneSecrets || prunePods {
if pruneConfigMaps || pruneSecrets || prunePersistentVolumeClaims {
var err error
d.pods, err = listPods(ctx, clientset, namespace)
if err != nil {
Expand All @@ -69,6 +70,10 @@ func newDeterminer(clientset *kubernetes.Clientset, r *resource.Result, namespac
d.usedSecrets = detectUsedSecrets(d.pods, sas)
}

if prunePersistentVolumeClaims {
d.usedPersistentVolumeClaims = detectUsedPersistentVolumeClaims(d.pods)
}

return d, nil
}

Expand All @@ -85,12 +90,14 @@ func (d *determiner) determinePrune(info *resource.Info) (bool, error) {
return true, nil
}

case kindPersistentVolumeClaim:
if _, ok := d.usedPersistentVolumeClaims[info.Name]; !ok {
return true, nil
}

case kindPod:
var pod corev1.Pod
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(
info.Object.(runtime.Unstructured).UnstructuredContent(),
&pod,
); err != nil {
pod, err := infoToPod(info)
if err != nil {
return false, err
}

Expand Down Expand Up @@ -184,3 +191,18 @@ func detectUsedSecrets(pods []*corev1.Pod, sas []*corev1.ServiceAccount) map[str

return usedSecrets
}

func detectUsedPersistentVolumeClaims(pods []*corev1.Pod) map[string]struct{} {
usedPersistentVolumeClaims := make(map[string]struct{})

for _, pod := range pods {
for _, volume := range pod.Spec.Volumes {
if volume.PersistentVolumeClaim == nil {
continue
}
usedPersistentVolumeClaims[volume.PersistentVolumeClaim.ClaimName] = struct{}{}
}
}

return usedPersistentVolumeClaims
}
86 changes: 72 additions & 14 deletions pkg/cmd/determiner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ import (
)

func Test_determiner_determinePrune(t *testing.T) {
const (
fakeConfigMap = "fake-cm"
fakeSecret = "fake-secret"
fakePersistentVolumeClaim = "fake-pvc"
)

type fields struct {
usedConfigMaps map[string]struct{}
usedSecrets map[string]struct{}
pods []*corev1.Pod
usedConfigMaps map[string]struct{}
usedSecrets map[string]struct{}
usedPersistentVolumes map[string]struct{}
pods []*corev1.Pod
}
type args struct {
info *resource.Info
Expand All @@ -29,7 +36,7 @@ func Test_determiner_determinePrune(t *testing.T) {
name: "configmap should be pruned when it is used",
fields: fields{
usedConfigMaps: map[string]struct{}{
"fake-cm": {},
fakeConfigMap: {},
},
pods: []*corev1.Pod{
{
Expand All @@ -40,7 +47,7 @@ func Test_determiner_determinePrune(t *testing.T) {
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "fake-cm",
Name: fakeConfigMap,
},
},
},
Expand All @@ -58,7 +65,7 @@ func Test_determiner_determinePrune(t *testing.T) {
Kind: kindConfigMap,
},
},
Name: "fake-cm",
Name: fakeConfigMap,
},
},
want: false,
Expand All @@ -73,7 +80,7 @@ func Test_determiner_determinePrune(t *testing.T) {
Kind: kindConfigMap,
},
},
Name: "fake-cm",
Name: fakeConfigMap,
},
},
want: true,
Expand All @@ -83,7 +90,7 @@ func Test_determiner_determinePrune(t *testing.T) {
name: "secret should be pruned when it is used",
fields: fields{
usedSecrets: map[string]struct{}{
"fake-secret": {},
fakeSecret: {},
},
pods: []*corev1.Pod{
{
Expand All @@ -94,7 +101,7 @@ func Test_determiner_determinePrune(t *testing.T) {
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "fake-secret",
Name: fakeSecret,
},
},
},
Expand All @@ -112,7 +119,7 @@ func Test_determiner_determinePrune(t *testing.T) {
Kind: kindSecret,
},
},
Name: "fake-secret",
Name: fakeSecret,
},
},
want: false,
Expand All @@ -127,7 +134,57 @@ func Test_determiner_determinePrune(t *testing.T) {
Kind: kindSecret,
},
},
Name: "fake-secret",
Name: fakeSecret,
},
},
want: true,
wantErr: false,
},
{
name: "pvc should be pruned when it is used",
fields: fields{
usedPersistentVolumes: map[string]struct{}{
fakePersistentVolumeClaim: {},
},
pods: []*corev1.Pod{
{
Spec: corev1.PodSpec{
Volumes: []corev1.Volume{
{
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: fakePersistentVolumeClaim,
},
},
},
},
},
},
},
},
args: args{
info: &resource.Info{
Object: &corev1.PersistentVolume{
TypeMeta: metav1.TypeMeta{
Kind: kindPersistentVolumeClaim,
},
},
Name: fakePersistentVolumeClaim,
},
},
want: false,
wantErr: false,
},
{
name: "pvc should not be pruned when it is not used",
args: args{
info: &resource.Info{
Object: &corev1.PersistentVolume{
TypeMeta: metav1.TypeMeta{
Kind: kindPersistentVolumeClaim,
},
},
Name: fakePersistentVolumeClaim,
},
},
want: true,
Expand All @@ -142,9 +199,10 @@ func Test_determiner_determinePrune(t *testing.T) {
t.Parallel()

d := &determiner{
usedConfigMaps: tt.fields.usedConfigMaps,
usedSecrets: tt.fields.usedSecrets,
pods: tt.fields.pods,
usedConfigMaps: tt.fields.usedConfigMaps,
usedSecrets: tt.fields.usedSecrets,
usedPersistentVolumeClaims: tt.fields.usedPersistentVolumes,
pods: tt.fields.pods,
}

got, err := d.determinePrune(tt.args.info)
Expand Down
14 changes: 14 additions & 0 deletions pkg/cmd/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes"
)

Expand Down Expand Up @@ -38,3 +40,15 @@ func podListToPods(podList *corev1.PodList) []*corev1.Pod {
}
return pods
}

func infoToPod(info *resource.Info) (*corev1.Pod, error) {
var pod corev1.Pod
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(
info.Object.(runtime.Unstructured).UnstructuredContent(),
&pod,
); err != nil {
return nil, err
}

return &pod, nil
}

0 comments on commit c23fe28

Please sign in to comment.