├── .gitignore ├── .github └── workflows │ └── workflow.yml ├── scripts └── create-release-notes.sh ├── stern ├── tail_test.go ├── config.go ├── container_state.go ├── main.go ├── watch.go └── tail.go ├── main.go ├── go.mod ├── CONTRIBUTING.md ├── kubernetes └── clientset.go ├── wercker.yml ├── cmd ├── completion.go └── cli.go ├── README.md ├── LICENSE └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | /vendor/*/ 27 | /.wercker 28 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - "*" 10 | 11 | jobs: 12 | go_unit_tests: 13 | name: Go unit tests 14 | runs-on: ubuntu-18.04 15 | container: 16 | image: golang:1.12.9 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v1 20 | - name: Go unit tests 21 | run: | 22 | go test -cover -race -v -mod=readonly ./... 23 | -------------------------------------------------------------------------------- /scripts/create-release-notes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | cat << EOF 6 | ## Changelog 7 | 8 | TODO 9 | 10 | ## Checksums 11 | 12 | - stern_linux_amd64: 13 | - sha256: \`$(awk '{print $1}' "$DIR/../artifacts/latest/linux_amd64/SHA256SUMS")\` 14 | - stern_darwin_amd64: 15 | - sha256: \`$(awk '{print $1}' "$DIR/../artifacts/latest/darwin_amd64/SHA256SUMS")\` 16 | - stern_windows_amd64.exe: 17 | - sha256: \`$(awk '{print $1}' "$DIR/../artifacts/latest/windows_amd64/SHA256SUMS")\` 18 | 19 | EOF 20 | 21 | -------------------------------------------------------------------------------- /stern/tail_test.go: -------------------------------------------------------------------------------- 1 | package stern 2 | 3 | import "testing" 4 | 5 | func TestDetermineColor(t *testing.T) { 6 | podName := "stern" 7 | podColor1, containerColor1 := determineColor(podName) 8 | podColor2, containerColor2 := determineColor(podName) 9 | 10 | if podColor1 != podColor2 { 11 | t.Errorf("expected color for pod to be the same between invocations but was %v and %v", 12 | podColor1, podColor2) 13 | } 14 | if containerColor1 != containerColor2 { 15 | t.Errorf("expected color for container to be the same between invocations but was %v and %v", 16 | containerColor1, containerColor2) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Wercker Holding BV 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "github.com/wercker/stern/cmd" 18 | 19 | func main() { 20 | cmd.Run() 21 | } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wercker/stern 2 | 3 | go 1.12.9 4 | 5 | require ( 6 | github.com/fatih/color v0.0.0-20180516100307-2d684516a886 7 | github.com/ghodss/yaml v1.0.0 // indirect 8 | github.com/gogo/protobuf v1.3.0 // indirect 9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b 10 | github.com/google/btree v1.0.0 // indirect 11 | github.com/google/gofuzz v1.0.0 12 | github.com/googleapis/gnostic v0.3.1 // indirect 13 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 14 | github.com/imdario/mergo v0.3.7 // indirect 15 | github.com/inconshreveable/mousetrap v1.0.0 16 | github.com/json-iterator/go v1.1.7 // indirect 17 | github.com/mattn/go-colorable v0.1.2 // indirect 18 | github.com/mattn/go-isatty v0.0.9 // indirect 19 | github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 20 | github.com/pkg/errors v0.0.0-20180311214515-816c9085562c 21 | github.com/spf13/cobra v0.0.0-20180629152535-a114f312e075 22 | github.com/spf13/pflag v1.0.1 23 | golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 // indirect 24 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect 25 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect 26 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect 27 | gopkg.in/inf.v0 v0.9.1 // indirect 28 | k8s.io/api v0.0.0-20190620084959-7cf5895f2711 29 | k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719 30 | k8s.io/client-go v0.0.0-20190620085101-78d2af792bab 31 | ) 32 | -------------------------------------------------------------------------------- /stern/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Wercker Holding BV 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stern 16 | 17 | import ( 18 | "regexp" 19 | "text/template" 20 | "time" 21 | 22 | "k8s.io/apimachinery/pkg/labels" 23 | ) 24 | 25 | // Config contains the config for stern 26 | type Config struct { 27 | KubeConfig string 28 | ContextName string 29 | Namespace string 30 | PodQuery *regexp.Regexp 31 | Timestamps bool 32 | ContainerQuery *regexp.Regexp 33 | ExcludeContainerQuery *regexp.Regexp 34 | ContainerState ContainerState 35 | Exclude []*regexp.Regexp 36 | Include []*regexp.Regexp 37 | InitContainers bool 38 | Since time.Duration 39 | AllNamespaces bool 40 | LabelSelector labels.Selector 41 | TailLines *int64 42 | Template *template.Template 43 | } 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Oracle welcomes contributions to this repository from anyone. 4 | 5 | If you want to submit a pull request to fix a bug or enhance an existing 6 | feature, please first open an issue and link to that issue when you 7 | submit your pull request. 8 | 9 | If you have any questions about a possible submission, feel free to open 10 | an issue too. 11 | 12 | ## Contributing to the Wercker repository 13 | 14 | Pull requests can be made under 15 | [The Oracle Contributor Agreement](https://www.oracle.com/technetwork/community/oca-486395.html) (OCA). 16 | 17 | For pull requests to be accepted, the bottom of your commit message must have 18 | the following line using your name and e-mail address as it appears in the 19 | OCA Signatories list. 20 | 21 | ``` 22 | Signed-off-by: Your Name 23 | ``` 24 | 25 | This can be automatically added to pull requests by committing with: 26 | 27 | ``` 28 | git commit --signoff 29 | ``` 30 | 31 | Only pull requests from committers that can be verified as having 32 | signed the OCA can be accepted. 33 | 34 | ### Pull request process 35 | 36 | 1. Fork this repository 37 | 1. Create a branch in your fork to implement the changes. We recommend using 38 | the issue number as part of your branch name, e.g. `1234-fixes` 39 | 1. Ensure that any documentation is updated with the changes that are required 40 | by your fix. 41 | 1. Ensure that any samples are updated if the base image has been changed. 42 | 1. Submit the pull request. *Do not leave the pull request blank*. Explain exactly 43 | what your changes are meant to do and provide simple steps on how to validate 44 | your changes. Ensure that you reference the issue you created as well. 45 | We will assign the pull request to 2-3 people for review before it is merged. 46 | -------------------------------------------------------------------------------- /stern/container_state.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Wercker Holding BV 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stern 16 | 17 | import ( 18 | "errors" 19 | 20 | v1 "k8s.io/api/core/v1" 21 | ) 22 | 23 | type ContainerState []string 24 | 25 | const ( 26 | RUNNING = "running" 27 | WAITING = "waiting" 28 | TERMINATED = "terminated" 29 | ) 30 | 31 | func NewContainerState(stateConfig []string) (ContainerState, error) { 32 | var containerState []string 33 | for _, p := range stateConfig { 34 | if p == RUNNING || p == WAITING || p == TERMINATED { 35 | containerState = append(containerState, p) 36 | } 37 | } 38 | if len(containerState) == 0 { 39 | return []string{}, errors.New("containerState should include 'running', 'waiting', or 'terminated'") 40 | } 41 | return containerState, nil 42 | } 43 | 44 | func (stateConfig ContainerState) Match(containerState v1.ContainerState) bool { 45 | if containerState.Running != nil && stateConfig.has(RUNNING) { 46 | return true 47 | } 48 | if containerState.Waiting != nil && stateConfig.has(WAITING) { 49 | return true 50 | } 51 | if containerState.Terminated != nil && stateConfig.has(TERMINATED) { 52 | return true 53 | } 54 | return false 55 | } 56 | 57 | func (stateConfig ContainerState) has(state string) bool { 58 | for _, s := range stateConfig { 59 | if s == state { 60 | return true 61 | } 62 | } 63 | return false 64 | } 65 | -------------------------------------------------------------------------------- /kubernetes/clientset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Wercker Holding BV 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package kubernetes 16 | 17 | import ( 18 | "path/filepath" 19 | 20 | "github.com/pkg/errors" 21 | "k8s.io/client-go/kubernetes" 22 | "k8s.io/client-go/tools/clientcmd" 23 | 24 | // auth providers 25 | _ "k8s.io/client-go/plugin/pkg/client/auth/azure" 26 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 27 | _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" 28 | ) 29 | 30 | // NewClientConfig returns a new Kubernetes client config set for a context 31 | func NewClientConfig(configPath string, contextName string) clientcmd.ClientConfig { 32 | configPathList := filepath.SplitList(configPath) 33 | configLoadingRules := &clientcmd.ClientConfigLoadingRules{} 34 | if len(configPathList) <= 1 { 35 | configLoadingRules.ExplicitPath = configPath 36 | } else { 37 | configLoadingRules.Precedence = configPathList 38 | } 39 | return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 40 | configLoadingRules, 41 | &clientcmd.ConfigOverrides{ 42 | CurrentContext: contextName, 43 | }, 44 | ) 45 | } 46 | 47 | // NewClientSet returns a new Kubernetes client for a client config 48 | func NewClientSet(clientConfig clientcmd.ClientConfig) (*kubernetes.Clientset, error) { 49 | c, err := clientConfig.ClientConfig() 50 | 51 | if err != nil { 52 | return nil, errors.Wrap(err, "failed to get client config") 53 | } 54 | 55 | clientset, err := kubernetes.NewForConfig(c) 56 | if err != nil { 57 | return nil, errors.Wrap(err, "failed to create clientset") 58 | } 59 | 60 | return clientset, nil 61 | } 62 | -------------------------------------------------------------------------------- /stern/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Wercker Holding BV 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stern 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | "sync" 22 | 23 | "github.com/pkg/errors" 24 | "github.com/wercker/stern/kubernetes" 25 | ) 26 | 27 | // Run starts the main run loop 28 | func Run(ctx context.Context, config *Config) error { 29 | clientConfig := kubernetes.NewClientConfig(config.KubeConfig, config.ContextName) 30 | clientset, err := kubernetes.NewClientSet(clientConfig) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | var namespace string 36 | // A specific namespace is ignored if all-namespaces is provided 37 | if config.AllNamespaces { 38 | namespace = "" 39 | } else { 40 | namespace = config.Namespace 41 | if namespace == "" { 42 | namespace, _, err = clientConfig.Namespace() 43 | if err != nil { 44 | return errors.Wrap(err, "unable to get default namespace") 45 | } 46 | } 47 | } 48 | 49 | added, removed, err := Watch(ctx, 50 | clientset.CoreV1().Pods(namespace), 51 | config.PodQuery, 52 | config.ContainerQuery, 53 | config.ExcludeContainerQuery, 54 | config.InitContainers, 55 | config.ContainerState, 56 | config.LabelSelector) 57 | if err != nil { 58 | return errors.Wrap(err, "failed to set up watch") 59 | } 60 | 61 | tails := make(map[string]*Tail) 62 | tailsMutex := sync.RWMutex{} 63 | logC := make(chan string, 1024) 64 | 65 | go func() { 66 | for { 67 | select { 68 | case str := <-logC: 69 | fmt.Fprintf(os.Stdout, str) 70 | case <-ctx.Done(): 71 | break 72 | } 73 | } 74 | }() 75 | 76 | go func() { 77 | for p := range added { 78 | id := p.GetID() 79 | tailsMutex.RLock() 80 | existing := tails[id] 81 | tailsMutex.RUnlock() 82 | if existing != nil { 83 | if existing.Active == true { 84 | continue 85 | } else { // cleanup failed tail to restart 86 | tailsMutex.Lock() 87 | tails[id].Close() 88 | delete(tails, id) 89 | tailsMutex.Unlock() 90 | } 91 | } 92 | tail := NewTail(p.Namespace, p.Pod, p.Container, config.Template, &TailOptions{ 93 | Timestamps: config.Timestamps, 94 | SinceSeconds: int64(config.Since.Seconds()), 95 | Exclude: config.Exclude, 96 | Include: config.Include, 97 | Namespace: config.AllNamespaces, 98 | TailLines: config.TailLines, 99 | }) 100 | tailsMutex.Lock() 101 | tails[id] = tail 102 | tailsMutex.Unlock() 103 | tail.Start(ctx, clientset.CoreV1().Pods(p.Namespace), logC) 104 | } 105 | }() 106 | 107 | go func() { 108 | for p := range removed { 109 | id := p.GetID() 110 | tailsMutex.RLock() 111 | existing := tails[id] 112 | tailsMutex.RUnlock() 113 | if existing == nil { 114 | continue 115 | } 116 | tailsMutex.Lock() 117 | tails[id].Close() 118 | delete(tails, id) 119 | tailsMutex.Unlock() 120 | } 121 | }() 122 | 123 | <-ctx.Done() 124 | 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /stern/watch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Wercker Holding BV 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stern 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "regexp" 21 | 22 | "github.com/pkg/errors" 23 | 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/labels" 27 | "k8s.io/apimachinery/pkg/watch" 28 | v1 "k8s.io/client-go/kubernetes/typed/core/v1" 29 | ) 30 | 31 | // Target is a target to watch 32 | type Target struct { 33 | Namespace string 34 | Pod string 35 | Container string 36 | } 37 | 38 | // GetID returns the ID of the object 39 | func (t *Target) GetID() string { 40 | return fmt.Sprintf("%s-%s-%s", t.Namespace, t.Pod, t.Container) 41 | } 42 | 43 | // Watch starts listening to Kubernetes events and emits modified 44 | // containers/pods. The first result is targets added, the second is targets 45 | // removed 46 | func Watch(ctx context.Context, i v1.PodInterface, podFilter *regexp.Regexp, containerFilter *regexp.Regexp, containerExcludeFilter *regexp.Regexp, initContainers bool, containerState ContainerState, labelSelector labels.Selector) (chan *Target, chan *Target, error) { 47 | watcher, err := i.Watch(metav1.ListOptions{Watch: true, LabelSelector: labelSelector.String()}) 48 | if err != nil { 49 | return nil, nil, errors.Wrap(err, "failed to set up watch") 50 | } 51 | 52 | added := make(chan *Target) 53 | removed := make(chan *Target) 54 | 55 | go func() { 56 | for { 57 | select { 58 | case e := <-watcher.ResultChan(): 59 | if e.Object == nil { 60 | // Closed because of error 61 | return 62 | } 63 | 64 | pod, ok := e.Object.(*corev1.Pod) 65 | if !ok { 66 | continue 67 | } 68 | 69 | if !podFilter.MatchString(pod.Name) { 70 | continue 71 | } 72 | 73 | switch e.Type { 74 | case watch.Added, watch.Modified: 75 | var statuses []corev1.ContainerStatus 76 | statuses = append(statuses, pod.Status.ContainerStatuses...) 77 | if initContainers { 78 | statuses = append(statuses, pod.Status.InitContainerStatuses...) 79 | } 80 | 81 | for _, c := range statuses { 82 | if !containerFilter.MatchString(c.Name) { 83 | continue 84 | } 85 | if containerExcludeFilter != nil && containerExcludeFilter.MatchString(c.Name) { 86 | continue 87 | } 88 | 89 | if containerState.Match(c.State) { 90 | added <- &Target{ 91 | Namespace: pod.Namespace, 92 | Pod: pod.Name, 93 | Container: c.Name, 94 | } 95 | } 96 | } 97 | case watch.Deleted: 98 | var containers []corev1.Container 99 | containers = append(containers, pod.Spec.Containers...) 100 | if initContainers { 101 | containers = append(containers, pod.Spec.InitContainers...) 102 | } 103 | 104 | for _, c := range containers { 105 | if !containerFilter.MatchString(c.Name) { 106 | continue 107 | } 108 | if containerExcludeFilter != nil && containerExcludeFilter.MatchString(c.Name) { 109 | continue 110 | } 111 | 112 | removed <- &Target{ 113 | Namespace: pod.Namespace, 114 | Pod: pod.Name, 115 | Container: c.Name, 116 | } 117 | } 118 | } 119 | case <-ctx.Done(): 120 | watcher.Stop() 121 | close(added) 122 | close(removed) 123 | return 124 | } 125 | } 126 | }() 127 | 128 | return added, removed, nil 129 | } 130 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: golang:1.8 2 | build: 3 | base-path: /go/src/github.com/wercker/stern 4 | steps: 5 | - script: 6 | name: install govendor 7 | code: go get -u github.com/kardianos/govendor 8 | 9 | - script: 10 | name: go vet 11 | code: govendor vet +local 12 | 13 | - script: 14 | name: install dependencies 15 | code: govendor sync 16 | 17 | - script: 18 | name: go build linux amd64 19 | code: | 20 | export CGO_ENABLED=0 21 | GOOS=linux GOARCH=amd64 go build \ 22 | -ldflags="-s -X github.com/wercker/stern.GitCommit=$WERCKER_GIT_COMMIT -X github.com/wercker/stern.PatchVersion=$(( ($(date +%s) - $(date --date=20150101 +%s) )/(60*60*24) )) -X github.com/wercker/stern.Compiled=$(date +%s)" \ 23 | -installsuffix cgo \ 24 | -o $WERCKER_OUTPUT_DIR/artifacts/latest/linux_amd64/stern 25 | 26 | cp $WERCKER_OUTPUT_DIR/artifacts/latest/linux_amd64/stern $WERCKER_REPORT_ARTIFACTS_DIR 27 | 28 | - script: 29 | name: go build darwin amd64 30 | code: | 31 | export CGO_ENABLED=0 32 | GOOS=darwin GOARCH=amd64 go build \ 33 | -ldflags="-s -X github.com/wercker/stern.GitCommit=$WERCKER_GIT_COMMIT -X github.com/wercker/stern.PatchVersion=$(( ($(date +%s) - $(date --date=20150101 +%s) )/(60*60*24) )) -X github.com/wercker/stern.Compiled=$(date +%s)" \ 34 | -installsuffix cgo \ 35 | -o $WERCKER_OUTPUT_DIR/artifacts/latest/darwin_amd64/stern 36 | 37 | cp $WERCKER_OUTPUT_DIR/artifacts/latest/darwin_amd64/stern $WERCKER_REPORT_ARTIFACTS_DIR 38 | 39 | - script: 40 | name: go build windows amd64 41 | code: | 42 | export CGO_ENABLED=0 43 | GOOS=windows GOARCH=amd64 go build \ 44 | -ldflags="-s -X github.com/wercker/stern.GitCommit=$WERCKER_GIT_COMMIT -X github.com/wercker/stern.PatchVersion=$(( ($(date +%s) - $(date --date=20150101 +%s) )/(60*60*24) )) -X github.com/wercker/stern.Compiled=$(date +%s)" \ 45 | -installsuffix cgo \ 46 | -o $WERCKER_OUTPUT_DIR/artifacts/latest/windows_amd64/stern.exe 47 | 48 | cp $WERCKER_OUTPUT_DIR/artifacts/latest/windows_amd64/stern.exe $WERCKER_REPORT_ARTIFACTS_DIR 49 | 50 | - script: 51 | name: generate SHAs 52 | code: | 53 | cd $WERCKER_OUTPUT_DIR 54 | for f in $(echo "$WERCKER_OUTPUT_DIR/artifacts/latest/linux_amd64/stern" "$WERCKER_OUTPUT_DIR/artifacts/latest/darwin_amd64/stern" "$WERCKER_OUTPUT_DIR/artifacts/latest/windows_amd64/stern.exe"); do 55 | cd $(dirname "$f") 56 | sha256sum $(basename "$f") > SHA256SUMS 57 | done 58 | 59 | - script: 60 | name: prepare 61 | code: cp -r scripts $WERCKER_OUTPUT_DIR 62 | 63 | release-stable: 64 | steps: 65 | - install-packages: 66 | packages: jshon 67 | 68 | - script: 69 | name: version 70 | code: | 71 | export STERN_VERSION=$(./artifacts/latest/linux_amd64/stern --version | awk '{print $3}') 72 | echo "Releasing stern stable v$STERN_VERSION" > $WERCKER_REPORT_MESSAGE_FILE 73 | echo "Releasing stern stable v$STERN_VERSION" 74 | 75 | - script: 76 | name: generate release notes 77 | code: | 78 | export RELEASE_NOTES=$(jshon -s "$(./scripts/create-release-notes.sh)" | sed 's/.$//; s/^.//') 79 | echo $RELEASE_NOTES 80 | 81 | - github-create-release: 82 | token: $GITHUB_TOKEN 83 | tag: $STERN_VERSION 84 | body: $RELEASE_NOTES 85 | 86 | - github-upload-asset: 87 | token: $GITHUB_TOKEN 88 | file: ./artifacts/latest/linux_amd64/stern 89 | filename: stern_linux_amd64 90 | content-type: application/octet-stream 91 | 92 | - github-upload-asset: 93 | token: $GITHUB_TOKEN 94 | file: ./artifacts/latest/darwin_amd64/stern 95 | filename: stern_darwin_amd64 96 | content-type: application/octet-stream 97 | 98 | - github-upload-asset: 99 | token: $GITHUB_TOKEN 100 | file: ./artifacts/latest/windows_amd64/stern.exe 101 | filename: stern_windows_amd64.exe 102 | content-type: application/octet-stream 103 | -------------------------------------------------------------------------------- /stern/tail.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Wercker Holding BV 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stern 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "context" 21 | "fmt" 22 | "hash/fnv" 23 | "os" 24 | "regexp" 25 | "text/template" 26 | 27 | "github.com/fatih/color" 28 | "github.com/pkg/errors" 29 | corev1 "k8s.io/api/core/v1" 30 | v1 "k8s.io/client-go/kubernetes/typed/core/v1" 31 | "k8s.io/client-go/rest" 32 | ) 33 | 34 | type Tail struct { 35 | Namespace string 36 | PodName string 37 | ContainerName string 38 | Options *TailOptions 39 | req *rest.Request 40 | closed chan struct{} 41 | Active bool 42 | podColor *color.Color 43 | containerColor *color.Color 44 | tmpl *template.Template 45 | } 46 | 47 | type TailOptions struct { 48 | Timestamps bool 49 | SinceSeconds int64 50 | Exclude []*regexp.Regexp 51 | Include []*regexp.Regexp 52 | Namespace bool 53 | TailLines *int64 54 | } 55 | 56 | // NewTail returns a new tail for a Kubernetes container inside a pod 57 | func NewTail(namespace, podName, containerName string, tmpl *template.Template, options *TailOptions) *Tail { 58 | return &Tail{ 59 | Namespace: namespace, 60 | PodName: podName, 61 | ContainerName: containerName, 62 | Options: options, 63 | closed: make(chan struct{}), 64 | Active: true, 65 | tmpl: tmpl, 66 | } 67 | } 68 | 69 | var colorList = [][2]*color.Color{ 70 | {color.New(color.FgHiCyan), color.New(color.FgCyan)}, 71 | {color.New(color.FgHiGreen), color.New(color.FgGreen)}, 72 | {color.New(color.FgHiMagenta), color.New(color.FgMagenta)}, 73 | {color.New(color.FgHiYellow), color.New(color.FgYellow)}, 74 | {color.New(color.FgHiBlue), color.New(color.FgBlue)}, 75 | {color.New(color.FgHiRed), color.New(color.FgRed)}, 76 | } 77 | 78 | func determineColor(podName string) (podColor, containerColor *color.Color) { 79 | hash := fnv.New32() 80 | hash.Write([]byte(podName)) 81 | idx := hash.Sum32() % uint32(len(colorList)) 82 | 83 | colors := colorList[idx] 84 | return colors[0], colors[1] 85 | } 86 | 87 | // Start starts tailing 88 | func (t *Tail) Start(ctx context.Context, i v1.PodInterface, logC chan<- string) { 89 | t.podColor, t.containerColor = determineColor(t.PodName) 90 | 91 | go func() { 92 | g := color.New(color.FgHiGreen, color.Bold).SprintFunc() 93 | p := t.podColor.SprintFunc() 94 | c := t.containerColor.SprintFunc() 95 | if t.Options.Namespace { 96 | logC <- fmt.Sprintf("%s %s %s › %s\n", g("+"), p(t.Namespace), p(t.PodName), c(t.ContainerName)) 97 | } else { 98 | logC <- fmt.Sprintf("%s %s › %s\n", g("+"), p(t.PodName), c(t.ContainerName)) 99 | } 100 | 101 | req := i.GetLogs(t.PodName, &corev1.PodLogOptions{ 102 | Follow: true, 103 | Timestamps: t.Options.Timestamps, 104 | Container: t.ContainerName, 105 | SinceSeconds: &t.Options.SinceSeconds, 106 | TailLines: t.Options.TailLines, 107 | }) 108 | 109 | stream, err := req.Stream() 110 | if err != nil { 111 | fmt.Println(errors.Wrapf(err, "Error opening stream to %s/%s: %s\n", t.Namespace, t.PodName, t.ContainerName)) 112 | t.Active = false 113 | return 114 | } 115 | defer stream.Close() 116 | 117 | go func() { 118 | <-t.closed 119 | stream.Close() 120 | t.Active = false 121 | }() 122 | 123 | reader := bufio.NewReader(stream) 124 | 125 | OUTER: 126 | for { 127 | line, err := reader.ReadBytes('\n') 128 | if err != nil { 129 | return 130 | } 131 | 132 | str := string(line) 133 | 134 | for _, rex := range t.Options.Exclude { 135 | if rex.MatchString(str) { 136 | continue OUTER 137 | } 138 | } 139 | 140 | if len(t.Options.Include) != 0 { 141 | matches := false 142 | for _, rin := range t.Options.Include { 143 | if rin.MatchString(str) { 144 | matches = true 145 | break 146 | } 147 | } 148 | if !matches { 149 | continue OUTER 150 | } 151 | } 152 | 153 | logC <- t.Print(str) 154 | } 155 | }() 156 | 157 | go func() { 158 | <-ctx.Done() 159 | close(t.closed) 160 | }() 161 | } 162 | 163 | // Close stops tailing 164 | func (t *Tail) Close() { 165 | r := color.New(color.FgHiRed, color.Bold).SprintFunc() 166 | p := t.podColor.SprintFunc() 167 | if t.Options.Namespace { 168 | fmt.Fprintf(os.Stderr, "%s %s %s\n", r("-"), p(t.Namespace), p(t.PodName)) 169 | } else { 170 | fmt.Fprintf(os.Stderr, "%s %s\n", r("-"), p(t.PodName)) 171 | } 172 | close(t.closed) 173 | } 174 | 175 | // Print prints a color coded log message with the pod and container names 176 | func (t *Tail) Print(msg string) string { 177 | vm := Log{ 178 | Message: msg, 179 | Namespace: t.Namespace, 180 | PodName: t.PodName, 181 | ContainerName: t.ContainerName, 182 | PodColor: t.podColor, 183 | ContainerColor: t.containerColor, 184 | } 185 | 186 | var buf bytes.Buffer 187 | err := t.tmpl.Execute(&buf, vm) 188 | if err != nil { 189 | os.Stderr.WriteString(fmt.Sprintf("expanding template failed: %s", err)) 190 | return "" 191 | } 192 | 193 | return buf.String() 194 | } 195 | 196 | // Log is the object which will be used together with the template to generate 197 | // the output. 198 | type Log struct { 199 | // Message is the log message itself 200 | Message string `json:"message"` 201 | 202 | // Namespace of the pod 203 | Namespace string `json:"namespace"` 204 | 205 | // PodName of the pod 206 | PodName string `json:"podName"` 207 | 208 | // ContainerName of the container 209 | ContainerName string `json:"containerName"` 210 | 211 | PodColor *color.Color `json:"-"` 212 | ContainerColor *color.Color `json:"-"` 213 | } 214 | -------------------------------------------------------------------------------- /cmd/completion.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Wercker Holding BV 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "os" 21 | 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | const ( 26 | // bash_completion_func depends on kubectl. 27 | // If kubectl isn't installed, it doesn't work, but error doesn't occur. 28 | bash_completion_func = ` 29 | __is_kubectl_installed=true 30 | if ! which kubectl >/dev/null 2>&1; then 31 | __is_kubectl_installed=false 32 | fi 33 | 34 | __kubectl_override_flag_list=(kubeconfig context namespace) 35 | __kubectl_override_flags() 36 | { 37 | local ${__kubectl_override_flag_list[*]} two_word_of of 38 | for w in "${words[@]}"; do 39 | if [ -n "${two_word_of}" ]; then 40 | eval "${two_word_of}=\"--${two_word_of}=\${w}\"" 41 | two_word_of= 42 | continue 43 | fi 44 | for of in "${__kubectl_override_flag_list[@]}"; do 45 | case "${w}" in 46 | --${of}=*) 47 | eval "${of}=\"${w}\"" 48 | ;; 49 | --${of}) 50 | two_word_of="${of}" 51 | ;; 52 | esac 53 | done 54 | if [ "${w}" == "--all-namespaces" ]; then 55 | namespace="--all-namespaces" 56 | fi 57 | done 58 | for of in "${__kubectl_override_flag_list[@]}"; do 59 | if eval "test -n \"\$${of}\""; then 60 | eval "echo \${${of}}" 61 | fi 62 | done 63 | } 64 | 65 | __kubectl_get_namespaces() 66 | { 67 | local template kubectl_out 68 | 69 | if ! $__is_kubectl_installed; then 70 | return 1 71 | fi 72 | template="{{ range .items }}{{ .metadata.name }} {{ end }}" 73 | if kubectl_out=$(kubectl get -o template --template="${template}" namespace 2>/dev/null); then 74 | COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) ) 75 | fi 76 | } 77 | 78 | __kubectl_config_get_contexts() 79 | { 80 | local template kubectl_out 81 | 82 | if ! $__is_kubectl_installed; then 83 | return 1 84 | fi 85 | template="{{ range .contexts }}{{ .name }} {{ end }}" 86 | if kubectl_out=$(kubectl config $(__kubectl_override_flags) -o template --template="${template}" view 2>/dev/null); then 87 | COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) ) 88 | fi 89 | } 90 | ` 91 | ) 92 | 93 | var bash_completion_flags = map[string]string{ 94 | "namespace": "__kubectl_get_namespaces", 95 | "context": "__kubectl_config_get_contexts", 96 | } 97 | 98 | func runCompletion(shell string, cmd *cobra.Command) error { 99 | var err error 100 | 101 | switch shell { 102 | case "bash": 103 | err = runCompletionBash(cmd) 104 | case "zsh": 105 | err = runCompletionZsh(cmd) 106 | default: 107 | err = fmt.Errorf("Unsupported shell type: %q", shell) 108 | } 109 | 110 | return err 111 | } 112 | 113 | func runCompletionBash(cmd *cobra.Command) error { 114 | return cmd.GenBashCompletion(os.Stdout) 115 | } 116 | 117 | // runCompletionZsh is based on `kubectl completion zsh`. This function should 118 | // be replaced by cobra implementation when cobra itself supports zsh completion. 119 | // https://github.com/kubernetes/kubernetes/blob/v1.6.1/pkg/kubectl/cmd/completion.go#L136 120 | func runCompletionZsh(cmd *cobra.Command) error { 121 | out := new(bytes.Buffer) 122 | 123 | zshInitialization := ` 124 | __stern_bash_source() { 125 | alias shopt=':' 126 | alias _expand=_bash_expand 127 | alias _complete=_bash_comp 128 | emulate -L sh 129 | setopt kshglob noshglob braceexpand 130 | source "$@" 131 | } 132 | __stern_type() { 133 | # -t is not supported by zsh 134 | if [ "$1" == "-t" ]; then 135 | shift 136 | # fake Bash 4 to disable "complete -o nospace". Instead 137 | # "compopt +-o nospace" is used in the code to toggle trailing 138 | # spaces. We don't support that, but leave trailing spaces on 139 | # all the time 140 | if [ "$1" = "__stern_compopt" ]; then 141 | echo builtin 142 | return 0 143 | fi 144 | fi 145 | type "$@" 146 | } 147 | __stern_compgen() { 148 | local completions w 149 | completions=( $(compgen "$@") ) || return $? 150 | # filter by given word as prefix 151 | while [[ "$1" = -* && "$1" != -- ]]; do 152 | shift 153 | shift 154 | done 155 | if [[ "$1" == -- ]]; then 156 | shift 157 | fi 158 | for w in "${completions[@]}"; do 159 | if [[ "${w}" = "$1"* ]]; then 160 | echo "${w}" 161 | fi 162 | done 163 | } 164 | __stern_compopt() { 165 | true # don't do anything. Not supported by bashcompinit in zsh 166 | } 167 | __stern_ltrim_colon_completions() 168 | { 169 | if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then 170 | # Remove colon-word prefix from COMPREPLY items 171 | local colon_word=${1%${1##*:}} 172 | local i=${#COMPREPLY[*]} 173 | while [[ $((--i)) -ge 0 ]]; do 174 | COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} 175 | done 176 | fi 177 | } 178 | __stern_get_comp_words_by_ref() { 179 | cur="${COMP_WORDS[COMP_CWORD]}" 180 | prev="${COMP_WORDS[${COMP_CWORD}-1]}" 181 | words=("${COMP_WORDS[@]}") 182 | cword=("${COMP_CWORD[@]}") 183 | } 184 | __stern_filedir() { 185 | local RET OLD_IFS w qw 186 | __debug "_filedir $@ cur=$cur" 187 | if [[ "$1" = \~* ]]; then 188 | # somehow does not work. Maybe, zsh does not call this at all 189 | eval echo "$1" 190 | return 0 191 | fi 192 | OLD_IFS="$IFS" 193 | IFS=$'\n' 194 | if [ "$1" = "-d" ]; then 195 | shift 196 | RET=( $(compgen -d) ) 197 | else 198 | RET=( $(compgen -f) ) 199 | fi 200 | IFS="$OLD_IFS" 201 | IFS="," __debug "RET=${RET[@]} len=${#RET[@]}" 202 | for w in ${RET[@]}; do 203 | if [[ ! "${w}" = "${cur}"* ]]; then 204 | continue 205 | fi 206 | if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then 207 | qw="$(__stern_quote "${w}")" 208 | if [ -d "${w}" ]; then 209 | COMPREPLY+=("${qw}/") 210 | else 211 | COMPREPLY+=("${qw}") 212 | fi 213 | fi 214 | done 215 | } 216 | __stern_quote() { 217 | if [[ $1 == \'* || $1 == \"* ]]; then 218 | # Leave out first character 219 | printf %q "${1:1}" 220 | else 221 | printf %q "$1" 222 | fi 223 | } 224 | autoload -U +X bashcompinit && bashcompinit 225 | # use word boundary patterns for BSD or GNU sed 226 | LWORD='[[:<:]]' 227 | RWORD='[[:>:]]' 228 | if sed --help 2>&1 | grep -q GNU; then 229 | LWORD='\<' 230 | RWORD='\>' 231 | fi 232 | __stern_convert_bash_to_zsh() { 233 | sed \ 234 | -e 's/declare -F/whence -w/' \ 235 | -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \ 236 | -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ 237 | -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ 238 | -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ 239 | -e "s/${LWORD}_filedir${RWORD}/__stern_filedir/g" \ 240 | -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__stern_get_comp_words_by_ref/g" \ 241 | -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__stern_ltrim_colon_completions/g" \ 242 | -e "s/${LWORD}compgen${RWORD}/__stern_compgen/g" \ 243 | -e "s/${LWORD}compopt${RWORD}/__stern_compopt/g" \ 244 | -e "s/${LWORD}declare${RWORD}/builtin declare/g" \ 245 | -e "s/\\\$(type${RWORD}/\$(__stern_type/g" \ 246 | <<'BASH_COMPLETION_EOF' 247 | ` 248 | out.Write([]byte(zshInitialization)) 249 | 250 | if err := cmd.GenBashCompletion(out); err != nil { 251 | return err 252 | } 253 | 254 | zshTail := ` 255 | BASH_COMPLETION_EOF 256 | } 257 | __stern_bash_source <(__stern_convert_bash_to_zsh) 258 | ` 259 | out.Write([]byte(zshTail)) 260 | 261 | fmt.Println(out) 262 | 263 | return nil 264 | } 265 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stern 2 | 3 | [![wercker status](https://app.wercker.com/status/fb1ed340ffed75c22dc301c38ab0893c/s/master "wercker status")](https://app.wercker.com/project/byKey/fb1ed340ffed75c22dc301c38ab0893c) 4 | 5 | Stern allows you to `tail` multiple pods on Kubernetes and multiple containers 6 | within the pod. Each result is color coded for quicker debugging. 7 | 8 | The query is a regular expression so the pod name can easily be filtered and 9 | you don't need to specify the exact id (for instance omitting the deployment 10 | id). If a pod is deleted it gets removed from tail and if a new pod is added it 11 | automatically gets tailed. 12 | 13 | When a pod contains multiple containers Stern can tail all of them too without 14 | having to do this manually for each one. Simply specify the `container` flag to 15 | limit what containers to show. By default all containers are listened to. 16 | 17 | ## Installation 18 | 19 | If you don't want to build from source go grab a [binary release](https://github.com/wercker/stern/releases) 20 | 21 | ``` 22 | git clone https://github.com/wercker/stern.git && cd stern 23 | go install 24 | ``` 25 | 26 | ### Homebrew 27 | 28 | On macOS, you can also install Stern using [Homebrew](https://brew.sh/): 29 | ``` 30 | brew install stern 31 | ``` 32 | 33 | ## Usage 34 | 35 | ``` 36 | stern pod-query [flags] 37 | ``` 38 | 39 | The `pod` query is a regular expression so you could provide `"web-\w"` to tail 40 | `web-backend` and `web-frontend` pods but not `web-123`. 41 | 42 | ### cli flags 43 | 44 | | flag | default | purpose | 45 | |----------------------|------------------|--------------------------------------------------------------------------------------------------------------| 46 | | `--container` | `.*` | Container name when multiple containers in pod (regular expression) | 47 | | `--exclude-container`| | Container name to exclude when multiple containers in pod (regular expression) | 48 | | `--container-state` | `running` | Tail containers with status in running, waiting or terminated. Default to running. | 49 | | `--timestamps` | | Print timestamps | 50 | | `--since` | | Return logs newer than a relative duration like 52, 2m, or 3h. Displays all if omitted | 51 | | `--context` | | Kubernetes context to use. Default to `kubectl config current-context` | 52 | | `--exclude` | | Log lines to exclude; specify multiple with additional `--exclude`; (regular expression) | 53 | | `--namespace` | | Kubernetes namespace to use. Default to namespace configured in Kubernetes context | 54 | | `--kubeconfig` | `~/.kube/config` | Path to kubeconfig file to use | 55 | | `--all-namespaces` | | If present, tail across all namespaces. A specific namespace is ignored even if specified with --namespace. | 56 | | `--selector` | | Selector (label query) to filter on. If present, default to `.*` for the pod-query. | 57 | | `--tail` | `-1` | The number of lines from the end of the logs to show. Defaults to -1, showing all logs. | 58 | | `--color` | `auto` | Force set color output. `auto`: colorize if tty attached, `always`: always colorize, `never`: never colorize | 59 | | `--output` | `default` | Specify predefined template. Currently support: [default, raw, json] See templates section | 60 | | `template` | | Template to use for log lines, leave empty to use --output flag | 61 | 62 | See `stern --help` for details 63 | 64 | Stern will use the `$KUBECONFIG` environment variable if set. If both the 65 | environment variable and `--kubeconfig` flag are passed the cli flag will be 66 | used. 67 | 68 | ### templates 69 | 70 | stern supports outputting custom log messages. There are a few predefined 71 | templates which you can use by specifying the `--output` flag: 72 | 73 | | output | description | 74 | |-----------|-------------------------------------------------------------------------------------------------------| 75 | | `default` | Displays the namespace, pod and container, and decorates it with color depending on --color | 76 | | `raw` | Only outputs the log message itself, useful when your logs are json and you want to pipe them to `jq` | 77 | | `json` | Marshals the log struct to json. Useful for programmatic purposes | 78 | 79 | It accepts a custom template through the `--template` flag, which will be 80 | compiled to a Go template and then used for every log message. This Go template 81 | will receive the following struct: 82 | 83 | | property | type | description | 84 | |-----------------|--------|---------------------------| 85 | | `Message` | string | The log message itself | 86 | | `Namespace` | string | The namespace of the pod | 87 | | `PodName` | string | The name of the pod | 88 | | `ContainerName` | string | The name of the container | 89 | 90 | The following functions are available within the template (besides the [builtin 91 | functions](https://golang.org/pkg/text/template/#hdr-Functions)): 92 | 93 | | func | arguments | description | 94 | |---------|-----------------------|-----------------------------------------------------------------| 95 | | `json` | `object` | Marshal the object and output it as a json text | 96 | | `color` | `color.Color, string` | Wrap the text in color (.ContainerColor and .PodColor provided) | 97 | 98 | 99 | 100 | ## Examples: 101 | 102 | Tail the `gateway` container running inside of the `envvars` pod on staging 103 | ``` 104 | stern envvars --context staging --container gateway 105 | ``` 106 | 107 | Tail the `staging` namespace excluding logs from `istio-proxy` container 108 | ``` 109 | stern -n staging --exclude-container istio-proxy . 110 | ``` 111 | 112 | Show auth activity from 15min ago with timestamps 113 | ``` 114 | stern auth -t --since 15m 115 | ``` 116 | 117 | Follow the development of `some-new-feature` in minikube 118 | ``` 119 | stern some-new-feature --context minikube 120 | ``` 121 | 122 | View pods from another namespace 123 | ``` 124 | stern kubernetes-dashboard --namespace kube-system 125 | ``` 126 | 127 | Tail the pods filtered by `run=nginx` label selector across all namespaces 128 | ``` 129 | stern --all-namespaces -l run=nginx 130 | ``` 131 | 132 | Follow the `frontend` pods in canary release 133 | ``` 134 | stern frontend --selector release=canary 135 | ``` 136 | 137 | Pipe the log message to jq: 138 | ``` 139 | stern backend -o json | jq . 140 | ``` 141 | 142 | Only output the log message itself: 143 | ``` 144 | stern backend -o raw 145 | ``` 146 | 147 | Output using a custom template: 148 | 149 | ``` 150 | stern --template '{{.Message}} ({{.Namespace}}/{{.PodName}}/{{.ContainerName}})' backend 151 | ``` 152 | 153 | Output using a custom template with stern-provided colors: 154 | 155 | ``` 156 | stern --template '{{.Message}} ({{.Namespace}}/{{color .PodColor .PodName}}/{{color .ContainerColor .ContainerName}})' backend 157 | ``` 158 | 159 | ## Completion 160 | 161 | Stern supports command-line auto completion for bash or zsh. `stern 162 | --completion=(bash|zsh)` outputs the shell completion code which work by being 163 | evaluated in `.bashrc`, etc for the specified shell. In addition, Stern 164 | supports dynamic completion for `--namespace` and `--context`. In order to use 165 | that, kubectl must be installed on your environment. 166 | 167 | If you use bash, stern bash completion code depends on the 168 | [bash-completion](https://github.com/scop/bash-completion). On the macOS, you 169 | can install it with homebrew as follows: 170 | 171 | ``` 172 | $ brew install bash-completion 173 | ``` 174 | 175 | Note that bash-completion must be sourced before sourcing the stern bash 176 | completion code in `.bashrc`. 177 | 178 | ```sh 179 | source <(brew --prefix)/etc/bash-completion 180 | source <(stern --completion=bash) 181 | ``` 182 | 183 | If you use zsh, just source the stern zsh completion code in `.zshrc`. 184 | 185 | ```sh 186 | source <(stern --completion=zsh) 187 | ``` 188 | 189 | ## Contributing to this repository 190 | 191 | Oracle welcomes contributions to this repository from anyone. Please see 192 | [CONTRIBUTING](CONTRIBUTING.md) for details. 193 | -------------------------------------------------------------------------------- /cmd/cli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Wercker Holding BV 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "log" 22 | "os" 23 | "path/filepath" 24 | "regexp" 25 | "text/template" 26 | "time" 27 | 28 | "k8s.io/apimachinery/pkg/labels" 29 | 30 | homedir "github.com/mitchellh/go-homedir" 31 | "github.com/pkg/errors" 32 | "github.com/spf13/cobra" 33 | "github.com/wercker/stern/stern" 34 | 35 | "github.com/fatih/color" 36 | ) 37 | 38 | const version = "1.11.0" 39 | 40 | type Options struct { 41 | container string 42 | excludeContainer string 43 | containerState []string 44 | timestamps bool 45 | since time.Duration 46 | context string 47 | namespace string 48 | kubeConfig string 49 | exclude []string 50 | include []string 51 | initContainers bool 52 | allNamespaces bool 53 | selector string 54 | tail int64 55 | color string 56 | version bool 57 | completion string 58 | template string 59 | output string 60 | } 61 | 62 | var opts = &Options{ 63 | container: ".*", 64 | containerState: []string{stern.RUNNING, stern.WAITING}, 65 | initContainers: true, 66 | tail: -1, 67 | color: "auto", 68 | template: "", 69 | output: "default", 70 | } 71 | 72 | func Run() { 73 | cmd := &cobra.Command{} 74 | cmd.Use = "stern pod-query" 75 | cmd.Short = "Tail multiple pods and containers from Kubernetes" 76 | 77 | cmd.Flags().StringVarP(&opts.container, "container", "c", opts.container, "Container name when multiple containers in pod") 78 | cmd.Flags().StringVarP(&opts.excludeContainer, "exclude-container", "E", opts.excludeContainer, "Exclude a Container name") 79 | cmd.Flags().StringSliceVar(&opts.containerState, "container-state", opts.containerState, "If present, tail containers with status in running, waiting or terminated. Default to running and waiting.") 80 | cmd.Flags().BoolVarP(&opts.timestamps, "timestamps", "t", opts.timestamps, "Print timestamps") 81 | cmd.Flags().DurationVarP(&opts.since, "since", "s", opts.since, "Return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to 48h.") 82 | cmd.Flags().StringVar(&opts.context, "context", opts.context, "Kubernetes context to use. Default to current context configured in kubeconfig.") 83 | cmd.Flags().StringVarP(&opts.namespace, "namespace", "n", opts.namespace, "Kubernetes namespace to use. Default to namespace configured in Kubernetes context") 84 | cmd.Flags().StringVar(&opts.kubeConfig, "kubeconfig", opts.kubeConfig, "Path to kubeconfig file to use") 85 | cmd.Flags().StringVar(&opts.kubeConfig, "kube-config", opts.kubeConfig, "Path to kubeconfig file to use") 86 | cmd.Flags().MarkDeprecated("kube-config", "Use --kubeconfig instead.") 87 | cmd.Flags().StringSliceVarP(&opts.exclude, "exclude", "e", opts.exclude, "Regex of log lines to exclude") 88 | cmd.Flags().StringSliceVarP(&opts.include, "include", "i", opts.include, "Regex of log lines to include") 89 | cmd.Flags().BoolVar(&opts.initContainers, "init-containers", opts.initContainers, "Include init containers") 90 | cmd.Flags().BoolVar(&opts.allNamespaces, "all-namespaces", opts.allNamespaces, "If present, tail across all namespaces. A specific namespace is ignored even if specified with --namespace.") 91 | cmd.Flags().StringVarP(&opts.selector, "selector", "l", opts.selector, "Selector (label query) to filter on. If present, default to \".*\" for the pod-query.") 92 | cmd.Flags().Int64Var(&opts.tail, "tail", opts.tail, "The number of lines from the end of the logs to show. Defaults to -1, showing all logs.") 93 | cmd.Flags().StringVar(&opts.color, "color", opts.color, "Color output. Can be 'always', 'never', or 'auto'") 94 | cmd.Flags().BoolVarP(&opts.version, "version", "v", opts.version, "Print the version and exit") 95 | cmd.Flags().StringVar(&opts.completion, "completion", opts.completion, "Outputs stern command-line completion code for the specified shell. Can be 'bash' or 'zsh'") 96 | cmd.Flags().StringVar(&opts.template, "template", opts.template, "Template to use for log lines, leave empty to use --output flag") 97 | cmd.Flags().StringVarP(&opts.output, "output", "o", opts.output, "Specify predefined template. Currently support: [default, raw, json]") 98 | 99 | // Specify custom bash completion function 100 | cmd.BashCompletionFunction = bash_completion_func 101 | for name, completion := range bash_completion_flags { 102 | if cmd.Flag(name) != nil { 103 | if cmd.Flag(name).Annotations == nil { 104 | cmd.Flag(name).Annotations = map[string][]string{} 105 | } 106 | cmd.Flag(name).Annotations[cobra.BashCompCustom] = append( 107 | cmd.Flag(name).Annotations[cobra.BashCompCustom], 108 | completion, 109 | ) 110 | } 111 | } 112 | 113 | cmd.RunE = func(cmd *cobra.Command, args []string) error { 114 | if opts.version { 115 | fmt.Printf("stern version %s\n", version) 116 | return nil 117 | } 118 | 119 | if opts.completion != "" { 120 | return runCompletion(opts.completion, cmd) 121 | } 122 | 123 | narg := len(args) 124 | if (narg > 1) || (narg == 0 && opts.selector == "") { 125 | return cmd.Help() 126 | } 127 | config, err := parseConfig(args) 128 | if err != nil { 129 | log.Println(err) 130 | os.Exit(2) 131 | } 132 | 133 | ctx, cancel := context.WithCancel(context.Background()) 134 | defer cancel() 135 | 136 | err = stern.Run(ctx, config) 137 | if err != nil { 138 | fmt.Println(err) 139 | os.Exit(1) 140 | } 141 | 142 | return nil 143 | } 144 | 145 | if err := cmd.Execute(); err != nil { 146 | log.Fatal(err) 147 | } 148 | } 149 | 150 | func parseConfig(args []string) (*stern.Config, error) { 151 | kubeConfig, err := getKubeConfig() 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | var podQuery string 157 | if len(args) == 0 { 158 | podQuery = ".*" 159 | } else { 160 | podQuery = args[0] 161 | } 162 | pod, err := regexp.Compile(podQuery) 163 | if err != nil { 164 | return nil, errors.Wrap(err, "failed to compile regular expression from query") 165 | } 166 | 167 | container, err := regexp.Compile(opts.container) 168 | if err != nil { 169 | return nil, errors.Wrap(err, "failed to compile regular expression for container query") 170 | } 171 | 172 | var excludeContainer *regexp.Regexp 173 | if opts.excludeContainer != "" { 174 | excludeContainer, err = regexp.Compile(opts.excludeContainer) 175 | if err != nil { 176 | return nil, errors.Wrap(err, "failed to compile regular expression for exclude container query") 177 | } 178 | } 179 | 180 | var exclude []*regexp.Regexp 181 | for _, ex := range opts.exclude { 182 | rex, err := regexp.Compile(ex) 183 | if err != nil { 184 | return nil, errors.Wrap(err, "failed to compile regular expression for exclusion filter") 185 | } 186 | 187 | exclude = append(exclude, rex) 188 | } 189 | 190 | var include []*regexp.Regexp 191 | for _, inc := range opts.include { 192 | rin, err := regexp.Compile(inc) 193 | if err != nil { 194 | return nil, errors.Wrap(err, "failed to compile regular expression for inclusion filter") 195 | } 196 | 197 | include = append(include, rin) 198 | } 199 | 200 | containerState, err := stern.NewContainerState(opts.containerState) 201 | if err != nil { 202 | return nil, err 203 | } 204 | 205 | var labelSelector labels.Selector 206 | selector := opts.selector 207 | if selector == "" { 208 | labelSelector = labels.Everything() 209 | } else { 210 | labelSelector, err = labels.Parse(selector) 211 | if err != nil { 212 | return nil, errors.Wrap(err, "failed to parse selector as label selector") 213 | } 214 | } 215 | 216 | var tailLines *int64 217 | if opts.tail != -1 { 218 | tailLines = &opts.tail 219 | } 220 | 221 | colorFlag := opts.color 222 | if colorFlag == "always" { 223 | color.NoColor = false 224 | } else if colorFlag == "never" { 225 | color.NoColor = true 226 | } else if colorFlag != "auto" { 227 | return nil, errors.New("color should be one of 'always', 'never', or 'auto'") 228 | } 229 | 230 | t := opts.template 231 | if t == "" { 232 | switch opts.output { 233 | case "default": 234 | if color.NoColor { 235 | t = "{{.PodName}} {{.ContainerName}} {{.Message}}" 236 | if opts.allNamespaces { 237 | t = fmt.Sprintf("{{.Namespace}} %s", t) 238 | } 239 | } else { 240 | t = "{{color .PodColor .PodName}} {{color .ContainerColor .ContainerName}} {{.Message}}" 241 | if opts.allNamespaces { 242 | t = fmt.Sprintf("{{color .PodColor .Namespace}} %s", t) 243 | } 244 | 245 | } 246 | case "raw": 247 | t = "{{.Message}}" 248 | case "json": 249 | t = "{{json .}}\n" 250 | } 251 | } 252 | 253 | funs := map[string]interface{}{ 254 | "json": func(in interface{}) (string, error) { 255 | b, err := json.Marshal(in) 256 | if err != nil { 257 | return "", err 258 | } 259 | return string(b), nil 260 | }, 261 | "color": func(color color.Color, text string) string { 262 | return color.SprintFunc()(text) 263 | }, 264 | } 265 | template, err := template.New("log").Funcs(funs).Parse(t) 266 | if err != nil { 267 | return nil, errors.Wrap(err, "unable to parse template") 268 | } 269 | 270 | if opts.since == 0 { 271 | opts.since = 172800000000000 // 48h 272 | } 273 | 274 | return &stern.Config{ 275 | KubeConfig: kubeConfig, 276 | PodQuery: pod, 277 | ContainerQuery: container, 278 | ExcludeContainerQuery: excludeContainer, 279 | ContainerState: containerState, 280 | Exclude: exclude, 281 | Include: include, 282 | Timestamps: opts.timestamps, 283 | Since: opts.since, 284 | ContextName: opts.context, 285 | Namespace: opts.namespace, 286 | AllNamespaces: opts.allNamespaces, 287 | LabelSelector: labelSelector, 288 | TailLines: tailLines, 289 | Template: template, 290 | }, nil 291 | } 292 | 293 | func getKubeConfig() (string, error) { 294 | var kubeconfig string 295 | 296 | if kubeconfig = opts.kubeConfig; kubeconfig != "" { 297 | return kubeconfig, nil 298 | } 299 | 300 | if kubeconfig = os.Getenv("KUBECONFIG"); kubeconfig != "" { 301 | return kubeconfig, nil 302 | } 303 | 304 | // kubernetes requires an absolute path 305 | home, err := homedir.Dir() 306 | if err != nil { 307 | return "", errors.Wrap(err, "failed to get user home directory") 308 | } 309 | 310 | kubeconfig = filepath.Join(home, ".kube/config") 311 | 312 | return kubeconfig, nil 313 | } 314 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtzYCrT+um/rrSQ1BfA= 4 | github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 5 | github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= 6 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 7 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 8 | github.com/Azure/go-autorest/autorest/adal v0.6.0 h1:UCTq22yE3RPgbU/8u4scfnnzuCW6pwQ9n+uBtV78ouo= 9 | github.com/Azure/go-autorest/autorest/adal v0.6.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= 10 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 11 | github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= 12 | github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= 13 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 14 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 15 | github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= 16 | github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= 17 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 18 | github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= 19 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda h1:NyywMz59neOoVRFDz+ccfKWxn784fiHMDnZSy6T+JXY= 24 | github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 25 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 26 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 27 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 28 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 29 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 30 | github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 31 | github.com/fatih/color v0.0.0-20180516100307-2d684516a886 h1:uG3h1WD7I3u1FP2+EdJjjhM1A3DKbZuRQz8H5cv6fyE= 32 | github.com/fatih/color v0.0.0-20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 33 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 34 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 35 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 36 | github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 37 | github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE= 38 | github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 39 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 40 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 41 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 42 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 44 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 45 | github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 46 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 47 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 48 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 49 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 50 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 51 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 52 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 53 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 54 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 55 | github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= 56 | github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= 57 | github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= 58 | github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 59 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 60 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 61 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 62 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 63 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 64 | github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= 65 | github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 66 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 67 | github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 68 | github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= 69 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 70 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 71 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 72 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 73 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 74 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 75 | github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= 76 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 77 | github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 h1:eQox4Rh4ewJF+mqYPxCkmBAirRnPaHEB26UkNuPyjlk= 78 | github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 79 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 80 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 81 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 82 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 83 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 84 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 85 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 86 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 87 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 88 | github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 89 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 90 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 91 | github.com/pkg/errors v0.0.0-20180311214515-816c9085562c h1:F5RoIh7F9wB47PvXvpP1+Ihq1TkyC8iRdvwfKkESEZQ= 92 | github.com/pkg/errors v0.0.0-20180311214515-816c9085562c/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 93 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 94 | github.com/spf13/cobra v0.0.0-20180629152535-a114f312e075 h1:xFjykS21EsLTfYTPQocDLZss7lWlJZHhZc6Ivvc8ptU= 95 | github.com/spf13/cobra v0.0.0-20180629152535-a114f312e075/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 96 | github.com/spf13/pflag v0.0.0-20180601132542-3ebe029320b2 h1:OAuq3yHhRwcm/kwCSAqf07pUm/EcLZYNz1ket+Bm0SI= 97 | github.com/spf13/pflag v0.0.0-20180601132542-3ebe029320b2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 98 | github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= 99 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 100 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 101 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 102 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 103 | golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 104 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 105 | golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM= 106 | golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 107 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 108 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 109 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 110 | golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 111 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 112 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= 113 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 114 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 115 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= 116 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 117 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 118 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 119 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 120 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 121 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 122 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 123 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 124 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= 125 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 126 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 127 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 128 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= 129 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 130 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 131 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 132 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 133 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 134 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 135 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 136 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 137 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 138 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 139 | gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 140 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 141 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 142 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 143 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 144 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 145 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 146 | k8s.io/api v0.0.0-20180708011939-8be2a0b24ed0 h1:Xd27jHDRRIIRx74wXBfD3v0FhPNr2L7LsPG30QlwomE= 147 | k8s.io/api v0.0.0-20180708011939-8be2a0b24ed0/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 148 | k8s.io/api v0.0.0-20190620084959-7cf5895f2711 h1:BblVYz/wE5WtBsD/Gvu54KyBUTJMflolzc5I2DTvh50= 149 | k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A= 150 | k8s.io/apimachinery v0.0.0-20180719193910-594fc14b6f14 h1:6TgbQjaOKpihf3ApmFZut+1w4aLMBKEGLoLNx/Y40/A= 151 | k8s.io/apimachinery v0.0.0-20180719193910-594fc14b6f14/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 152 | k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719 h1:uV4S5IB5g4Nvi+TBVNf3e9L4wrirlwYJ6w88jUQxTUw= 153 | k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA= 154 | k8s.io/client-go v0.0.0-20180719235251-739dd8f9d480 h1:bKwgfc11XEmkB+Ifyfh+3yY9Li7qBlxYcZYgqER+kTU= 155 | k8s.io/client-go v0.0.0-20180719235251-739dd8f9d480/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 156 | k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g= 157 | k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k= 158 | k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68= 159 | k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 160 | k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 161 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4= 162 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= 163 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 164 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 165 | --------------------------------------------------------------------------------