Skip to content

Commit

Permalink
allow multiple namespaces and glob expressions for namespaces and res…
Browse files Browse the repository at this point in the history
…ource names
  • Loading branch information
xrstf committed Mar 19, 2022
1 parent 21086c9 commit 4fc672a
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 32 deletions.
9 changes: 4 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (

type options struct {
kubeconfig string
namespace string
namespaces []string
labels string
hideManagedFields bool
jsonPath string
Expand All @@ -45,15 +45,14 @@ func main() {
rootCtx := context.Background()

opt := options{
namespace: "default",
hideManagedFields: true,
showEmpty: false,
disableWordDiff: false,
contextLines: 3,
}

pflag.StringVar(&opt.kubeconfig, "kubeconfig", opt.kubeconfig, "kubeconfig file to use (uses $KUBECONFIG by default)")
pflag.StringVarP(&opt.namespace, "namespace", "n", opt.namespace, "Kubernetes namespace to watch resources in")
pflag.StringArrayVarP(&opt.namespaces, "namespace", "n", opt.namespaces, "Kubernetes namespace to watch resources in (supports glob expression) (can be given multiple times)")
pflag.StringVarP(&opt.labels, "labels", "l", opt.labels, "Label-selector as an alternative to specifying resource names")
pflag.BoolVar(&opt.hideManagedFields, "hide-managed", opt.hideManagedFields, "Do not show managed fields")
pflag.StringVarP(&opt.jsonPath, "jsonpath", "j", opt.jsonPath, "JSON path expression to transform the output (applied before the --show paths)")
Expand Down Expand Up @@ -209,7 +208,7 @@ func watchKubernetes(ctx context.Context, log logrus.FieldLogger, args []string,

wg := sync.WaitGroup{}
for _, gvk := range kinds {
dynamicInterface, err := kubeutil.GetDynamicInterface(gvk, appOpts.namespace, dynamicClient, mapper)
dynamicInterface, err := kubeutil.GetDynamicInterface(gvk, dynamicClient, mapper)
if err != nil {
log.Fatalf("Failed to create dynamic interface for %q resources: %v", gvk.Kind, err)
}
Expand All @@ -223,7 +222,7 @@ func watchKubernetes(ctx context.Context, log logrus.FieldLogger, args []string,

wg.Add(1)
go func() {
watcher.NewWatcher(printer, resourceNames).Watch(ctx, w)
watcher.NewWatcher(printer, appOpts.namespaces, resourceNames).Watch(ctx, w)
wg.Done()
}()
}
Expand Down
15 changes: 2 additions & 13 deletions pkg/kubernetes/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,13 @@ func computeDiscoverCacheDir(parentDir, host string) string {
return filepath.Join(parentDir, safeHost)
}

func GetDynamicInterface(gvk schema.GroupVersionKind, namespace string, dynamicClient dynamic.Interface, mapper meta.RESTMapper) (dynamic.ResourceInterface, error) {
func GetDynamicInterface(gvk schema.GroupVersionKind, dynamicClient dynamic.Interface, mapper meta.RESTMapper) (dynamic.ResourceInterface, error) {
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return nil, fmt.Errorf("failed to determine mapping: %w", err)
}

namespaced := mapping.Scope.Name() == meta.RESTScopeNameNamespace

var dr dynamic.ResourceInterface
if namespaced {
// namespaced resources should specify the namespace
dr = dynamicClient.Resource(mapping.Resource).Namespace(namespace)
} else {
// for cluster-wide resources
dr = dynamicClient.Resource(mapping.Resource)
}

return dr, nil
return dynamicClient.Resource(mapping.Resource), nil
}

func MappingFor(restMapper meta.RESTMapper, cache discovery.CachedDiscoveryInterface, resourceOrKindArg string) (*meta.RESTMapping, error) {
Expand Down
59 changes: 45 additions & 14 deletions pkg/watcher/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package watcher

import (
"context"
"path/filepath"
"strings"

"go.xrstf.de/stalk/pkg/diff"

Expand All @@ -11,12 +13,14 @@ import (

type Watcher struct {
printer *diff.Printer
namespaces []string
resourceNames []string
}

func NewWatcher(printer *diff.Printer, resourceNames []string) *Watcher {
func NewWatcher(printer *diff.Printer, namespaces, resourceNames []string) *Watcher {
return &Watcher{
printer: printer,
namespaces: namespaces,
resourceNames: resourceNames,
}
}
Expand All @@ -28,20 +32,47 @@ func (w *Watcher) Watch(ctx context.Context, wi watch.Interface) {
continue
}

include := false
if len(w.resourceNames) > 0 {
for _, wantedName := range w.resourceNames {
if wantedName == obj.GetName() {
include = true
break
}
}

if !include {
continue
}
if w.resourceNameMatches(obj) && w.resourceNamespaceMatches(obj) {
w.printer.Print(obj, event.Type)
}
}
}

func (w *Watcher) resourceNameMatches(obj *unstructured.Unstructured) bool {
// no names given, so all resources match
if len(w.resourceNames) == 0 {
return true
}

for _, wantedName := range w.resourceNames {
if nameMatches(obj.GetName(), wantedName) {
return true
}
}

return false
}

w.printer.Print(obj, event.Type)
func (w *Watcher) resourceNamespaceMatches(obj *unstructured.Unstructured) bool {
// no namespaces given, so all resources match
if len(w.namespaces) == 0 {
return true
}

for _, wantedNamespace := range w.namespaces {
if nameMatches(obj.GetNamespace(), wantedNamespace) {
return true
}
}

return false
}

func nameMatches(name string, pattern string) bool {
if strings.Contains(pattern, "*") {
matched, _ := filepath.Match(pattern, name)
return matched
}

return name == pattern
}

0 comments on commit 4fc672a

Please sign in to comment.