Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pkg/console): support interactive console #56

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type Commander interface {
PermsList(string, bool, int) error
PermCreate(string, string, bool) error
PermDelete(string, string, bool) error
PsConsole(string, string, string, string, bool) error
PsList(string, int) error
PsScale(string, []string) error
PsRestart(string, string) error
Expand Down
17 changes: 17 additions & 0 deletions cmd/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"github.com/teamhephy/controller-sdk-go"
"github.com/teamhephy/controller-sdk-go/api"
"github.com/teamhephy/controller-sdk-go/ps"
"github.com/teamhephy/workflow-cli/pkg/console"
"github.com/teamhephy/workflow-cli/settings"
)

// PsList lists an app's processes.
Expand Down Expand Up @@ -160,3 +162,18 @@ func parsePsTargets(targets []string) (map[string]int, error) {

return targetMap, nil
}

// Get a console on a running pod.
func (d *HephyCmd) PsConsole(podName string, procType string, appID string, execCommand string, debug bool) error {
s, err := settings.Load(d.ConfigFile)
if err != nil {
return err
}
d.Printf("Starting console session with command %s for pod %s for application %s\n", execCommand, podName, appID)
consoleErr := console.Start(s.Client, appID, podName, procType, execCommand, debug)
if consoleErr != nil {
return consoleErr
}

return nil
}
19 changes: 14 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ require (
github.com/arschles/assert v1.0.0
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/engineyard/workflow-cli v2.21.6+incompatible // indirect
github.com/gorilla/websocket v1.4.2
github.com/goware/urlx v0.2.0
github.com/imdario/mergo v0.3.12 // indirect
github.com/mattn/go-runewidth v0.0.5-0.20181218000649-703b5e6b11ae
github.com/olekukonko/tablewriter v0.0.2-0.20190315073140-f82d31373321
github.com/sirupsen/logrus v1.8.1
github.com/teamhephy/controller-sdk-go v0.0.0-20181015154232-a1ffb4886a5f
github.com/teamhephy/pkg v0.5.1-0.20180912202400-777f37a30108
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
golang.org/x/sys v0.0.0-20190318195719-6c81ef8f67ca
golang.org/x/text v0.3.1-0.20190306152657-5d731a35f486
gopkg.in/yaml.v2 v2.2.2
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073
golang.org/x/text v0.3.4
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
gopkg.in/yaml.v2 v2.4.0
k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d
k8s.io/client-go v0.0.0-20190819141724-e14f31a72a77
k8s.io/klog v1.0.0 // indirect
k8s.io/utils v0.0.0-20210527160623-6fdb442a123b // indirect
)
477 changes: 477 additions & 0 deletions go.sum

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions parser/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ func Ps(argv []string, cmdr cmd.Commander) error {
usage := executable.Render(`
Valid commands for processes:

ps:console get shell access to a running pod
ps:list list application processes
ps:restart restart an application or its process types
ps:scale scale processes (e.g. web=4 worker=2)
Expand All @@ -19,6 +20,8 @@ Use '{{.Name}} help [command]' to learn more.
`)

switch argv[0] {
case "ps:console":
return psConsole(argv, cmdr)
case "ps:list":
return psList(argv, cmdr)
case "ps:restart":
Expand Down Expand Up @@ -114,3 +117,37 @@ Options:
apps := safeGetValue(args, "--app")
return cmdr.PsScale(apps, args["<type>=<num>"].([]string))
}

func psConsole(argv []string, cmdr cmd.Commander) error {
usage := executable.Render(`
Get a console session to a running container within a pod
Usage: {{.Name}} ps:console <podname> <type> <app> <command> [options]
Arguments:
<podname>
the process name as defined in your Procfile, such as 'web' or 'worker'.
Note that Dockerfile apps have a default 'cmd' process type.
<type>
the process name as defined in your Procfile, such as 'web' or 'worker'.
<app>
the uniquely identifiable name for the application.
<command>
the command to use upon entring the pod e.g. '/bin/bash'
Options:
--debug=true
show debug information
`)

args, err := docopt.Parse(usage, argv, true, "", false, true)

if err != nil {
return err
}

podName := safeGetValue(args, "<podname>")
procType := safeGetValue(args, "<type>")
application := safeGetValue(args, "<app>")
execCommand := safeGetValue(args, "<command>")
debug := safeGetValue(args, "--debug") == "true"

return cmdr.PsConsole(podName, procType, application, execCommand, debug)
}
115 changes: 115 additions & 0 deletions pkg/console/console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package console

import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/sirupsen/logrus"
deis "github.com/teamhephy/controller-sdk-go"
"io/ioutil"
"k8s.io/client-go/tools/clientcmd"
"os"
"time"
)

var log = logrus.New()

func init() {
log.SetLevel(logrus.PanicLevel)
log.SetOutput(os.Stdout)
}

type KubeApiAccess struct {
ApiEndpoint string
Token string
WebsocketTimeout int
Error bool
Msg string
}

var myKubeApiAccess KubeApiAccess
var timeOutContext context.Context
var timeOutContextCancelation context.CancelFunc

func getKubernetesApiAndToken(c *deis.Client, application string) error {
u := fmt.Sprintf("/v2/apps/%s/console-token", application)
httpResp, httpErr := c.Request("GET", u, nil)
if httpErr != nil {
log.Println(httpErr)
return httpErr
}
defer httpResp.Body.Close()

body, httpErr := ioutil.ReadAll(httpResp.Body)
if httpErr != nil {
log.Println(httpErr)
return httpErr
}
// No hephy token provided == 401; Wrong hephy token provided == 403
if httpResp.StatusCode == 401 || httpResp.StatusCode == 403 {
return errors.New("\nPermission denied. Please ensure that you have access to the application '" + application + "'")
}
json.Unmarshal([]byte(body), &myKubeApiAccess)
if myKubeApiAccess.Error {
return errors.New(myKubeApiAccess.Msg)
}

return nil
}

func Start(c *deis.Client, applicationName string, podName string, procType string, execCommand string, debug bool) error {
if debug {
log.SetLevel(logrus.DebugLevel)
}
collectParametersError := getKubernetesApiAndToken(c, applicationName)
if collectParametersError != nil {
return collectParametersError
}

containerName := applicationName + "-" + procType
opts := &ExecOptions{}
opts.Namespace = applicationName
opts.Pod = podName
opts.Container = containerName
opts.TTY = true
opts.Stdin = true
opts.Command = []string{execCommand}

log.Printf("[Start] Container name: %s", opts.Container)

sDec, err := base64.StdEncoding.DecodeString(myKubeApiAccess.Token)
if err != nil {
log.Println(err)
return err
}
myKubeApiAccess.Token = string(sDec)
config, err := clientcmd.BuildConfigFromFlags(myKubeApiAccess.ApiEndpoint, "")
if err != nil {
log.Println(err)
return err
}

timeOutContext, timeOutContextCancelation = context.WithTimeout(context.TODO(), time.Duration(myKubeApiAccess.WebsocketTimeout)*time.Second)
defer timeOutContextCancelation()

wrt, err := ExecRoundTripper(config, WebsocketCallback)
if err != nil {
log.Println(err)
return err
}

req, err := ExecRequest(config, opts)
if err != nil {
log.Println(err)
return err
}

if _, err = wrt.RoundTrip(req); err != nil {
log.Println(err)
return err
}

return nil
}
Loading