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

Commit

Permalink
Add hidden kube command
Browse files Browse the repository at this point in the history
Signed-off-by: Darren Shepherd <[email protected]>
  • Loading branch information
ibuildthecloud committed Jul 21, 2023
1 parent e68fb04 commit 6a839cb
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ require (
github.com/morikuni/aec v1.0.0 // indirect
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/nightlyone/lockfile v1.0.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,8 @@ github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA=
github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI=
Expand Down
1 change: 1 addition & 0 deletions pkg/cli/acorn.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func New() *cobra.Command {
NewVolume(cmdContext),
NewWait(cmdContext),
NewVersion(cmdContext),
NewKubectl(cmdContext),
)
// This will produce an error if the project flag doesn't exist or a completion function has already
// been registered for this flag. Not returning the error since neither of these is likely occur.
Expand Down
83 changes: 83 additions & 0 deletions pkg/cli/kube.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cli

import (
"context"
"fmt"
"os"
"os/exec"

cli "github.com/acorn-io/runtime/pkg/cli/builder"
"github.com/spf13/cobra"
)

func NewKubectl(c CommandContext) *cobra.Command {
cmd := cli.Command(&Kube{client: c.ClientFactory}, cobra.Command{
Use: "kube [flags]",
Args: cobra.MinimumNArgs(1),
Hidden: true,
SilenceUsage: true,
Short: "Run command with KUBECONFIG env set to a generated kubeconfig of the current project",
Example: `
acorn -j acorn kube k9s
`})
cmd.Flags().SetInterspersed(false)
return cmd
}

type Kube struct {
client ClientFactory
}

func (s *Kube) Run(cmd *cobra.Command, args []string) error {
c, err := s.client.CreateDefault()
if err != nil {
return err
}

ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()

server, err := c.KubeProxyAddress(ctx)
if err != nil {
return err
}

f, err := os.CreateTemp("", "acorn-kube")
if err != nil {
return err
}
defer func() {
_ = os.Remove(f.Name())
}()

_, err = f.Write([]byte(fmt.Sprintf(`
apiVersion: v1
clusters:
- cluster:
server: "%s"
name: default
contexts:
- context:
cluster: default
user: default
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
`, server)))
if err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}

k := exec.Command(args[0], args[1:]...)
k.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", f.Name()))
k.Stdin = os.Stdin
k.Stdout = os.Stdout
k.Stderr = os.Stderr
return k.Run()
}
5 changes: 5 additions & 0 deletions pkg/cli/testdata/MockClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ type MockClient struct {
EventItem *apiv1.Event
}

func (m *MockClient) KubeProxyAddress(ctx context.Context) (string, error) {
//TODO implement me
panic("implement me")
}

func (m *MockClient) DevSessionRenew(ctx context.Context, name string, client v1.DevSessionInstanceClient) error {
//TODO implement me
panic("implement me")
Expand Down
27 changes: 27 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package client
import (
"context"
"net"
"net/http"
"os"
"strconv"

Expand All @@ -12,6 +13,7 @@ import (
"github.com/acorn-io/runtime/pkg/client/term"
"github.com/acorn-io/runtime/pkg/k8schannel"
"github.com/acorn-io/runtime/pkg/k8sclient"
"github.com/acorn-io/runtime/pkg/proxy"
"github.com/acorn-io/runtime/pkg/scheme"
"github.com/acorn-io/runtime/pkg/streams"
"github.com/acorn-io/runtime/pkg/system"
Expand Down Expand Up @@ -267,6 +269,7 @@ type Client interface {
GetProject() string
GetNamespace() string
GetClient() (kclient.WithWatch, error)
KubeProxyAddress(ctx context.Context) (string, error)
}

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

func (c *DefaultClient) KubeProxyAddress(ctx context.Context) (string, error) {
handler, err := proxy.Handler(c.RESTConfig)
if err != nil {
return "", err
}

ln, err := net.Listen("tcp", "127.0.0.1:")
if err != nil {
return "", err
}

srv := &http.Server{
Handler: handler,
BaseContext: func(listener net.Listener) context.Context {
return ctx
},
}
go func() {
_ = srv.Serve(ln)
os.Exit(1)
}()
return "http://" + ln.Addr().String(), nil
}

func (c *DefaultClient) GetProject() string {
return c.Project
}
Expand Down
7 changes: 7 additions & 0 deletions pkg/client/deferred.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,3 +444,10 @@ func (d *DeferredClient) GetClient() (client.WithWatch, error) {
}
return d.Client.GetClient()
}

func (d *DeferredClient) KubeProxyAddress(ctx context.Context) (string, error) {
if err := d.create(); err != nil {
return "", err
}
return d.Client.KubeProxyAddress(ctx)
}
8 changes: 8 additions & 0 deletions pkg/client/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,3 +639,11 @@ func (m *MultiClient) GetClient() (kclient.WithWatch, error) {
}
return c.GetClient()
}

func (m *MultiClient) KubeProxyAddress(ctx context.Context) (string, error) {
c, err := m.Factory.ForProject(context.Background(), m.Factory.DefaultProject())
if err != nil {
panic(err)
}
return c.KubeProxyAddress(ctx)
}
15 changes: 15 additions & 0 deletions pkg/mocks/mock_client.go

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

92 changes: 92 additions & 0 deletions pkg/proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package proxy

import (
"net/http"
"net/url"
"strings"

"k8s.io/apimachinery/pkg/util/proxy"
"k8s.io/client-go/rest"
"k8s.io/client-go/transport"
)

var (
er = &errorResponder{}
)

type errorResponder struct {
}

func (e *errorResponder) Error(w http.ResponseWriter, req *http.Request, err error) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
}

// Mostly copied from "kubectl proxy" code
func Handler(cfg *rest.Config) (http.Handler, error) {
host := cfg.Host
if !strings.HasSuffix(host, "/") {
host = host + "/"
}
target, err := url.Parse(host)
if err != nil {
return nil, err
}

transport, err := rest.TransportFor(cfg)
if err != nil {
return nil, err
}
upgradeTransport, err := makeUpgradeTransport(cfg, transport)
if err != nil {
return nil, err
}

proxy := proxy.NewUpgradeAwareHandler(target, transport, false, false, er)
proxy.UpgradeTransport = upgradeTransport
proxy.UseRequestLocation = true
proxy.UseLocationHost = true

handler := http.Handler(proxy)

if len(target.Path) > 1 {
handler = prependPath(target.Path[:len(target.Path)-1], handler)
}

return proxyHeaders(handler), nil
}

func proxyHeaders(handler http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
req.Header.Del("Authorization")
if req.Header.Get("X-Forwarded-Proto") == "" && req.TLS != nil {
req.Header.Set("X-Forwarded-Proto", "https")
}
handler.ServeHTTP(rw, req)
})
}

func prependPath(prefix string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) > 1 {
req.URL.Path = prefix + req.URL.Path
} else {
req.URL.Path = prefix
}
h.ServeHTTP(w, req)
})
}

func makeUpgradeTransport(config *rest.Config, rt http.RoundTripper) (proxy.UpgradeRequestRoundTripper, error) {
transportConfig, err := config.TransportConfig()
if err != nil {
return nil, err
}

upgrader, err := transport.HTTPWrappersForConfig(transportConfig, proxy.MirrorRequest)
if err != nil {
return nil, err
}

return proxy.NewUpgradeRequestRoundTripper(rt, upgrader), nil
}

0 comments on commit 6a839cb

Please sign in to comment.