├── .gitignore ├── Gopkg.toml ├── docs ├── archetypes │ └── default.md ├── config.toml └── content │ ├── contributing │ └── index.md │ ├── introduction │ └── index.md │ └── examples │ └── index.md ├── Makefile ├── .circleci └── config.yml ├── examples ├── hello │ └── main.go ├── secrets │ └── main.go └── stdin │ └── main.go ├── LICENSE ├── README.md ├── cmd.go ├── CODE_OF_CONDUCT.md ├── kube_util.go └── Gopkg.lock /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !/**/ 3 | !*.* 4 | 5 | vendor/ 6 | public 7 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | [[constraint]] 2 | name = "k8s.io/client-go" 3 | version = "10.0.0" 4 | -------------------------------------------------------------------------------- /docs/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SUBDIRS := $(wildcard examples/*) 2 | 3 | 4 | .PHONY: dep 5 | dep: 6 | go get -u github.com/golang/dep/cmd/dep && \ 7 | dep ensure 8 | 9 | .PHONY: build 10 | build: 11 | go build 12 | 13 | .PHONY : examples $(SUBDIRS) 14 | examples : $(SUBDIRS) 15 | 16 | $(SUBDIRS) : 17 | cd $@ && \ 18 | go build 19 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | # specify the version 6 | - image: circleci/golang:1.10-stretch 7 | 8 | working_directory: /go/src/github.com/engineerd/kube-exec 9 | steps: 10 | - checkout 11 | 12 | - run: make dep 13 | - run: make build 14 | - run: make examples 15 | -------------------------------------------------------------------------------- /examples/hello/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | kube "github.com/engineerd/kube-exec" 8 | ) 9 | 10 | func main() { 11 | 12 | cfg := kube.Config{ 13 | Kubeconfig: os.Getenv("KUBECONFIG"), 14 | Image: "ubuntu", 15 | Name: "kube-example", 16 | Namespace: "default", 17 | } 18 | 19 | // also sleeping for a couple of seconds 20 | // if the pod completes too fast, we don't have time to attach to it 21 | 22 | cmd := kube.Command(cfg, "/bin/sh", "-c", "sleep 2; echo Running from Kubernetes pod;") 23 | cmd.Stdout = os.Stdout 24 | 25 | err := cmd.Run() 26 | if err != nil { 27 | log.Fatalf("error: %v", err) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/secrets/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | kube "github.com/engineerd/kube-exec" 8 | ) 9 | 10 | func main() { 11 | cfg := kube.Config{ 12 | Kubeconfig: os.Getenv("KUBECONFIG"), 13 | Image: "ubuntu", 14 | Name: "secret", 15 | Namespace: "default", 16 | 17 | Secrets: []kube.Secret{ 18 | { 19 | EnvVarName: "SUPERPRIVATESECRET", 20 | SecretName: "k8s-secret", 21 | SecretKey: "password", 22 | }, 23 | }, 24 | } 25 | 26 | cmd := kube.Command(cfg, "/bin/sh", "-c", "sleep 2; echo Your private secret: $SUPERPRIVATESECRET;") 27 | cmd.Stdout = os.Stdout 28 | 29 | err := cmd.Run() 30 | if err != nil { 31 | log.Fatalf("error: %v", err) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /examples/stdin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | 8 | kube "github.com/engineerd/kube-exec" 9 | ) 10 | 11 | func main() { 12 | 13 | cfg := kube.Config{ 14 | Kubeconfig: os.Getenv("KUBECONFIG"), 15 | Image: "ubuntu", 16 | Name: "kube-attach", 17 | Namespace: "default", 18 | } 19 | 20 | cmd := kube.Command(cfg, "/bin/sh", "-c", "while true; read test; do echo You said: $test; sleep .5; done") 21 | 22 | w, err := cmd.StdinPipe() 23 | if err != nil { 24 | log.Fatalf("cannot get pipe to stdin: %v", err) 25 | } 26 | 27 | // write in cmd.Stdin 28 | go func() { 29 | defer w.Close() 30 | _, err = io.Copy(w, os.Stdin) 31 | if err != nil { 32 | log.Fatalf("cannot copy from stdin: %v", err) 33 | } 34 | }() 35 | 36 | cmd.Stdout = os.Stdout 37 | 38 | err = cmd.Run() 39 | if err != nil { 40 | log.Fatalf("error: %v", err) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Engineerd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/config.toml: -------------------------------------------------------------------------------- 1 | baseurl = "https://engineerd.github.io/kube-exec/" 2 | languageCode = "en-us" 3 | title = "kube-exec" 4 | theme = "hugo-material-docs" 5 | metadataformat = "yaml" 6 | canonifyurls = true 7 | # Enable Google Analytics by entering your tracking id 8 | googleAnalytics = "" 9 | 10 | [params] 11 | # General information 12 | author = "Engineerd" 13 | description = "Lightweight Golang package for executing commands in remote Kubernetes pods" 14 | copyright = "Released under the MIT license" 15 | 16 | # Repository 17 | provider = "GitHub" 18 | repo_url = "https://github.com/engineerd/kube-exec" 19 | 20 | permalink = "#" 21 | 22 | [params.palette] 23 | primary = "blue-grey" 24 | accent = "light-blue" 25 | 26 | [params.font] 27 | text = "Ubuntu" 28 | code = "Ubuntu Mono" 29 | 30 | [social] 31 | twitter = "" 32 | github = "engineerd" 33 | 34 | [[menu.main]] 35 | name = "Introduction" 36 | url = "introduction/" 37 | weight = 1 38 | 39 | [[menu.main]] 40 | name = "Examples" 41 | url = "examples/" 42 | weight = 2 43 | 44 | [[menu.main]] 45 | name = "Contributing" 46 | url = "contributing/" 47 | weight = 3 48 | 49 | [blackfriday] 50 | smartypants = true 51 | fractions = true 52 | smartDashes = true 53 | plainIDAnchors = true 54 | -------------------------------------------------------------------------------- /docs/content/contributing/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2019-02-18 3 | title: Contributing 4 | --- 5 | 6 | ## Contributing 7 | 8 | We are delighted you want to contribute to this project! Keep in mind that any contribution to this project **MUST** adhere to the [Contributor Covenant Code of Conduct][coc]. 9 | 10 | Here's a short check list to help you contribute to this project: 11 | 12 | - check the [issue queue][issues] and [PR queue][prs] to make sure you're not duplicating any developer's work. 13 | - if there is an existing issue, please comment on it before starting to work on the implementation - if there isn't one, please create it. 14 | - fork the project. 15 | - follow the instructions below to make sure you have all required prerequisites to build the project. 16 | - create a pull request with your changes. 17 | 18 | Any contribution is extremely appreciated - documentation, bug fixes or features. Thank you! 19 | 20 | ## Prerequisites 21 | 22 | - [the Go toolchain][go] 23 | - [`dep`][dep] 24 | - `make` (optional) 25 | 26 | ## Building from source 27 | 28 | - `dep ensure` 29 | - `make build` to build the library 30 | - `make examples` to build all examples in `examples/` 31 | - if running locally, you should provide an environment variable for the Kubernetes configuration file: 32 | - on Linux (including Windows Subsystem for Linux) and macOS: `export KUBECONFIG=` 33 | - on Windows: `$env:KUBECONFIG=""` 34 | - alternatively, you can individually `go run` the desired example locally, provided you pass a valid Kubernetes config file 35 | 36 | 37 | [coc]: https://www.contributor-covenant.org/ 38 | 39 | [issues]: https://github.com/engineerd/kube-exec/issues 40 | [prs]: https://github.com/engineerd/kube-exec/pulls 41 | 42 | [go]: https://golang.org/doc/install 43 | [dep]: https://github.com/golang/dep 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kube-exec 2 | ========= 3 | 4 | [![CircleCI](https://circleci.com/gh/engineerd/kube-exec/tree/master.svg?style=shield&circle-token=13a8324e6b860dc9158a67d0102920047b5c1144)](https://circleci.com/gh/engineerd/kube-exec) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/engineerd/kube-exec)](https://goreportcard.com/report/github.com/engineerd/kube-exec) 6 | [![Documentation](https://godoc.org/github.com/engineerd/kube-exec?status.svg)](https://godoc.org/github.com/engineerd/kube-exec) 7 | 8 | `kube-exec` is a library similar to [`os/exec`][1] that allows you to run commands in a Kubernetes pod, as if that command was executed locally. 9 | > It is inspired from [`go-dexec`][2] by [ahmetb][3], which does the same thing, but for a Docker engine. 10 | 11 | The interface of the package is similar to `os/exec`, and essentially this: 12 | 13 | - creates a new pod in Kubernetes based on a user-specified image 14 | - waits for the pod to be in [`Running`](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/) state 15 | - attaches to the pod and allows you to stream data to the pod through `stdin`, and from the pod back to the program through `stdout` and `stderr` 16 | 17 | 18 | How to use it 19 | ------------- 20 | 21 | ```go 22 | cfg := kube.Config{ 23 | Kubeconfig: os.Getenv("KUBECONFIG"), 24 | Image: "ubuntu", 25 | Name: "kube-example", 26 | Namespace: "default", 27 | } 28 | 29 | cmd := kube.Command(cfg, "/bin/sh", "-c", "sleep 2; echo Running from Kubernetes pod;") 30 | cmd.Stdout = os.Stdout 31 | 32 | err := cmd.Run() 33 | if err != nil { 34 | log.Fatalf("error: %v", err) 35 | } 36 | ``` 37 | 38 | 39 | Here's a list of full examples you can find in this repo: 40 | 41 | - [simple hello example](/examples/hello/main.go) 42 | - [pass `stdin` to the pod](/examples/stdin/main.go) 43 | - [pass Kubernetes secrets as environment variables](/examples/secrets/main.go) 44 | 45 | 46 | [1]: https://golang.org/pkg/os/exec 47 | [2]: https://github.com/ahmetb/go-dexec 48 | [3]: https://twitter.com/ahmetb 49 | 50 | [4]: /examples/main.go 51 | -------------------------------------------------------------------------------- /docs/content/introduction/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2019-02-18 3 | title: Introduction 4 | type: index 5 | --- 6 | 7 | ## What is `kube-exec`? 8 | 9 | [![CircleCI](https://circleci.com/gh/engineerd/kube-exec/tree/master.svg?style=shield&circle-token=13a8324e6b860dc9158a67d0102920047b5c1144)](https://circleci.com/gh/engineerd/kube-exec) 10 | [![Go Report Card](https://goreportcard.com/badge/github.com/engineerd/kube-exec)](https://goreportcard.com/report/github.com/engineerd/kube-exec) 11 | [![Documentation](https://godoc.org/github.com/engineerd/kube-exec?status.svg)](https://godoc.org/github.com/engineerd/kube-exec) 12 | 13 | `kube-exec` is a library similar to [`os/exec`][1] that allows you to run commands in a Kubernetes pod, as if that command was executed locally. 14 | > It is inspired from [`go-dexec`][2] by [ahmetb][3], which does the same thing, but for a Docker engine. 15 | 16 | The interface of the package is similar to `os/exec`, and essentially this: 17 | 18 | - creates a new pod in Kubernetes based on a user-specified image 19 | - waits for the pod to be in [`Running`](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/) state 20 | - attaches to the pod and allows you to stream data to the pod through `stdin`, and from the pod back to the program through `stdout` and `stderr` 21 | 22 | 23 | ## How to use it 24 | 25 | 26 | ```go 27 | cfg := kube.Config{ 28 | Kubeconfig: os.Getenv("KUBECONFIG"), 29 | Image: "ubuntu", 30 | Name: "kube-example", 31 | Namespace: "default", 32 | } 33 | 34 | cmd := kube.Command(cfg, "/bin/sh", "-c", "sleep 2; echo Running from Kubernetes pod;") 35 | cmd.Stdout = os.Stdout 36 | 37 | err := cmd.Run() 38 | if err != nil { 39 | log.Fatalf("error: %v", err) 40 | } 41 | ``` 42 | 43 | 44 | Here's a list of full examples you can find in this repo: 45 | 46 | - [simple hello example](/examples/#the-simplest-example) 47 | - [pass `stdin` to the pod](/examples/#passing-stdin-to-the-pod) 48 | - [pass Kubernetes secrets as environment variables](/examples/#passing-secrets-to-the-pod) 49 | 50 | 51 | [1]: https://golang.org/pkg/os/exec 52 | [2]: https://github.com/ahmetb/go-dexec 53 | [3]: https://twitter.com/ahmetb 54 | -------------------------------------------------------------------------------- /cmd.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | 8 | v1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | // Config contains all Kubernetes configuration 12 | type Config struct { 13 | Kubeconfig string 14 | Namespace string 15 | Name string 16 | Image string 17 | 18 | Secrets []Secret 19 | } 20 | 21 | // Secret represents a Kubernetes secret to pass into the pod as env variable 22 | type Secret struct { 23 | EnvVarName string 24 | SecretName string 25 | SecretKey string 26 | } 27 | 28 | // Cmd represents the command to execute inside the pod 29 | type Cmd struct { 30 | Path string 31 | Args []string 32 | //Env []string 33 | Dir string 34 | 35 | Cfg Config 36 | pod *v1.Pod 37 | 38 | Stdin io.Reader 39 | Stdout io.Writer 40 | Stderr io.Writer 41 | } 42 | 43 | // Command returns the Cmd struct to execute the named program with 44 | // the given arguments. 45 | func Command(cfg Config, name string, arg ...string) *Cmd { 46 | return &Cmd{ 47 | Cfg: cfg, 48 | Path: name, 49 | Args: arg, 50 | } 51 | } 52 | 53 | // Start starts the specified command but does not wait for it to complete. 54 | func (cmd *Cmd) Start() error { 55 | pod, err := createPod(cmd.Cfg, []string{cmd.Path}, cmd.Args) 56 | if err != nil { 57 | return fmt.Errorf("cannot create pod: %v", err) 58 | } 59 | 60 | cmd.pod = pod 61 | 62 | return nil 63 | } 64 | 65 | // Wait waits for the command to exit and waits for any copying to 66 | // stdin or copying from stdout or stderr to complete. 67 | // 68 | // The command must have been started by Start. 69 | func (cmd *Cmd) Wait() error { 70 | if cmd.Stdin == nil { 71 | cmd.Stdin = ioutil.NopCloser(nil) 72 | } 73 | 74 | if cmd.Stdout == nil { 75 | cmd.Stdout = ioutil.Discard 76 | } 77 | 78 | if cmd.Stderr == nil { 79 | cmd.Stderr = ioutil.Discard 80 | } 81 | 82 | // wait for pod to be running 83 | waitPod(cmd.Cfg.Kubeconfig, cmd.pod) 84 | 85 | attachOptions := &v1.PodAttachOptions{ 86 | Stdin: cmd.Stdin != ioutil.NopCloser(nil), 87 | Stdout: cmd.Stdout != ioutil.Discard, 88 | 89 | // For k8s 1.9 - see https://github.com/kubernetes/kubernetes/pull/52686 90 | //Stderr: cmd.Stderr != ioutil.Discard, 91 | 92 | Stderr: true, 93 | TTY: false, 94 | } 95 | 96 | err := attach(cmd.Cfg.Kubeconfig, cmd.pod, attachOptions, cmd.Stdin, cmd.Stdout, cmd.Stderr) 97 | if err != nil { 98 | return fmt.Errorf("cannot attach: %v", err) 99 | } 100 | 101 | return nil 102 | } 103 | 104 | // Run starts the specified command and waits for it to complete. 105 | func (cmd *Cmd) Run() error { 106 | err := cmd.Start() 107 | if err != nil { 108 | return fmt.Errorf("cannot start command: %v", err) 109 | } 110 | 111 | return cmd.Wait() 112 | } 113 | 114 | // StdinPipe returns a pipe that will be connected to the command's standard input 115 | // when the command starts. 116 | // 117 | // Different than os/exec.StdinPipe, returned io.WriteCloser should be closed by user. 118 | func (cmd *Cmd) StdinPipe() (io.WriteCloser, error) { 119 | 120 | pr, pw := io.Pipe() 121 | cmd.Stdin = pr 122 | return pw, nil 123 | } 124 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at root@radu.sh. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /docs/content/examples/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2019-02-18 3 | title: Examples 4 | --- 5 | 6 | 7 | ## The simplest example 8 | 9 | The following example creates a new pod based on the official Ubuntu image, then simply prints a message. 10 | 11 | > Note: Because of a [known temporary limitation][log-issue], if the execution time of the command started inside the pod is too small, the logs will not be visible when executing, but have to be gathered using `kubectl`. Because of this reason, in this example there is a `sleep` command as well. This does **not** affect functionality, the command can be executed without the additional sleep statement, and this will be improved in the future. 12 | 13 | [embedmd]:# (../../examples/hello/main.go go) 14 | ```go 15 | package main 16 | 17 | import ( 18 | "log" 19 | "os" 20 | 21 | kube "github.com/engineerd/kube-exec" 22 | ) 23 | 24 | func main() { 25 | 26 | cfg := kube.Config{ 27 | Kubeconfig: os.Getenv("KUBECONFIG"), 28 | Image: "ubuntu", 29 | Name: "kube-example", 30 | Namespace: "default", 31 | } 32 | 33 | // also sleeping for a couple of seconds 34 | // if the pod completes too fast, we don't have time to attach to it 35 | 36 | cmd := kube.Command(cfg, "/bin/sh", "-c", "sleep 2; echo Running from Kubernetes pod;") 37 | cmd.Stdout = os.Stdout 38 | 39 | err := cmd.Run() 40 | if err != nil { 41 | log.Fatalf("error: %v", err) 42 | } 43 | 44 | } 45 | ``` 46 | 47 | ## Passing `stdin` to the pod 48 | 49 | Because this package follows the implementation of `os/exec`, you can also pass a pipe to the started pod and use it as standard input, as well as standard output, and standard error. 50 | 51 | [embedmd]:# (../../examples/stdin/main.go go /func main/ $) 52 | ```go 53 | func main() { 54 | 55 | cfg := kube.Config{ 56 | Kubeconfig: os.Getenv("KUBECONFIG"), 57 | Image: "ubuntu", 58 | Name: "kube-attach", 59 | Namespace: "default", 60 | } 61 | 62 | cmd := kube.Command(cfg, "/bin/sh", "-c", "while true; read test; do echo You said: $test; sleep .5; done") 63 | 64 | w, err := cmd.StdinPipe() 65 | if err != nil { 66 | log.Fatalf("cannot get pipe to stdin: %v", err) 67 | } 68 | 69 | // write in cmd.Stdin 70 | go func() { 71 | defer w.Close() 72 | _, err = io.Copy(w, os.Stdin) 73 | if err != nil { 74 | log.Fatalf("cannot copy from stdin: %v", err) 75 | } 76 | }() 77 | 78 | cmd.Stdout = os.Stdout 79 | 80 | err = cmd.Run() 81 | if err != nil { 82 | log.Fatalf("error: %v", err) 83 | } 84 | 85 | } 86 | ``` 87 | 88 | ## Passing secrets to the pod 89 | 90 | If you need secret values to use inside your pod, Kubernetes secrets are available as environment variables. 91 | 92 | [embedmd]:# (../../examples/secrets/main.go go /func main/ $) 93 | ```go 94 | func main() { 95 | cfg := kube.Config{ 96 | Kubeconfig: os.Getenv("KUBECONFIG"), 97 | Image: "ubuntu", 98 | Name: "secret", 99 | Namespace: "default", 100 | 101 | Secrets: []kube.Secret{ 102 | { 103 | EnvVarName: "SUPERPRIVATESECRET", 104 | SecretName: "k8s-secret", 105 | SecretKey: "password", 106 | }, 107 | }, 108 | } 109 | 110 | cmd := kube.Command(cfg, "/bin/sh", "-c", "sleep 2; echo Your private secret: $SUPERPRIVATESECRET;") 111 | cmd.Stdout = os.Stdout 112 | 113 | err := cmd.Run() 114 | if err != nil { 115 | log.Fatalf("error: %v", err) 116 | } 117 | 118 | } 119 | ``` 120 | 121 | 122 | [log-issue]: https://github.com/engineerd/kube-exec/issues/6 123 | -------------------------------------------------------------------------------- /kube_util.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/url" 8 | "sync" 9 | "time" 10 | 11 | v1 "k8s.io/api/core/v1" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/fields" 14 | "k8s.io/client-go/kubernetes" 15 | "k8s.io/client-go/kubernetes/scheme" 16 | restclient "k8s.io/client-go/rest" 17 | "k8s.io/client-go/tools/cache" 18 | "k8s.io/client-go/tools/clientcmd" 19 | "k8s.io/client-go/tools/remotecommand" 20 | ) 21 | 22 | // getKubeClient is a convenience method for creating kubernetes config and client 23 | // for a given kubeconfig 24 | func getKubeClient(kubeconfig string) (*kubernetes.Clientset, *restclient.Config, error) { 25 | config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) 26 | if err != nil { 27 | return nil, nil, fmt.Errorf("could not get kubernetes config from kubeconfig '%s': %v", kubeconfig, err) 28 | } 29 | 30 | clientset, err := kubernetes.NewForConfig(config) 31 | if err != nil { 32 | return nil, nil, fmt.Errorf("could not get kubernetes client: %s", err) 33 | } 34 | return clientset, config, nil 35 | } 36 | 37 | // getPod returns a pod, given a namespace and pod name 38 | func getPod(kubeconfig, namespace, name string) (*v1.Pod, error) { 39 | clientset, _, err := getKubeClient(kubeconfig) 40 | if err != nil { 41 | log.Fatalf("cannot get clientset: %v", err) 42 | } 43 | 44 | podsClient := clientset.CoreV1().Pods(namespace) 45 | 46 | return podsClient.Get(name, metav1.GetOptions{}) 47 | } 48 | 49 | // createPod creates a new pod within a namespaces, with specified image and command to run 50 | func createPod(cfg Config, command, args []string) (*v1.Pod, error) { 51 | clientset, _, err := getKubeClient(cfg.Kubeconfig) 52 | if err != nil { 53 | log.Fatalf("cannot get clientset: %v", err) 54 | } 55 | 56 | // convert to Kubernetes API env var from secret 57 | // TODO - make this part generic and add volume mount secret support 58 | env := []v1.EnvVar{} 59 | for _, s := range cfg.Secrets { 60 | env = append(env, v1.EnvVar{ 61 | Name: s.EnvVarName, 62 | ValueFrom: &v1.EnvVarSource{ 63 | SecretKeyRef: &v1.SecretKeySelector{ 64 | LocalObjectReference: v1.LocalObjectReference{ 65 | Name: s.SecretName, 66 | }, 67 | Key: s.SecretKey, 68 | }, 69 | }, 70 | }) 71 | } 72 | 73 | podsClient := clientset.CoreV1().Pods(cfg.Namespace) 74 | return podsClient.Create(&v1.Pod{ 75 | 76 | ObjectMeta: metav1.ObjectMeta{ 77 | Name: cfg.Name, 78 | }, 79 | Spec: v1.PodSpec{ 80 | Containers: []v1.Container{ 81 | { 82 | TTY: false, 83 | Stdin: true, 84 | 85 | Name: cfg.Name, 86 | Image: cfg.Image, 87 | Command: command, 88 | Args: args, 89 | SecurityContext: &v1.SecurityContext{ 90 | Privileged: boolPtr(false), 91 | }, 92 | ImagePullPolicy: v1.PullPolicy(v1.PullAlways), 93 | Env: env, 94 | VolumeMounts: []v1.VolumeMount{}, 95 | }, 96 | }, 97 | RestartPolicy: v1.RestartPolicyOnFailure, 98 | Volumes: []v1.Volume{}, 99 | ImagePullSecrets: []v1.LocalObjectReference{}, 100 | }, 101 | }) 102 | } 103 | 104 | // containerToAttach returns a reference to the container to attach to, given 105 | // by name or the first container if name is empty. 106 | func containerToAttachTo(container string, pod *v1.Pod) (*v1.Container, error) { 107 | if len(container) > 0 { 108 | for i := range pod.Spec.Containers { 109 | if pod.Spec.Containers[i].Name == container { 110 | return &pod.Spec.Containers[i], nil 111 | } 112 | } 113 | for i := range pod.Spec.InitContainers { 114 | if pod.Spec.InitContainers[i].Name == container { 115 | return &pod.Spec.InitContainers[i], nil 116 | } 117 | } 118 | return nil, fmt.Errorf("container not found (%s)", container) 119 | } 120 | return &pod.Spec.Containers[0], nil 121 | } 122 | 123 | // attach attaches to a given pod, outputting to stdout and stderr 124 | func attach(kubeconfig string, pod *v1.Pod, attachOptions *v1.PodAttachOptions, stdin io.Reader, stdout, stderr io.Writer) error { 125 | clientset, config, err := getKubeClient(kubeconfig) 126 | if err != nil { 127 | log.Fatalf("cannot get clientset: %v", err) 128 | } 129 | 130 | container, err := containerToAttachTo("", pod) 131 | if err != nil { 132 | return fmt.Errorf("cannot get container to attach to: %v", err) 133 | } 134 | 135 | req := clientset.CoreV1().RESTClient().Post(). 136 | Resource("pods"). 137 | Name(pod.Name). 138 | Namespace(pod.Namespace). 139 | SubResource("attach") 140 | 141 | attachOptions.Container = container.Name 142 | req.VersionedParams(attachOptions, scheme.ParameterCodec) 143 | 144 | streamOptions := getStreamOptions(attachOptions, stdin, stdout, stderr) 145 | 146 | err = startStream("POST", req.URL(), config, streamOptions) 147 | if err != nil { 148 | return fmt.Errorf("error executing: %v", err) 149 | } 150 | 151 | return nil 152 | } 153 | 154 | func startStream(method string, url *url.URL, config *restclient.Config, streamOptions remotecommand.StreamOptions) error { 155 | exec, err := remotecommand.NewSPDYExecutor(config, method, url) 156 | if err != nil { 157 | return err 158 | } 159 | 160 | return exec.Stream(streamOptions) 161 | } 162 | 163 | // waitPod waits until the created pod is in running state 164 | func waitPod(kubeconfig string, pod *v1.Pod) { 165 | clientset, _, err := getKubeClient(kubeconfig) 166 | if err != nil { 167 | log.Fatalf("cannot get clientset: %v", err) 168 | } 169 | 170 | stop := newStopChan() 171 | 172 | watchlist := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", pod.Namespace, fields.Everything()) 173 | _, controller := cache.NewInformer(watchlist, &v1.Pod{}, time.Second*1, cache.ResourceEventHandlerFuncs{ 174 | UpdateFunc: func(o, n interface{}) { 175 | newPod := n.(*v1.Pod) 176 | 177 | // not the pod we created 178 | if newPod.Name != pod.Name { 179 | return 180 | } 181 | 182 | // if the pod is running, stop watching and continue with the cmd execution 183 | if newPod.Status.Phase == v1.PodRunning { 184 | stop.closeOnce() 185 | return 186 | } 187 | }, 188 | }) 189 | 190 | controller.Run(stop.c) 191 | } 192 | 193 | func getStreamOptions(attachOptions *v1.PodAttachOptions, stdin io.Reader, stdout, stderr io.Writer) remotecommand.StreamOptions { 194 | var streamOptions remotecommand.StreamOptions 195 | if attachOptions.Stdin { 196 | streamOptions.Stdin = stdin 197 | } 198 | 199 | if attachOptions.Stdout { 200 | streamOptions.Stdout = stdout 201 | } 202 | 203 | if attachOptions.Stderr { 204 | streamOptions.Stderr = stderr 205 | } 206 | 207 | return streamOptions 208 | } 209 | 210 | type stopChan struct { 211 | c chan struct{} 212 | sync.Once 213 | } 214 | 215 | func newStopChan() *stopChan { 216 | return &stopChan{c: make(chan struct{})} 217 | } 218 | 219 | func (s *stopChan) closeOnce() { 220 | s.Do(func() { 221 | close(s.c) 222 | }) 223 | } 224 | 225 | // boolPtr returns a pointer to the passed bool. 226 | func boolPtr(b bool) *bool { 227 | return &b 228 | } 229 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" 6 | name = "github.com/davecgh/go-spew" 7 | packages = ["spew"] 8 | pruneopts = "" 9 | revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" 10 | version = "v1.1.1" 11 | 12 | [[projects]] 13 | branch = "master" 14 | digest = "1:d6c13a378213e3de60445e49084b8a0a9ce582776dfc77927775dbeb3ff72a35" 15 | name = "github.com/docker/spdystream" 16 | packages = [ 17 | ".", 18 | "spdy", 19 | ] 20 | pruneopts = "" 21 | revision = "6480d4af844c189cf5dd913db24ddd339d3a4f85" 22 | 23 | [[projects]] 24 | digest = "1:527e1e468c5586ef2645d143e9f5fbd50b4fe5abc8b1e25d9f1c416d22d24895" 25 | name = "github.com/gogo/protobuf" 26 | packages = [ 27 | "proto", 28 | "sortkeys", 29 | ] 30 | pruneopts = "" 31 | revision = "4cbf7e384e768b4e01799441fdf2a706a5635ae7" 32 | version = "v1.2.0" 33 | 34 | [[projects]] 35 | digest = "1:3dd078fda7500c341bc26cfbc6c6a34614f295a2457149fc1045cab767cbcf18" 36 | name = "github.com/golang/protobuf" 37 | packages = [ 38 | "proto", 39 | "ptypes", 40 | "ptypes/any", 41 | "ptypes/duration", 42 | "ptypes/timestamp", 43 | ] 44 | pruneopts = "" 45 | revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" 46 | version = "v1.2.0" 47 | 48 | [[projects]] 49 | branch = "master" 50 | digest = "1:1e5b1e14524ed08301977b7b8e10c719ed853cbf3f24ecb66fae783a46f207a6" 51 | name = "github.com/google/btree" 52 | packages = ["."] 53 | pruneopts = "" 54 | revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306" 55 | 56 | [[projects]] 57 | branch = "master" 58 | digest = "1:754f77e9c839b24778a4b64422236d38515301d2baeb63113aa3edc42e6af692" 59 | name = "github.com/google/gofuzz" 60 | packages = ["."] 61 | pruneopts = "" 62 | revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1" 63 | 64 | [[projects]] 65 | digest = "1:16b2837c8b3cf045fa2cdc82af0cf78b19582701394484ae76b2c3bc3c99ad73" 66 | name = "github.com/googleapis/gnostic" 67 | packages = [ 68 | "OpenAPIv2", 69 | "compiler", 70 | "extensions", 71 | ] 72 | pruneopts = "" 73 | revision = "7c663266750e7d82587642f65e60bc4083f1f84e" 74 | version = "v0.2.0" 75 | 76 | [[projects]] 77 | branch = "master" 78 | digest = "1:5e345eb75d8bfb2b91cfbfe02a82a79c0b2ea55cf06c5a4d180a9321f36973b4" 79 | name = "github.com/gregjones/httpcache" 80 | packages = [ 81 | ".", 82 | "diskcache", 83 | ] 84 | pruneopts = "" 85 | revision = "c63ab54fda8f77302f8d414e19933f2b6026a089" 86 | 87 | [[projects]] 88 | digest = "1:3313a63031ae281e5f6fd7b0bbca733dfa04d2429df86519e3b4d4c016ccb836" 89 | name = "github.com/hashicorp/golang-lru" 90 | packages = [ 91 | ".", 92 | "simplelru", 93 | ] 94 | pruneopts = "" 95 | revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768" 96 | version = "v0.5.0" 97 | 98 | [[projects]] 99 | digest = "1:7ab38c15bd21e056e3115c8b526d201eaf74e0308da9370997c6b3c187115d36" 100 | name = "github.com/imdario/mergo" 101 | packages = ["."] 102 | pruneopts = "" 103 | revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4" 104 | version = "v0.3.6" 105 | 106 | [[projects]] 107 | digest = "1:b79fc583e4dc7055ed86742e22164ac41bf8c0940722dbcb600f1a3ace1a8cb5" 108 | name = "github.com/json-iterator/go" 109 | packages = ["."] 110 | pruneopts = "" 111 | revision = "1624edc4454b8682399def8740d46db5e4362ba4" 112 | version = "v1.1.5" 113 | 114 | [[projects]] 115 | digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd" 116 | name = "github.com/modern-go/concurrent" 117 | packages = ["."] 118 | pruneopts = "" 119 | revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" 120 | version = "1.0.3" 121 | 122 | [[projects]] 123 | digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855" 124 | name = "github.com/modern-go/reflect2" 125 | packages = ["."] 126 | pruneopts = "" 127 | revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" 128 | version = "1.0.1" 129 | 130 | [[projects]] 131 | branch = "master" 132 | digest = "1:c24598ffeadd2762552269271b3b1510df2d83ee6696c1e543a0ff653af494bc" 133 | name = "github.com/petar/GoLLRB" 134 | packages = ["llrb"] 135 | pruneopts = "" 136 | revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4" 137 | 138 | [[projects]] 139 | digest = "1:b46305723171710475f2dd37547edd57b67b9de9f2a6267cafdd98331fd6897f" 140 | name = "github.com/peterbourgon/diskv" 141 | packages = ["."] 142 | pruneopts = "" 143 | revision = "5f041e8faa004a95c88a202771f4cc3e991971e6" 144 | version = "v2.0.1" 145 | 146 | [[projects]] 147 | digest = "1:cbaf13cdbfef0e4734ed8a7504f57fe893d471d62a35b982bf6fb3f036449a66" 148 | name = "github.com/spf13/pflag" 149 | packages = ["."] 150 | pruneopts = "" 151 | revision = "298182f68c66c05229eb03ac171abe6e309ee79a" 152 | version = "v1.0.3" 153 | 154 | [[projects]] 155 | branch = "master" 156 | digest = "1:887074c37fcefc2f49b5ae9c6f9f36107341aec23185613d0e9f1ee81db7f94a" 157 | name = "golang.org/x/crypto" 158 | packages = ["ssh/terminal"] 159 | pruneopts = "" 160 | revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447" 161 | 162 | [[projects]] 163 | branch = "master" 164 | digest = "1:3b4532388bb9dfcc98530527160974cf5ec03120002887777516a98092725ba0" 165 | name = "golang.org/x/net" 166 | packages = [ 167 | "context", 168 | "context/ctxhttp", 169 | "http/httpguts", 170 | "http2", 171 | "http2/hpack", 172 | "idna", 173 | ] 174 | pruneopts = "" 175 | revision = "e147a9138326bc0e9d4e179541ffd8af41cff8a9" 176 | 177 | [[projects]] 178 | branch = "master" 179 | digest = "1:ea010cdb976f9de0c763728a76278f9109fca3299abd0dc3e8f2ccb9ff347268" 180 | name = "golang.org/x/oauth2" 181 | packages = [ 182 | ".", 183 | "internal", 184 | ] 185 | pruneopts = "" 186 | revision = "d668ce993890a79bda886613ee587a69dd5da7a6" 187 | 188 | [[projects]] 189 | branch = "master" 190 | digest = "1:c2c4c39f961dde3f317a48713db65329863b61316edabf19b96784b2da8406ac" 191 | name = "golang.org/x/sys" 192 | packages = [ 193 | "unix", 194 | "windows", 195 | ] 196 | pruneopts = "" 197 | revision = "4d1cda033e0619309c606fc686de3adcf599539e" 198 | 199 | [[projects]] 200 | digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4" 201 | name = "golang.org/x/text" 202 | packages = [ 203 | "collate", 204 | "collate/build", 205 | "internal/colltab", 206 | "internal/gen", 207 | "internal/tag", 208 | "internal/triegen", 209 | "internal/ucd", 210 | "language", 211 | "secure/bidirule", 212 | "transform", 213 | "unicode/bidi", 214 | "unicode/cldr", 215 | "unicode/norm", 216 | "unicode/rangetable", 217 | ] 218 | pruneopts = "" 219 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" 220 | version = "v0.3.0" 221 | 222 | [[projects]] 223 | branch = "master" 224 | digest = "1:14cb1d4240bcbbf1386ae763957e04e2765ec4e4ce7bb2769d05fa6faccd774e" 225 | name = "golang.org/x/time" 226 | packages = ["rate"] 227 | pruneopts = "" 228 | revision = "85acf8d2951cb2a3bde7632f9ff273ef0379bcbd" 229 | 230 | [[projects]] 231 | digest = "1:77d3cff3a451d50be4b52db9c7766c0d8570ba47593f0c9dc72173adb208e788" 232 | name = "google.golang.org/appengine" 233 | packages = [ 234 | "internal", 235 | "internal/base", 236 | "internal/datastore", 237 | "internal/log", 238 | "internal/remote_api", 239 | "internal/urlfetch", 240 | "urlfetch", 241 | ] 242 | pruneopts = "" 243 | revision = "4a4468ece617fc8205e99368fa2200e9d1fad421" 244 | version = "v1.3.0" 245 | 246 | [[projects]] 247 | digest = "1:75fb3fcfc73a8c723efde7777b40e8e8ff9babf30d8c56160d01beffea8a95a6" 248 | name = "gopkg.in/inf.v0" 249 | packages = ["."] 250 | pruneopts = "" 251 | revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf" 252 | version = "v0.9.1" 253 | 254 | [[projects]] 255 | digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d" 256 | name = "gopkg.in/yaml.v2" 257 | packages = ["."] 258 | pruneopts = "" 259 | revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" 260 | version = "v2.2.2" 261 | 262 | [[projects]] 263 | branch = "master" 264 | digest = "1:4c9196e95e80a535cac4e8b5ddede652857b5c6c17070c8a484c553d2fc4e021" 265 | name = "k8s.io/api" 266 | packages = [ 267 | "admissionregistration/v1alpha1", 268 | "admissionregistration/v1beta1", 269 | "apps/v1", 270 | "apps/v1beta1", 271 | "apps/v1beta2", 272 | "auditregistration/v1alpha1", 273 | "authentication/v1", 274 | "authentication/v1beta1", 275 | "authorization/v1", 276 | "authorization/v1beta1", 277 | "autoscaling/v1", 278 | "autoscaling/v2beta1", 279 | "autoscaling/v2beta2", 280 | "batch/v1", 281 | "batch/v1beta1", 282 | "batch/v2alpha1", 283 | "certificates/v1beta1", 284 | "coordination/v1beta1", 285 | "core/v1", 286 | "events/v1beta1", 287 | "extensions/v1beta1", 288 | "networking/v1", 289 | "policy/v1beta1", 290 | "rbac/v1", 291 | "rbac/v1alpha1", 292 | "rbac/v1beta1", 293 | "scheduling/v1alpha1", 294 | "scheduling/v1beta1", 295 | "settings/v1alpha1", 296 | "storage/v1", 297 | "storage/v1alpha1", 298 | "storage/v1beta1", 299 | ] 300 | pruneopts = "" 301 | revision = "d04500c8c3dda9c980b668c57abc2ca61efcf5c4" 302 | 303 | [[projects]] 304 | branch = "master" 305 | digest = "1:c104c9d2d26b85af4923c7c7dd85ed5d877087c3e5e260f73c709f67b64a353e" 306 | name = "k8s.io/apimachinery" 307 | packages = [ 308 | "pkg/api/errors", 309 | "pkg/api/meta", 310 | "pkg/api/resource", 311 | "pkg/apis/meta/internalversion", 312 | "pkg/apis/meta/v1", 313 | "pkg/apis/meta/v1/unstructured", 314 | "pkg/apis/meta/v1beta1", 315 | "pkg/conversion", 316 | "pkg/conversion/queryparams", 317 | "pkg/fields", 318 | "pkg/labels", 319 | "pkg/runtime", 320 | "pkg/runtime/schema", 321 | "pkg/runtime/serializer", 322 | "pkg/runtime/serializer/json", 323 | "pkg/runtime/serializer/protobuf", 324 | "pkg/runtime/serializer/recognizer", 325 | "pkg/runtime/serializer/streaming", 326 | "pkg/runtime/serializer/versioning", 327 | "pkg/selection", 328 | "pkg/types", 329 | "pkg/util/cache", 330 | "pkg/util/clock", 331 | "pkg/util/diff", 332 | "pkg/util/errors", 333 | "pkg/util/framer", 334 | "pkg/util/httpstream", 335 | "pkg/util/httpstream/spdy", 336 | "pkg/util/intstr", 337 | "pkg/util/json", 338 | "pkg/util/naming", 339 | "pkg/util/net", 340 | "pkg/util/remotecommand", 341 | "pkg/util/runtime", 342 | "pkg/util/sets", 343 | "pkg/util/validation", 344 | "pkg/util/validation/field", 345 | "pkg/util/wait", 346 | "pkg/util/yaml", 347 | "pkg/version", 348 | "pkg/watch", 349 | "third_party/forked/golang/netutil", 350 | "third_party/forked/golang/reflect", 351 | ] 352 | pruneopts = "" 353 | revision = "4d029f0333996cf231080e108e0bd1ece2a94d9f" 354 | 355 | [[projects]] 356 | digest = "1:96ab89894f66b77a0137bba12e607c6a9be992acadfb075b5602939e8519a157" 357 | name = "k8s.io/client-go" 358 | packages = [ 359 | "discovery", 360 | "kubernetes", 361 | "kubernetes/scheme", 362 | "kubernetes/typed/admissionregistration/v1alpha1", 363 | "kubernetes/typed/admissionregistration/v1beta1", 364 | "kubernetes/typed/apps/v1", 365 | "kubernetes/typed/apps/v1beta1", 366 | "kubernetes/typed/apps/v1beta2", 367 | "kubernetes/typed/auditregistration/v1alpha1", 368 | "kubernetes/typed/authentication/v1", 369 | "kubernetes/typed/authentication/v1beta1", 370 | "kubernetes/typed/authorization/v1", 371 | "kubernetes/typed/authorization/v1beta1", 372 | "kubernetes/typed/autoscaling/v1", 373 | "kubernetes/typed/autoscaling/v2beta1", 374 | "kubernetes/typed/autoscaling/v2beta2", 375 | "kubernetes/typed/batch/v1", 376 | "kubernetes/typed/batch/v1beta1", 377 | "kubernetes/typed/batch/v2alpha1", 378 | "kubernetes/typed/certificates/v1beta1", 379 | "kubernetes/typed/coordination/v1beta1", 380 | "kubernetes/typed/core/v1", 381 | "kubernetes/typed/events/v1beta1", 382 | "kubernetes/typed/extensions/v1beta1", 383 | "kubernetes/typed/networking/v1", 384 | "kubernetes/typed/policy/v1beta1", 385 | "kubernetes/typed/rbac/v1", 386 | "kubernetes/typed/rbac/v1alpha1", 387 | "kubernetes/typed/rbac/v1beta1", 388 | "kubernetes/typed/scheduling/v1alpha1", 389 | "kubernetes/typed/scheduling/v1beta1", 390 | "kubernetes/typed/settings/v1alpha1", 391 | "kubernetes/typed/storage/v1", 392 | "kubernetes/typed/storage/v1alpha1", 393 | "kubernetes/typed/storage/v1beta1", 394 | "pkg/apis/clientauthentication", 395 | "pkg/apis/clientauthentication/v1alpha1", 396 | "pkg/apis/clientauthentication/v1beta1", 397 | "pkg/version", 398 | "plugin/pkg/client/auth/exec", 399 | "rest", 400 | "rest/watch", 401 | "tools/auth", 402 | "tools/cache", 403 | "tools/clientcmd", 404 | "tools/clientcmd/api", 405 | "tools/clientcmd/api/latest", 406 | "tools/clientcmd/api/v1", 407 | "tools/metrics", 408 | "tools/pager", 409 | "tools/reference", 410 | "tools/remotecommand", 411 | "transport", 412 | "transport/spdy", 413 | "util/buffer", 414 | "util/cert", 415 | "util/connrotation", 416 | "util/exec", 417 | "util/flowcontrol", 418 | "util/homedir", 419 | "util/integer", 420 | "util/retry", 421 | ] 422 | pruneopts = "" 423 | revision = "e64494209f554a6723674bd494d69445fb76a1d4" 424 | version = "v10.0.0" 425 | 426 | [[projects]] 427 | digest = "1:4f5eb833037cc0ba0bf8fe9cae6be9df62c19dd1c869415275c708daa8ccfda5" 428 | name = "k8s.io/klog" 429 | packages = ["."] 430 | pruneopts = "" 431 | revision = "a5bc97fbc634d635061f3146511332c7e313a55a" 432 | version = "v0.1.0" 433 | 434 | [[projects]] 435 | digest = "1:321081b4a44256715f2b68411d8eda9a17f17ebfe6f0cc61d2cc52d11c08acfa" 436 | name = "sigs.k8s.io/yaml" 437 | packages = ["."] 438 | pruneopts = "" 439 | revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" 440 | version = "v1.1.0" 441 | 442 | [solve-meta] 443 | analyzer-name = "dep" 444 | analyzer-version = 1 445 | input-imports = [ 446 | "k8s.io/api/core/v1", 447 | "k8s.io/apimachinery/pkg/apis/meta/v1", 448 | "k8s.io/apimachinery/pkg/fields", 449 | "k8s.io/client-go/kubernetes", 450 | "k8s.io/client-go/kubernetes/scheme", 451 | "k8s.io/client-go/rest", 452 | "k8s.io/client-go/tools/cache", 453 | "k8s.io/client-go/tools/clientcmd", 454 | "k8s.io/client-go/tools/remotecommand", 455 | ] 456 | solver-name = "gps-cdcl" 457 | solver-version = 1 458 | --------------------------------------------------------------------------------