Skip to content

Commit

Permalink
Lookup status of ConfigMap/Secret in watch tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
bmaynard committed Jul 8, 2020
1 parent 61fc3c6 commit a5b9d6c
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 26 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ Features:

You can download the latest release from [Releases](https://github.com/bmaynard/kubevol/releases).

## Watch And Record Changes

Since Kubernetes doesn't keep track of when a `Secret` or `Configmap` was updated, `kubevol` has Kubernetes controller that will watch for all changes and will record the last modified date. This then gives `kubevol` the ability to detect if an attached `Secret` or `Configmap` is outdated.

To install the watch controller, run:

```bash
$ kubectl apply -f https://raw.githubusercontent.com/bmaynard/kubevol/master/deployment/manifest.yaml
```

### Configuration

If your kubeconfig is not in the default location in your home directory, you can specify a custom kubeconfig file by creating the following file:
Expand All @@ -27,14 +37,14 @@ kubeconfig: /path/to/kube/config
## Sample Output

```
There are 1 pods in the cluster
$ kubevol secret
There are 12 pods in the cluster
Searching for pods that have a Secret attached
+------------------+----------+-----------------------+-----------------------+-------------+
| NAMESPACE | POD NAME | SECRET NAME | VOLUME NAME | OUT OF DATE |
+------------------+----------+-----------------------+-----------------------+-------------+
| kubevol-test-run | redis | redis-secret | redis-secret | Unknown |
| kubevol-test-run | redis | redis-secret-outdated | redis-secret-outdated | Yes |
| kubevol-test-run | redis | default-token-nd4wr | default-token-nd4wr | Unknown |
+------------------+----------+-----------------------+-----------------------+-------------+
```
30 changes: 27 additions & 3 deletions cmd/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package cmd

import (
"fmt"
"strconv"
"time"

"github.com/bmaynard/kubevol/pkg/core"
"github.com/bmaynard/kubevol/pkg/watch"
"github.com/fatih/color"
"github.com/spf13/cobra"

"github.com/jedib0t/go-pretty/v6/table"
)

func NewConfigMapCommand(k core.KubeData) *cobra.Command {
func NewConfigMapCommand(f *core.Factory, k *core.KubeData) *cobra.Command {
var cmd = &cobra.Command{
Use: "configmap",
Short: "Find all pods that have a specific ConfigMap attached",
Expand All @@ -25,14 +28,19 @@ func NewConfigMapCommand(k core.KubeData) *cobra.Command {
}

ui := core.SetupTable(table.Row{"Namespace", "Pod Name", "ConfigMap Name", "Volume Name", "Out of Date"}, cmd.OutOrStdout())
configmapTracker, err := k.GetConfigMap(watch.WatchConfigMapTrackerName, watch.WatchNamespace)

if err != nil {
f.Logger.Error(err)
}

for _, pod := range pods.Items {
podName := pod.ObjectMeta.Name
namespace := pod.ObjectMeta.Namespace
_, err := k.GetPod(podName, namespace)

if err != nil {
panic(err.Error())
f.Logger.Error(err)
}

podCreationTime := pod.ObjectMeta.CreationTimestamp.Time
Expand All @@ -41,12 +49,28 @@ func NewConfigMapCommand(k core.KubeData) *cobra.Command {
if volume.ConfigMap != nil {
if objectName == "" || (volume.ConfigMap != nil && volume.ConfigMap.LocalObjectReference.Name == objectName) {
configMap, err := k.GetConfigMap(volume.ConfigMap.LocalObjectReference.Name, namespace)
outOfDate := color.YellowString("Unknown")
trackerName := watch.GetConfigMapKey(namespace, volume.ConfigMap.LocalObjectReference.Name)
var outOfDate string

if configmapTracker.CreationTimestamp.Time.Before(configMap.ObjectMeta.CreationTimestamp.Time) {
outOfDate = color.GreenString("No")
} else {
outOfDate = color.YellowString("Unknown")
}

if err != nil || configMap.ObjectMeta.CreationTimestamp.Time.After(podCreationTime) {
outOfDate = color.RedString("Yes")
}

if updatedTime, ok := configmapTracker.Data[trackerName]; ok {
parsedTime, err := strconv.ParseInt(updatedTime, 10, 64)
if err == nil && configMap.ObjectMeta.CreationTimestamp.Time.Before(time.Unix(parsedTime, 0)) {
outOfDate = color.RedString("Yes")
} else {
outOfDate = color.RedString("No")
}
}

ui.AppendRow([]table.Row{
{color.BlueString(namespace), podName, volume.ConfigMap.LocalObjectReference.Name, volume.Name, outOfDate},
})
Expand Down
6 changes: 3 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ func NewKubevolApp() *cobra.Command {

kubeData := core.NewKubeData(coreClient)

rootCmd.AddCommand(NewConfigMapCommand(*kubeData))
rootCmd.AddCommand(NewSecretCommand(*kubeData))
rootCmd.AddCommand(NewWatchCommand(*factory))
rootCmd.AddCommand(NewConfigMapCommand(factory, kubeData))
rootCmd.AddCommand(NewSecretCommand(factory, kubeData))
rootCmd.AddCommand(NewWatchCommand(factory))

return rootCmd
}
Expand Down
28 changes: 26 additions & 2 deletions cmd/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package cmd

import (
"fmt"
"strconv"
"time"

"github.com/bmaynard/kubevol/pkg/core"
"github.com/bmaynard/kubevol/pkg/watch"
"github.com/fatih/color"
"github.com/spf13/cobra"

"github.com/jedib0t/go-pretty/v6/table"
)

func NewSecretCommand(k core.KubeData) *cobra.Command {
func NewSecretCommand(f *core.Factory, k *core.KubeData) *cobra.Command {
var cmd = &cobra.Command{
Use: "secret",
Short: "Find all pods that have a specific Secret attached",
Expand All @@ -25,6 +28,11 @@ func NewSecretCommand(k core.KubeData) *cobra.Command {
}

ui := core.SetupTable(table.Row{"Namespace", "Pod Name", "Secret Name", "Volume Name", "Out of Date"}, cmd.OutOrStdout())
secretTracker, err := k.GetConfigMap(watch.WatchSecretTrackerName, watch.WatchNamespace)

if err != nil {
f.Logger.Error(err)
}

for _, pod := range pods.Items {
podName := pod.ObjectMeta.Name
Expand All @@ -41,12 +49,28 @@ func NewSecretCommand(k core.KubeData) *cobra.Command {
if volume.Secret != nil {
if objectName == "" || (volume.Secret != nil && volume.Secret.SecretName == objectName) {
secret, err := k.GetSecret(volume.Secret.SecretName, namespace)
outOfDate := color.YellowString("Unknown")
trackerName := watch.GetConfigMapKey(namespace, volume.Secret.SecretName)
var outOfDate string

if secretTracker.CreationTimestamp.Time.Before(secret.ObjectMeta.CreationTimestamp.Time) {
outOfDate = color.GreenString("No")
} else {
outOfDate = color.YellowString("Unknown")
}

if err != nil || secret.ObjectMeta.CreationTimestamp.Time.After(podCreationTime) {
outOfDate = color.RedString("Yes")
}

if updatedTime, ok := secretTracker.Data[trackerName]; ok {
parsedTime, err := strconv.ParseInt(updatedTime, 10, 64)
if err == nil && secret.ObjectMeta.CreationTimestamp.Time.Before(time.Unix(parsedTime, 0)) {
outOfDate = color.RedString("Yes")
} else {
outOfDate = color.RedString("No")
}
}

ui.AppendRow([]table.Row{
{color.BlueString(namespace), podName, volume.Secret.SecretName, volume.Name, outOfDate},
})
Expand Down
6 changes: 3 additions & 3 deletions cmd/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func NewWatchCommand(f core.Factory) *cobra.Command {
func NewWatchCommand(f *core.Factory) *cobra.Command {
var cmd = &cobra.Command{
Use: "watch",
Short: "Watch for updates to ConfigMaps and Secrets",
Expand All @@ -30,8 +30,8 @@ func NewWatchCommand(f core.Factory) *cobra.Command {
var wg sync.WaitGroup
wg.Add(2)

go watchConfigmap(&wg, &f, clientset)
go watchSecret(&wg, &f, clientset)
go watchConfigmap(&wg, f, clientset)
go watchSecret(&wg, f, clientset)
wg.Wait()
return nil
},
Expand Down
47 changes: 47 additions & 0 deletions deployment/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,52 @@ kind: Namespace
metadata:
name: kubevol
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubevol
namespace: "kubevol"
labels:
app: kubevol
app.kubernetes.io/name: kubevol
app.kubernetes.io/instance: kubevol-watcher
app.kubernetes.io/component: "controller"
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: kubevol
labels:
app: kubevol
app.kubernetes.io/name: kubevol
app.kubernetes.io/instance: kubevol-watcher
app.kubernetes.io/component: "controller"
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmap"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: kubevol
labels:
app: kubevol
app.kubernetes.io/name: kubevol
app.kubernetes.io/instance: kubevol-watcher
app.kubernetes.io/component: "controller"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubevol
subjects:
- name: kubevol
namespace: "kubevol"
kind: ServiceAccount
---
apiVersion: apps/v1
kind: Deployment
metadata:
Expand All @@ -25,6 +71,7 @@ spec:
app.kubernetes.io/instance: kubevol-watch
app.kubernetes.io/component: "controller"
spec:
serviceAccountName: kubevol
containers:
- name: kubevol-watch
image: "bmaynard/kubevol-watch:v0.5.0"
Expand Down
5 changes: 0 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,9 @@ go 1.14

require (
github.com/fatih/color v1.9.0
github.com/go-delve/delve v1.4.1 // indirect
github.com/go-openapi/strfmt v0.19.5 // indirect
github.com/jedib0t/go-pretty v4.3.0+incompatible
github.com/jedib0t/go-pretty/v6 v6.0.3
github.com/mitchellh/go-homedir v1.1.0
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.6.0
github.com/smartystreets/goconvey v1.6.4
github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.7.0
k8s.io/api v0.17.0
Expand Down
12 changes: 7 additions & 5 deletions pkg/watch/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func (w Watch) UpateConfigMapTracker(old, new interface{}) {
defer mutex.Unlock()

cmTracker, err := w.kubeData.GetConfigMap(WatchConfigMapTrackerName, WatchNamespace)
trackerName := GetConfigMapKey(cm.Namespace, cm.Name)

now := time.Now()
currentTime := fmt.Sprintf("%d", now.Unix())
Expand All @@ -29,7 +30,7 @@ func (w Watch) UpateConfigMapTracker(old, new interface{}) {
Name: WatchConfigMapTrackerName,
},
Data: map[string]string{
cm.Name: currentTime,
trackerName: currentTime,
},
}

Expand All @@ -46,11 +47,11 @@ func (w Watch) UpateConfigMapTracker(old, new interface{}) {
cmTracker.Data = make(map[string]string)
}

cmTracker.Data[cm.Name] = currentTime
cmTracker.Data[trackerName] = currentTime
_, err := w.clientset.CoreV1().ConfigMaps(WatchNamespace).Update(cmTracker)

if err != nil {
w.f.Logger.Errorf("Unable to update tracker configmap: %w", err)
w.f.Logger.Errorf("Unable to update tracker configmap: %v", err)
} else {
w.f.Logger.Infof("Updated tracker for configmap: \"%s\"", cm.Name)
}
Expand All @@ -68,17 +69,18 @@ func (w Watch) DeleteConfigMapTracker(obj interface{}) {
defer mutex.Unlock()

cmTracker, err := w.kubeData.GetConfigMap(WatchConfigMapTrackerName, WatchNamespace)
trackerName := GetConfigMapKey(cm.Namespace, cm.Name)

if err != nil {
w.f.Logger.Info("Unable find tracker configmap")
return
}

delete(cmTracker.Data, cm.Name)
delete(cmTracker.Data, trackerName)
_, dErr := w.clientset.CoreV1().ConfigMaps(WatchNamespace).Update(cmTracker)

if dErr != nil {
w.f.Logger.Errorf("Unable to delete configmap from tracker; Error: %w", err)
w.f.Logger.Errorf("Unable to delete configmap from tracker; Error: %v", err)
} else {
w.f.Logger.Infof("Deleted configmap: \"%s\" from tracker", cm.Name)
}
Expand Down
8 changes: 5 additions & 3 deletions pkg/watch/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func (w Watch) UpateSecretTracker(old, new interface{}) {
defer mutex.Unlock()

cmTracker, err := w.kubeData.GetConfigMap(WatchSecretTrackerName, WatchNamespace)
trackerName := GetConfigMapKey(cm.Namespace, cm.Name)

now := time.Now()
currentTime := fmt.Sprintf("%d", now.Unix())
Expand All @@ -29,7 +30,7 @@ func (w Watch) UpateSecretTracker(old, new interface{}) {
Name: WatchSecretTrackerName,
},
Data: map[string]string{
cm.Name: currentTime,
trackerName: currentTime,
},
}

Expand All @@ -46,7 +47,7 @@ func (w Watch) UpateSecretTracker(old, new interface{}) {
cmTracker.Data = make(map[string]string)
}

cmTracker.Data[cm.Name] = currentTime
cmTracker.Data[trackerName] = currentTime
_, err := w.clientset.CoreV1().ConfigMaps(WatchNamespace).Update(cmTracker)

if err != nil {
Expand All @@ -68,13 +69,14 @@ func (w Watch) DeleteSecretTracker(obj interface{}) {
defer mutex.Unlock()

cmTracker, err := w.kubeData.GetConfigMap(WatchSecretTrackerName, WatchNamespace)
trackerName := GetConfigMapKey(cm.Namespace, cm.Name)

if err != nil {
w.f.Logger.Info("Unable find tracker configmap")
return
}

delete(cmTracker.Data, cm.Name)
delete(cmTracker.Data, trackerName)
_, dErr := w.clientset.CoreV1().ConfigMaps(WatchNamespace).Update(cmTracker)

if dErr != nil {
Expand Down
8 changes: 8 additions & 0 deletions pkg/watch/watch.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package watch

import (
"crypto/md5"
"encoding/hex"
"fmt"
"sync"

"github.com/bmaynard/kubevol/pkg/core"
Expand Down Expand Up @@ -35,3 +38,8 @@ func NewWatch(f *core.Factory) *Watch {
clientset: clientset,
}
}

func GetConfigMapKey(namespace string, name string) string {
hash := md5.Sum([]byte(fmt.Sprintf("%s|%s", namespace, name)))
return hex.EncodeToString(hash[:])
}

0 comments on commit a5b9d6c

Please sign in to comment.