Skip to content

Commit d164eaf

Browse files
sumitkumarmSumit Kumar Mjonnylangefeld
authored
Support multiple namespaces (#15)
* `-n` switch accepts an optional comma-separated list of namespaces * Breaking change: Negative RegEx is now handled using `-x` If a list of namespaces is provided for the `-n` option, each command is now run against _each_ namespace for _each_ context Fix #14 Co-authored-by: Sumit Kumar M <skumarmu@adobe.com> Co-authored-by: Jonny <jonny.langefeld@gmail.com>
1 parent 63fff2b commit d164eaf

3 files changed

Lines changed: 53 additions & 31 deletions

File tree

pkg/mc/mc.go

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,18 @@ var (
3636

3737
// MC contains the options of the command
3838
type MC struct {
39-
Cmd *cobra.Command
40-
Regex string
41-
NegRegex string
42-
ListOnly bool
43-
MaxProc int
44-
Debug bool
45-
Output string
39+
Cmd *cobra.Command
40+
Regex string
41+
NegRegex string
42+
Namespaces string
43+
ListOnly bool
44+
MaxProc int
45+
Debug bool
46+
Output string
4647

4748
// to allow dependency injection
4849
getListContextsCmd func() Cmd
49-
getKubectlCmd func(args []string, context string) Cmd
50+
getKubectlCmd func(args []string, context string, namespace string) Cmd
5051
}
5152

5253
// Cmd is an interface for exec.Cmd to allow for dependency injection
@@ -63,8 +64,8 @@ func New(version string) *MC {
6364
mc.getListContextsCmd = func() Cmd {
6465
return exec.Command("kubectl", []string{"config", "get-contexts", "-o", "name"}...)
6566
}
66-
mc.getKubectlCmd = func(args []string, context string) Cmd {
67-
return exec.Command("kubectl", getLocalArgs(args, context)...)
67+
mc.getKubectlCmd = func(args []string, context string, namespace string) Cmd {
68+
return exec.Command("kubectl", getLocalArgs(args, context, namespace)...)
6869
}
6970

7071
cmd := &cobra.Command{
@@ -112,7 +113,8 @@ mc -r kind -o json -- get pods -n kube-system | jq 'keys[] as $k | "\($k) \(.[$k
112113
}
113114

114115
cmd.Flags().StringVarP(&mc.Regex, "regex", "r", mc.Regex, "a regex to filter the list of context names in kubeconfig. If not given all contexts are used")
115-
cmd.Flags().StringVarP(&mc.NegRegex, "negative-regex", "n", mc.NegRegex, "a regex to exclude matches from the result set. Evaluated succeeding to the including regex filter")
116+
cmd.Flags().StringVarP(&mc.NegRegex, "negative-regex", "x", mc.NegRegex, "a regex to exclude matches from the result set. Evaluated succeeding to the including regex filter")
117+
cmd.Flags().StringVarP(&mc.Namespaces, "namespaces", "n", mc.Namespaces, "comma-separated list of namespaces. Overrides namespace(s) specified in kubectl command. The default is the current namespace of the context")
116118
cmd.Flags().BoolVarP(&mc.ListOnly, "list-only", "l", mc.ListOnly, "just list the contexts matching the regex. Good for testing your regex")
117119
cmd.Flags().IntVarP(&mc.MaxProc, "max-processes", "p", 5, "max amount of parallel kubectl to be executed. Can be used to limit cpu activity")
118120
cmd.Flags().BoolVarP(&mc.Debug, "debug", "d", mc.Debug, "enable debug output")
@@ -149,9 +151,11 @@ func (mc *MC) run(args []string) error {
149151
wait := make(chan bool)
150152
var mutex = &sync.Mutex{}
151153

154+
namespaces := strings.Split(mc.Namespaces, ",")
155+
152156
logger.Debug("start wait group")
153157
go func() {
154-
for i := 0; i < len(contexts); i++ {
158+
for i := 0; i < len(contexts)*len(namespaces); i++ {
155159
<-done
156160
parallelProc <- true
157161
}
@@ -161,10 +165,12 @@ func (mc *MC) run(args []string) error {
161165

162166
output := map[string]json.RawMessage{}
163167
for _, c := range contexts {
164-
logger.Debug("waiting for next free spot", zap.String("context", c))
165-
<-parallelProc
166-
logger.Debug("executing", zap.String("context", c))
167-
go do(done, c, output, mc.Output == "", mc.Cmd.OutOrStdout(), mc.getKubectlCmd(args, c), mutex)
168+
for _, ns := range namespaces {
169+
logger.Debug("waiting for next free spot", zap.String("context", c), zap.String("namespace", ns))
170+
<-parallelProc
171+
logger.Debug("executing", zap.String("context", c), zap.String("namespace", ns))
172+
go do(done, c, ns, output, mc.Output == "", mc.Cmd.OutOrStdout(), mc.getKubectlCmd(args, c, ns), mutex)
173+
}
168174
}
169175
<-wait
170176
if mc.Output != "" {
@@ -221,16 +227,21 @@ func (mc *MC) listContexts(cmd Cmd) (contexts []string, err error) {
221227
}
222228

223229
// do executes a command against kubectl and sends a bool to the done channel when done
224-
func do(done chan bool, context string, output map[string]json.RawMessage, writeToStdout bool, out io.Writer, cmd Cmd, mutex *sync.Mutex) {
230+
func do(done chan bool, context string, namespace string, output map[string]json.RawMessage, writeToStdout bool, out io.Writer, cmd Cmd, mutex *sync.Mutex) {
225231
stdout, err := kubectl(cmd)
226232
if err != nil {
227233
stdout = []byte(err.Error())
228234
}
229235
mutex.Lock()
230-
output[context] = stdout
236+
237+
cns := context
238+
if namespace != "" {
239+
cns += ": " + namespace
240+
}
241+
output[cns] = stdout
231242
mutex.Unlock()
232243
if writeToStdout {
233-
fmt.Fprint(out, formatContext(context, stdout))
244+
fmt.Fprint(out, formatContext(context, namespace, stdout))
234245
}
235246
done <- true
236247
}
@@ -247,18 +258,25 @@ func kubectl(cmd Cmd) ([]byte, error) {
247258
// getLocalArgs transforms kubectl args slice by injecting the context flag into the right position.
248259
// if the kubectl command contained `--` (for instance for a `kubectl exec` command, we inject the context flag before
249260
// that.
250-
func getLocalArgs(args []string, context string) (localArgs []string) {
261+
func getLocalArgs(args []string, context string, namespace string) (localArgs []string) {
262+
251263
var skipContext bool
252264
for _, arg := range args {
253265
if arg == "--" {
254266
// If this is given, we need to insert the context before this arg
255267
localArgs = append(localArgs, "--context", context)
268+
if len(namespace) > 0 {
269+
localArgs = append(localArgs, "--namespace", namespace)
270+
}
256271
skipContext = true
257272
}
258273
localArgs = append(localArgs, arg)
259274
}
260275
if !skipContext {
261276
localArgs = append(localArgs, "--context", context)
277+
if len(namespace) > 0 {
278+
localArgs = append(localArgs, "--namespace", namespace)
279+
}
262280
}
263281
return
264282
}
@@ -274,6 +292,9 @@ func outputsString() string {
274292
}
275293

276294
// formatContext returns a formated strings with the context has header, separated from the contents by a divider
277-
func formatContext(context string, stdout []byte) string {
278-
return fmt.Sprintf("\n%s\n%s\n%s", context, strings.Repeat("-", len(context)), string(stdout))
295+
func formatContext(context string, namespace string, stdout []byte) string {
296+
if namespace != "" {
297+
namespace = ": " + namespace
298+
}
299+
return fmt.Sprintf("\n%s%s\n%s\n%s", context, namespace, strings.Repeat("-", len(context)+len(namespace)), string(stdout))
279300
}

pkg/mc/mc_helpers_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package mc
22

33
const (
4-
context = "kind-kind"
4+
context = "kind-kind"
5+
namespace = "default"
56
)
67

78
var (

pkg/mc/mc_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ coredns-66bff467f8-4lnsg 1/1 Running 1 22h
9393
mc.getListContextsCmd = func() Cmd {
9494
return m
9595
}
96-
mc.getKubectlCmd = func(args []string, context string) Cmd {
96+
mc.getKubectlCmd = func(args []string, context string, namespace string) Cmd {
9797
return m
9898
}
9999
b := bytes.NewBuffer([]byte(``))
@@ -173,9 +173,9 @@ func TestDo(t *testing.T) {
173173
done := make(chan bool, 1)
174174
var mutex = &sync.Mutex{}
175175
output := map[string]json.RawMessage{}
176-
do(done, context, output, false, nil, m, mutex)
176+
do(done, context, namespace, output, false, nil, m, mutex)
177177
assert.True(t, <-done)
178-
assert.Equal(t, map[string]json.RawMessage{context: kubectlReturn}, output)
178+
assert.Equal(t, map[string]json.RawMessage{context + ": " + namespace: kubectlReturn}, output)
179179
}
180180

181181
func TestKubectl(t *testing.T) {
@@ -204,17 +204,17 @@ func TestGetLocalArgs(t *testing.T) {
204204
}{
205205
"default": {
206206
args: []string{"get", "pods", "-n", "kube-system"},
207-
want: []string{"get", "pods", "-n", "kube-system", "--context", context},
207+
want: []string{"get", "pods", "-n", "kube-system", "--context", context, "--namespace", namespace},
208208
},
209209
"exec": {
210210
args: []string{"exec", "deployment/local-path-provisioner", "-n", "local-path-storage", "-it", "--", "ls", "/usr"},
211-
want: []string{"exec", "deployment/local-path-provisioner", "-n", "local-path-storage", "-it", "--context", context, "--", "ls", "/usr"},
211+
want: []string{"exec", "deployment/local-path-provisioner", "-n", "local-path-storage", "-it", "--context", context, "--namespace", namespace, "--", "ls", "/usr"},
212212
},
213213
}
214214

215215
for name, test := range tests {
216216
t.Run(name, func(t *testing.T) {
217-
got := getLocalArgs(test.args, context)
217+
got := getLocalArgs(test.args, context, namespace)
218218
assert.Equal(t, test.want, got)
219219
})
220220
}
@@ -226,6 +226,6 @@ func TestOutputsString(t *testing.T) {
226226
}
227227

228228
func TestFormatContext(t *testing.T) {
229-
got := formatContext(context, kubectlReturn)
230-
assert.Equal(t, "\nkind-kind\n---------\n"+string(kubectlReturn), got)
229+
got := formatContext(context, namespace, kubectlReturn)
230+
assert.Equal(t, "\nkind-kind: default\n------------------\n"+string(kubectlReturn), got)
231231
}

0 commit comments

Comments
 (0)