Skip to content

Commit

Permalink
Add output feature (#5)
Browse files Browse the repository at this point in the history
fix #2
  • Loading branch information
jonnylangefeld committed Feb 7, 2021
1 parent ffbeb93 commit a778922
Show file tree
Hide file tree
Showing 37 changed files with 11,306 additions and 6 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ require (
github.com/spf13/cobra v1.1.1
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0
gopkg.in/yaml.v2 v2.2.8 // indirect
k8s.io/client-go v11.0.0+incompatible // indirect
sigs.k8s.io/yaml v1.2.0
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,15 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E=
k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
68 changes: 62 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,33 @@ package main
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/spf13/cobra"
"go.uber.org/zap"
"os"
"os/exec"
"regexp"
"sigs.k8s.io/yaml"
"strings"
)

const (
YAML = "yaml"
JSON = "json"
)

var (
version string
logger *zap.Logger
outputs = map[string]bool{
YAML: true,
JSON: true,
}

ErrUnknownOutput = errors.New(fmt.Sprintf("this output format is unknown. Choose one of %s", outputsString()))
ErrCouldntParseOutput = errors.New(fmt.Sprintf("couldn't parse this output. Are you sure your kubectl command allows for json output? Run command with -d to see debug output"))
)

func main() {
Expand All @@ -31,6 +46,7 @@ type MC struct {
ListOnly bool
MaxProc int
Debug bool
Output string
}

// NewMC registers the default mc command
Expand All @@ -55,7 +71,10 @@ mc -r dev -l -n '-test-'
# list all pods with label 'app.kubernetes.io/name=audit' in the 'default' namespace from all clusters with 'gke' in the name, but not 'dev'
# run max 5 processes in parallel and enable debug output
mc -r gke -n 'dev' -p 5 -d -- get pods -n gatekeeper-system -l app.kubernetes.io/name=audit`,
mc -r gke -n 'dev' -p 5 -d -- get pods -n gatekeeper-system -l app.kubernetes.io/name=audit
# print the context and the pod names in kube-system using jq
mc -r kind -o json -- get pods -n kube-system | jq 'keys[] as $k | "\($k) \(.[$k] | .items[].metadata.name)"'`,
SilenceUsage: true,
Version: version,
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -64,6 +83,12 @@ mc -r gke -n 'dev' -p 5 -d -- get pods -n gatekeeper-system -l app.kubernetes.io
logger, _ = zap.NewDevelopment()
}
defer logger.Sync()
if mc.Output != "" {
if _, ok := outputs[mc.Output]; !ok {
return ErrUnknownOutput
}
args = append(args, "-o", "json")
}
if len(args) == 0 && !mc.ListOnly {
cmd.Usage()
return nil
Expand All @@ -77,6 +102,7 @@ mc -r gke -n 'dev' -p 5 -d -- get pods -n gatekeeper-system -l app.kubernetes.io
cmd.Flags().BoolVarP(&mc.ListOnly, "list-only", "l", mc.ListOnly, "just list the contexts matching the regex. Good for testing your regex")
cmd.Flags().IntVarP(&mc.MaxProc, "max-processes", "p", 100, "max amount of parallel kubectl to be exectued. Can be used to limit cpu activity")
cmd.Flags().BoolVarP(&mc.Debug, "debug", "d", mc.Debug, "enable debug output")
cmd.Flags().StringVarP(&mc.Output, "output", "o", mc.Output, fmt.Sprintf("specify the output format. Useful for parsing with another tool like jq or yq. One of %s", outputsString()))

return cmd
}
Expand Down Expand Up @@ -115,13 +141,32 @@ func (mc *MC) run(args []string) error {
wait <- true
}()

output := map[string]json.RawMessage{}
for _, c := range contexts {
logger.Debug("waiting for next free spot", zap.String("context", c))
<-parallelProc
logger.Debug("executing", zap.String("context", c))
go do(done, c, args)
go do(done, c, output, mc.Output == "", args)
}
<-wait
if mc.Output != "" {
logger.Debug("parsing output...")
o, err := json.MarshalIndent(output, "", " ")
if err != nil {
logger.Debug("failed to parse output", zap.String("retrieved", fmt.Sprintf("%s", output)))
return ErrCouldntParseOutput
}
switch mc.Output {
case JSON:
fmt.Printf("%s", o)
case YAML:
o, err := yaml.JSONToYAML(o)
if err != nil {
return err
}
fmt.Printf("%s", o)
}
}
logger.Debug("done")

return nil
Expand Down Expand Up @@ -160,25 +205,28 @@ func (mc *MC) listContexts() (contexts []string, err error) {
}

// do executes a command against kubectl and sends a bool to the done channel when done
func do(done chan bool, c string, args []string) {
func do(done chan bool, context string, output map[string]json.RawMessage, writeToStdout bool, args []string) {
var localArgs []string
var skipContext bool
for _, arg := range args {
if arg == "--" {
// If this is given, we need to insert the context before this arg
localArgs = append(localArgs, "--context", c)
localArgs = append(localArgs, "--context", context)
skipContext = true
}
localArgs = append(localArgs, arg)
}
if !skipContext {
localArgs = append(localArgs, "--context", c)
localArgs = append(localArgs, "--context", context)
}
stdout, err := kubectl(localArgs)
if err != nil {
stdout = bytes.NewBuffer([]byte(err.Error()))
}
fmt.Printf("\n%s\n%s\n%s", c, strings.Repeat("-", len(c)), string(stdout.Bytes()))
output[context] = stdout.Bytes()
if writeToStdout {
fmt.Printf("\n%s\n%s\n%s", context, strings.Repeat("-", len(context)), string(stdout.Bytes()))
}
done <- true
}

Expand All @@ -193,3 +241,11 @@ func kubectl(args []string) (*bytes.Buffer, error) {
}
return &stdout, nil
}

func outputsString() string {
keys := make([]string, 0, len(outputs))
for k := range outputs {
keys = append(keys, k)
}
return strings.Join(keys, "|")
}
16 changes: 16 additions & 0 deletions vendor/gopkg.in/yaml.v2/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

201 changes: 201 additions & 0 deletions vendor/gopkg.in/yaml.v2/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a778922

Please sign in to comment.