[#73] Minimum actually viable product
This commit is contained in:
parent
a80457578e
commit
0d2e113319
|
@ -5,4 +5,6 @@ go 1.16
|
|||
require (
|
||||
github.com/alecthomas/kong v0.2.17 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/term v1.1.0 // indirect
|
||||
)
|
||||
|
|
|
@ -6,9 +6,13 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
|
|||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
|
||||
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -8,10 +8,15 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"syscall"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pkg/term"
|
||||
)
|
||||
|
||||
var cli struct {
|
||||
|
@ -34,6 +39,18 @@ type terminalOutput struct {
|
|||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
type serviceFailed struct {
|
||||
message
|
||||
Service string `json:"service"`
|
||||
Error string `json:"error"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
type errorExit struct {
|
||||
error
|
||||
status int
|
||||
}
|
||||
|
||||
func run() error {
|
||||
apiUrl, err := url.Parse(cli.Host)
|
||||
if err != nil {
|
||||
|
@ -56,33 +73,89 @@ func run() error {
|
|||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
done := make(chan struct{})
|
||||
tty, err := term.Open("/dev/tty")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open stdin tty")
|
||||
}
|
||||
if err := tty.SetRaw(); err != nil {
|
||||
return errors.Wrap(err, "failed to set raw mode")
|
||||
}
|
||||
defer tty.Restore()
|
||||
sigint := make(chan os.Signal, 1)
|
||||
sigterm := make(chan os.Signal, 1)
|
||||
signal.Notify(sigint, syscall.SIGINT)
|
||||
signal.Notify(sigterm, syscall.SIGTERM)
|
||||
done1 := make(chan error)
|
||||
go func() {
|
||||
defer close(done)
|
||||
defer close(done1)
|
||||
for {
|
||||
_, rawMsg, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("failed to read websocket message:", err)
|
||||
done1 <- errors.Wrap(err, "failed to read websocket message")
|
||||
return
|
||||
}
|
||||
var genericMsg message
|
||||
if err := json.Unmarshal(rawMsg, &genericMsg); err != nil {
|
||||
log.Println("failed to parse websocket message:", err)
|
||||
done1 <- errors.Wrap(err, "failed to parse websocket message")
|
||||
return
|
||||
}
|
||||
switch genericMsg.Event {
|
||||
case "terminalOutput":
|
||||
var msg terminalOutput
|
||||
if err := json.Unmarshal(rawMsg, &msg); err != nil {
|
||||
log.Println("failed to parse websocket message:", err)
|
||||
done1 <- errors.Wrap(err, "failed to parse websocket message")
|
||||
return
|
||||
}
|
||||
fmt.Print(msg.Output)
|
||||
case "serviceFailed":
|
||||
var msg serviceFailed
|
||||
if err := json.Unmarshal(rawMsg, &msg); err != nil {
|
||||
done1 <- errors.Wrap(err, "failed to parse websocket message")
|
||||
return
|
||||
}
|
||||
done1 <- errorExit{nil, msg.Code}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
input := make(chan []byte)
|
||||
done2 := make(chan error)
|
||||
go func() {
|
||||
defer close(done2)
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
n, err := os.Stdin.Read(buf)
|
||||
if err != nil {
|
||||
done2 <- errors.Wrap(err, "failed to read from stdin")
|
||||
return
|
||||
}
|
||||
input <- buf[:n]
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case err := <-done1:
|
||||
return err
|
||||
case err := <-done2:
|
||||
return err
|
||||
case <-sigint:
|
||||
return errorExit{nil, int(syscall.SIGINT) + 128}
|
||||
case <-sigterm:
|
||||
return errorExit{nil, int(syscall.SIGTERM) + 128}
|
||||
case data := <-input:
|
||||
msg := terminalInput{
|
||||
message: message{
|
||||
Event: "terminalInput",
|
||||
},
|
||||
Input: string(data),
|
||||
}
|
||||
rawMsg, err := json.Marshal(&msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create websocket message")
|
||||
}
|
||||
if err := conn.WriteMessage(websocket.TextMessage, rawMsg); err != nil {
|
||||
return errors.Wrap(err, "failed to send websocket message")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -90,7 +163,17 @@ func run() error {
|
|||
|
||||
func main() {
|
||||
kong.Parse(&cli)
|
||||
exitStatus := 0
|
||||
if err := run(); err != nil {
|
||||
log.Fatalln(err)
|
||||
if typedErr, ok := err.(errorExit); ok {
|
||||
err = typedErr.error
|
||||
exitStatus = typedErr.status
|
||||
} else {
|
||||
exitStatus = 1
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue