Skip to content
This repository was archived by the owner on Mar 16, 2024. It is now read-only.

Commit 95a93bc

Browse files
Merge pull request #1958 from ibuildthecloud/kube
Add hidden kube command
2 parents 8361996 + 6a839cb commit 95a93bc

File tree

10 files changed

+241
-0
lines changed

10 files changed

+241
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ require (
206206
github.com/morikuni/aec v1.0.0 // indirect
207207
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
208208
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
209+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
209210
github.com/nightlyone/lockfile v1.0.0 // indirect
210211
github.com/oklog/ulid v1.3.1 // indirect
211212
github.com/olekukonko/tablewriter v0.0.5 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,8 @@ github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9
683683
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
684684
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
685685
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
686+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
687+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
686688
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
687689
github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA=
688690
github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI=

pkg/cli/acorn.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func New() *cobra.Command {
7070
NewVolume(cmdContext),
7171
NewWait(cmdContext),
7272
NewVersion(cmdContext),
73+
NewKubectl(cmdContext),
7374
)
7475
// This will produce an error if the project flag doesn't exist or a completion function has already
7576
// been registered for this flag. Not returning the error since neither of these is likely occur.

pkg/cli/kube.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
9+
cli "github.com/acorn-io/runtime/pkg/cli/builder"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func NewKubectl(c CommandContext) *cobra.Command {
14+
cmd := cli.Command(&Kube{client: c.ClientFactory}, cobra.Command{
15+
Use: "kube [flags]",
16+
Args: cobra.MinimumNArgs(1),
17+
Hidden: true,
18+
SilenceUsage: true,
19+
Short: "Run command with KUBECONFIG env set to a generated kubeconfig of the current project",
20+
Example: `
21+
acorn -j acorn kube k9s
22+
`})
23+
cmd.Flags().SetInterspersed(false)
24+
return cmd
25+
}
26+
27+
type Kube struct {
28+
client ClientFactory
29+
}
30+
31+
func (s *Kube) Run(cmd *cobra.Command, args []string) error {
32+
c, err := s.client.CreateDefault()
33+
if err != nil {
34+
return err
35+
}
36+
37+
ctx, cancel := context.WithCancel(cmd.Context())
38+
defer cancel()
39+
40+
server, err := c.KubeProxyAddress(ctx)
41+
if err != nil {
42+
return err
43+
}
44+
45+
f, err := os.CreateTemp("", "acorn-kube")
46+
if err != nil {
47+
return err
48+
}
49+
defer func() {
50+
_ = os.Remove(f.Name())
51+
}()
52+
53+
_, err = f.Write([]byte(fmt.Sprintf(`
54+
apiVersion: v1
55+
clusters:
56+
- cluster:
57+
server: "%s"
58+
name: default
59+
contexts:
60+
- context:
61+
cluster: default
62+
user: default
63+
name: default
64+
current-context: default
65+
kind: Config
66+
preferences: {}
67+
users:
68+
- name: default
69+
`, server)))
70+
if err != nil {
71+
return err
72+
}
73+
if err := f.Close(); err != nil {
74+
return err
75+
}
76+
77+
k := exec.Command(args[0], args[1:]...)
78+
k.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", f.Name()))
79+
k.Stdin = os.Stdin
80+
k.Stdout = os.Stdout
81+
k.Stderr = os.Stderr
82+
return k.Run()
83+
}

pkg/cli/testdata/MockClient.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ type MockClient struct {
119119
EventItem *apiv1.Event
120120
}
121121

122+
func (m *MockClient) KubeProxyAddress(ctx context.Context) (string, error) {
123+
//TODO implement me
124+
panic("implement me")
125+
}
126+
122127
func (m *MockClient) DevSessionRenew(ctx context.Context, name string, client v1.DevSessionInstanceClient) error {
123128
//TODO implement me
124129
panic("implement me")

pkg/client/client.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package client
33
import (
44
"context"
55
"net"
6+
"net/http"
67
"os"
78
"strconv"
89

@@ -12,6 +13,7 @@ import (
1213
"github.com/acorn-io/runtime/pkg/client/term"
1314
"github.com/acorn-io/runtime/pkg/k8schannel"
1415
"github.com/acorn-io/runtime/pkg/k8sclient"
16+
"github.com/acorn-io/runtime/pkg/proxy"
1517
"github.com/acorn-io/runtime/pkg/scheme"
1618
"github.com/acorn-io/runtime/pkg/streams"
1719
"github.com/acorn-io/runtime/pkg/system"
@@ -267,6 +269,7 @@ type Client interface {
267269
GetProject() string
268270
GetNamespace() string
269271
GetClient() (kclient.WithWatch, error)
272+
KubeProxyAddress(ctx context.Context) (string, error)
270273
}
271274

272275
type CredentialLookup func(ctx context.Context, serverAddress string) (*apiv1.RegistryAuth, bool, error)
@@ -376,6 +379,30 @@ type DefaultClient struct {
376379
Dialer *k8schannel.Dialer
377380
}
378381

382+
func (c *DefaultClient) KubeProxyAddress(ctx context.Context) (string, error) {
383+
handler, err := proxy.Handler(c.RESTConfig)
384+
if err != nil {
385+
return "", err
386+
}
387+
388+
ln, err := net.Listen("tcp", "127.0.0.1:")
389+
if err != nil {
390+
return "", err
391+
}
392+
393+
srv := &http.Server{
394+
Handler: handler,
395+
BaseContext: func(listener net.Listener) context.Context {
396+
return ctx
397+
},
398+
}
399+
go func() {
400+
_ = srv.Serve(ln)
401+
os.Exit(1)
402+
}()
403+
return "http://" + ln.Addr().String(), nil
404+
}
405+
379406
func (c *DefaultClient) GetProject() string {
380407
return c.Project
381408
}

pkg/client/deferred.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,10 @@ func (d *DeferredClient) GetClient() (client.WithWatch, error) {
444444
}
445445
return d.Client.GetClient()
446446
}
447+
448+
func (d *DeferredClient) KubeProxyAddress(ctx context.Context) (string, error) {
449+
if err := d.create(); err != nil {
450+
return "", err
451+
}
452+
return d.Client.KubeProxyAddress(ctx)
453+
}

pkg/client/multi.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,11 @@ func (m *MultiClient) GetClient() (kclient.WithWatch, error) {
639639
}
640640
return c.GetClient()
641641
}
642+
643+
func (m *MultiClient) KubeProxyAddress(ctx context.Context) (string, error) {
644+
c, err := m.Factory.ForProject(context.Background(), m.Factory.DefaultProject())
645+
if err != nil {
646+
panic(err)
647+
}
648+
return c.KubeProxyAddress(ctx)
649+
}

pkg/mocks/mock_client.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/proxy/proxy.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package proxy
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
"strings"
7+
8+
"k8s.io/apimachinery/pkg/util/proxy"
9+
"k8s.io/client-go/rest"
10+
"k8s.io/client-go/transport"
11+
)
12+
13+
var (
14+
er = &errorResponder{}
15+
)
16+
17+
type errorResponder struct {
18+
}
19+
20+
func (e *errorResponder) Error(w http.ResponseWriter, req *http.Request, err error) {
21+
w.WriteHeader(http.StatusInternalServerError)
22+
_, _ = w.Write([]byte(err.Error()))
23+
}
24+
25+
// Mostly copied from "kubectl proxy" code
26+
func Handler(cfg *rest.Config) (http.Handler, error) {
27+
host := cfg.Host
28+
if !strings.HasSuffix(host, "/") {
29+
host = host + "/"
30+
}
31+
target, err := url.Parse(host)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
transport, err := rest.TransportFor(cfg)
37+
if err != nil {
38+
return nil, err
39+
}
40+
upgradeTransport, err := makeUpgradeTransport(cfg, transport)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
proxy := proxy.NewUpgradeAwareHandler(target, transport, false, false, er)
46+
proxy.UpgradeTransport = upgradeTransport
47+
proxy.UseRequestLocation = true
48+
proxy.UseLocationHost = true
49+
50+
handler := http.Handler(proxy)
51+
52+
if len(target.Path) > 1 {
53+
handler = prependPath(target.Path[:len(target.Path)-1], handler)
54+
}
55+
56+
return proxyHeaders(handler), nil
57+
}
58+
59+
func proxyHeaders(handler http.Handler) http.Handler {
60+
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
61+
req.Header.Del("Authorization")
62+
if req.Header.Get("X-Forwarded-Proto") == "" && req.TLS != nil {
63+
req.Header.Set("X-Forwarded-Proto", "https")
64+
}
65+
handler.ServeHTTP(rw, req)
66+
})
67+
}
68+
69+
func prependPath(prefix string, h http.Handler) http.Handler {
70+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
71+
if len(req.URL.Path) > 1 {
72+
req.URL.Path = prefix + req.URL.Path
73+
} else {
74+
req.URL.Path = prefix
75+
}
76+
h.ServeHTTP(w, req)
77+
})
78+
}
79+
80+
func makeUpgradeTransport(config *rest.Config, rt http.RoundTripper) (proxy.UpgradeRequestRoundTripper, error) {
81+
transportConfig, err := config.TransportConfig()
82+
if err != nil {
83+
return nil, err
84+
}
85+
86+
upgrader, err := transport.HTTPWrappersForConfig(transportConfig, proxy.MirrorRequest)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
return proxy.NewUpgradeRequestRoundTripper(rt, upgrader), nil
92+
}

0 commit comments

Comments
 (0)