Skip to content

Commit 71a0b0a

Browse files
committed
feat(workflow-cli): add exec pod
1 parent cf55923 commit 71a0b0a

7 files changed

Lines changed: 170 additions & 3 deletions

File tree

cmd/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ type Commander interface {
6464
PermCreate(string, string, bool) error
6565
PermDelete(string, string, bool) error
6666
PsList(string, int) error
67+
PsExec(string, string, bool, bool, []string) error
6768
PsScale(string, []string) error
6869
PsRestart(string, string) error
6970
RegistryList(string) error

cmd/ps.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package cmd
22

33
import (
4+
"encoding/base64"
45
"fmt"
56
"io"
7+
"os"
68
"regexp"
79
"strconv"
810
"strings"
@@ -11,6 +13,8 @@ import (
1113
drycc "github.com/drycc/controller-sdk-go"
1214
"github.com/drycc/controller-sdk-go/api"
1315
"github.com/drycc/controller-sdk-go/ps"
16+
"github.com/gorilla/websocket"
17+
"golang.org/x/term"
1418
)
1519

1620
// PsList lists an app's processes.
@@ -34,6 +38,25 @@ func (d *DryccCmd) PsList(appID string, results int) error {
3438
return nil
3539
}
3640

41+
// PsList lists an app's processes.
42+
func (d *DryccCmd) PsExec(appID, podID string, tty, stdin bool, command []string) error {
43+
s, appID, err := load(d.ConfigFile, appID)
44+
if err != nil {
45+
return err
46+
}
47+
conn, err := ps.Exec(s.Client, appID, podID, tty, stdin, command)
48+
if err != nil {
49+
return err
50+
}
51+
defer conn.Close()
52+
if stdin {
53+
streamExec(conn)
54+
} else {
55+
printExec(d, conn)
56+
}
57+
return nil
58+
}
59+
3760
// PsScale scales an app's processes.
3861
func (d *DryccCmd) PsScale(appID string, targets []string) error {
3962
s, appID, err := load(d.ConfigFile, appID)
@@ -117,6 +140,57 @@ func printProcesses(appID string, input []api.Pods, wOut io.Writer) {
117140
}
118141
}
119142

143+
func printExec(d *DryccCmd, conn *websocket.Conn) error {
144+
messageType, message, err := conn.ReadMessage()
145+
if err != nil {
146+
return err
147+
}
148+
if messageType == websocket.TextMessage {
149+
d.Printf("%s", string(message))
150+
} else {
151+
d.Printf(base64.StdEncoding.EncodeToString(message))
152+
}
153+
return nil
154+
}
155+
156+
func streamExec(conn *websocket.Conn) error {
157+
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
158+
if err != nil {
159+
panic(err)
160+
}
161+
defer term.Restore(int(os.Stdin.Fd()), oldState)
162+
163+
t := term.NewTerminal(os.Stdin, "")
164+
165+
go func() {
166+
for {
167+
messageType, message, err := conn.ReadMessage()
168+
if err != nil {
169+
break
170+
} else if messageType == websocket.CloseMessage {
171+
break
172+
} else {
173+
t.Write(message)
174+
}
175+
}
176+
}()
177+
178+
for {
179+
line, err := t.ReadLine()
180+
if err != nil {
181+
return err
182+
}
183+
if line == "exit" {
184+
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
185+
break
186+
}
187+
if err := conn.WriteMessage(websocket.TextMessage, []byte(line+"\n")); err != nil {
188+
break
189+
}
190+
}
191+
return nil
192+
}
193+
120194
func parseType(target string, appID string) (string, string) {
121195
var psType, psName string
122196

cmd/ps_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package cmd
33
import (
44
"bytes"
55
"fmt"
6+
"log"
67
"net/http"
78
"testing"
89

910
"github.com/drycc/controller-sdk-go/api"
1011
"github.com/drycc/controller-sdk-go/pkg/time"
1112
"github.com/drycc/workflow-cli/pkg/testutil"
13+
"github.com/gorilla/websocket"
1214
"github.com/stretchr/testify/assert"
1315
)
1416

@@ -116,6 +118,45 @@ foo-web-4084101150-c871y up (v2)
116118
`, "output")
117119
}
118120

121+
var upgrader = websocket.Upgrader{} // use default options
122+
123+
func TestPsExec(t *testing.T) {
124+
t.Parallel()
125+
cf, server, err := testutil.NewTestServerAndClient()
126+
if err != nil {
127+
t.Fatal(err)
128+
}
129+
defer server.Close()
130+
var b bytes.Buffer
131+
cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
132+
133+
server.Mux.HandleFunc("/v2/apps/foo/pods/foo-web-111/exec/", func(w http.ResponseWriter, r *http.Request) {
134+
c, err := upgrader.Upgrade(w, r, nil)
135+
if err != nil {
136+
log.Print("upgrade:", err)
137+
return
138+
}
139+
defer c.Close()
140+
for {
141+
messageType, message, err := c.ReadMessage()
142+
if err != nil {
143+
log.Println("read:", err)
144+
break
145+
}
146+
147+
log.Printf("recv: %s", message)
148+
err = c.WriteMessage(messageType, []byte("# "+"\n"))
149+
if err != nil {
150+
log.Println("write:", err)
151+
break
152+
}
153+
}
154+
})
155+
156+
err = cmdr.PsExec("foo", "foo-web-111", true, false, []string{"/bin/sh"})
157+
assert.NoError(t, err)
158+
}
159+
119160
type psTargetCases struct {
120161
Targets []string
121162
ExpectedError bool

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ go 1.18
44

55
require (
66
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
7-
github.com/drycc/controller-sdk-go v0.0.0-20220801120356-de65d8cb5598
7+
github.com/drycc/controller-sdk-go v0.0.0-20220822080925-801edf7edf7b
88
github.com/drycc/pkg v0.0.0-20210826011456-c60b87108840
9+
github.com/gorilla/websocket v1.5.0
910
github.com/olekukonko/tablewriter v0.0.5
1011
github.com/stretchr/testify v1.8.0
12+
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
13+
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
1114
gopkg.in/yaml.v2 v2.4.0
1215
)
1316

@@ -19,6 +22,7 @@ require (
1922
github.com/mattn/go-runewidth v0.0.9 // indirect
2023
github.com/pmezard/go-difflib v1.0.0 // indirect
2124
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect
25+
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
2226
golang.org/x/text v0.3.6 // indirect
2327
gopkg.in/yaml.v3 v3.0.1 // indirect
2428
)

go.sum

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
3636
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
3737
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
3838
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
39+
github.com/arschles/assert v1.0.1-0.20191213221312-71f210f9375a/go.mod h1:DEpb9dlSxnDdJFQLnLwJHnNNpB+Ssa3mrqTSJ/xuv3E=
3940
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
4041
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
4142
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -48,8 +49,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
4849
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4950
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
5051
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
51-
github.com/drycc/controller-sdk-go v0.0.0-20220801120356-de65d8cb5598 h1:SfwHwhoe7QpNzEmN96G6egIbZKWkfiyLCFk7SlhpQ8U=
52-
github.com/drycc/controller-sdk-go v0.0.0-20220801120356-de65d8cb5598/go.mod h1:aGxL2AkmmG7d5afWiUGy93/+UPDnIxgsWSwxuznjteE=
52+
github.com/drycc/controller-sdk-go v0.0.0-20220822080925-801edf7edf7b h1:lMOLLsl/qBqiDbZ+vl2MU4H1AR9k3RW9I4DkaTVVdGg=
53+
github.com/drycc/controller-sdk-go v0.0.0-20220822080925-801edf7edf7b/go.mod h1:AWyrmGt7lPQ5Dd2HwO4zyOM/sfuBXVPlpw1Mr5kUZNg=
5354
github.com/drycc/pkg v0.0.0-20210826011456-c60b87108840 h1:0OhP9AQ0mh3q0TMxK4PJTPSFwD/wj0xugiaZ3lnLcNA=
5455
github.com/drycc/pkg v0.0.0-20210826011456-c60b87108840/go.mod h1:KX1FLm7Fq6FLCsjjRsgfI/bMQuHZXqYf1ZXU9fzJhDw=
5556
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
@@ -118,6 +119,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
118119
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
119120
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
120121
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
122+
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
123+
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
121124
github.com/goware/urlx v0.3.1 h1:BbvKl8oiXtJAzOzMqAQ0GfIhf96fKeNEZfm9ocNSUBI=
122125
github.com/goware/urlx v0.3.1/go.mod h1:h8uwbJy68o+tQXCGZNa9D73WN8n0r9OBae5bUnLcgjw=
123126
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -204,6 +207,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
204207
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
205208
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
206209
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
210+
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
211+
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
207212
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
208213
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
209214
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -289,9 +294,13 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
289294
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
290295
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
291296
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
297+
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
298+
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
292299
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
293300
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
294301
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
302+
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
303+
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
295304
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
296305
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
297306
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

parser/ps.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package parser
22

33
import (
4+
"os"
5+
46
docopt "github.com/docopt/docopt-go"
57
"github.com/drycc/workflow-cli/cmd"
8+
"golang.org/x/exp/slices"
69
)
710

811
// Ps routes ps commands to their specific function.
@@ -11,6 +14,7 @@ func Ps(argv []string, cmdr cmd.Commander) error {
1114
Valid commands for processes:
1215
1316
ps:list list application processes
17+
ps:exec execute a command in a container
1418
ps:restart restart an application or its process types
1519
ps:scale scale processes (e.g. web=4 worker=2)
1620
ps:stop stop processes (e.g. web worker)
@@ -22,6 +26,8 @@ Use 'drycc help [command]' to learn more.
2226
switch argv[0] {
2327
case "ps:list":
2428
return psList(argv, cmdr)
29+
case "ps:exec":
30+
return psExec(argv, cmdr)
2531
case "ps:restart":
2632
return psRestart(argv, cmdr)
2733
case "ps:scale":
@@ -61,6 +67,34 @@ Options:
6167
return cmdr.PsList(safeGetValue(args, "--app"), 1000)
6268
}
6369

70+
func psExec(argv []string, cmdr cmd.Commander) error {
71+
usage := `
72+
Execute a command in a container.
73+
74+
Usage: drycc ps:exec <pod> [options] -- <command>...
75+
76+
Options:
77+
-a --app=<app>
78+
the uniquely identifiable name for the application.
79+
-t --tty
80+
stdin is a TTY.
81+
-i --stdin
82+
pass stdin to the container.
83+
`
84+
85+
args, err := docopt.Parse(usage, argv, true, "", false, true)
86+
if err != nil {
87+
return err
88+
}
89+
app := safeGetValue(args, "--app")
90+
pod := safeGetValue(args, "<pod>")
91+
tty := args["--tty"].(bool)
92+
stdin := args["--stdin"].(bool)
93+
index := slices.Index(os.Args, "--")
94+
command := os.Args[index+1:]
95+
return cmdr.PsExec(app, pod, tty, stdin, command)
96+
}
97+
6498
func psRestart(argv []string, cmdr cmd.Commander) error {
6599
usage := `
66100
Restart an application, a process type or a specific process.

parser/ps_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ func (d FakeDryccCmd) PsList(string, int) error {
1616
return errors.New("ps:list")
1717
}
1818

19+
func (d FakeDryccCmd) PsExec(appID, podID string, tty, stdin bool, command []string) error {
20+
return errors.New("ps:exec")
21+
}
22+
1923
func (d FakeDryccCmd) PsScale(string, []string) error {
2024
return errors.New("ps:scale")
2125
}

0 commit comments

Comments
 (0)