Skip to content

Commit e82eca3

Browse files
authored
Merge pull request #233 from planetscale/nickvanw/connect-exec
connect: add experimental feature for having connect run a command after
2 parents f019059 + e24af3a commit e82eca3

File tree

4 files changed

+60
-13
lines changed

4 files changed

+60
-13
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23 // indirect
1616
github.com/lensesio/tableprinter v0.0.0-20201125135848-89e81fc956e7
1717
github.com/mattn/go-runewidth v0.0.9 // indirect
18+
github.com/mattn/go-shellwords v1.0.11
1819
github.com/mitchellh/go-homedir v1.1.0
1920
github.com/pkg/browser v0.0.0-20201112035734-206646e67786
2021
github.com/pkg/errors v0.9.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
222222
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
223223
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
224224
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
225+
github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
226+
github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
225227
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
226228
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
227229
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=

internal/cmd/connect/connect.go

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
"errors"
66
"fmt"
77
"os"
8+
"os/exec"
89
"runtime"
910
"syscall"
1011

12+
"github.com/mattn/go-shellwords"
1113
"github.com/planetscale/cli/internal/cmdutil"
1214
"github.com/planetscale/cli/internal/printer"
1315
"github.com/planetscale/cli/internal/promptutil"
@@ -21,8 +23,9 @@ import (
2123

2224
func ConnectCmd(ch *cmdutil.Helper) *cobra.Command {
2325
var flags struct {
24-
localAddr string
25-
remoteAddr string
26+
localAddr string
27+
remoteAddr string
28+
execCommand string
2629
}
2730

2831
cmd := &cobra.Command{
@@ -47,10 +50,6 @@ argument:
4750
ctx := context.Background()
4851
database := args[0]
4952

50-
if !printer.IsTTY || ch.Printer.Format() != printer.Human {
51-
return errors.New("pscale connect only works in interactive mode")
52-
}
53-
5453
client, err := ch.Config.NewClientFromConfig()
5554
if err != nil {
5655
return err
@@ -97,12 +96,18 @@ argument:
9796
Logger: cmdutil.NewZapLogger(ch.Debug()),
9897
}
9998

100-
err = runProxy(proxyOpts, database, branch)
99+
proxyReady := make(chan string, 1)
100+
101+
if flags.execCommand != "" {
102+
go runCommand(ctx, flags.execCommand, database, branch, proxyReady)
103+
}
104+
105+
err = runProxy(proxyOpts, database, branch, proxyReady)
101106
if err != nil {
102107
if isAddrInUse(err) {
103108
ch.Printer.Println("Tried address 127.0.0.1:3306, but it's already in use. Picking up a random port ...")
104109
proxyOpts.LocalAddr = "127.0.0.1:0"
105-
return runProxy(proxyOpts, database, branch)
110+
return runProxy(proxyOpts, database, branch, proxyReady)
106111
}
107112
return err
108113
}
@@ -117,18 +122,19 @@ argument:
117122
cmd.PersistentFlags().StringVar(&flags.remoteAddr, "remote-addr", "",
118123
"PlanetScale Database remote network address. By default the remote address is populated automatically from the PlanetScale API.")
119124
cmd.MarkPersistentFlagRequired("org") // nolint:errcheck
125+
cmd.PersistentFlags().StringVar(&flags.execCommand, "execute", "", "Run this command after successfully connecting to the database.")
120126

121127
return cmd
122128
}
123129

124-
func runProxy(proxyOpts proxy.Options, database, branch string) error {
130+
func runProxy(proxyOpts proxy.Options, database, branch string, ready chan string) error {
125131
ctx := context.Background()
126132
p, err := proxy.NewClient(proxyOpts)
127133
if err != nil {
128134
return fmt.Errorf("couldn't create proxy client: %s", err)
129135
}
130136

131-
go func() {
137+
go func(ready chan string) {
132138
// this is blocking and will only return once p.Run() below is
133139
// invoked
134140
addr, err := p.LocalAddr()
@@ -142,14 +148,51 @@ func runProxy(proxyOpts proxy.Options, database, branch string) error {
142148
printer.BoldBlue(branch),
143149
printer.BoldBlue(addr.String()),
144150
)
145-
}()
151+
ready <- addr.String()
152+
}(ready)
146153

147154
// TODO(fatih): replace with signal.NotifyContext once Go 1.16 is released
148155
// https://go-review.googlesource.com/c/go/+/219640
149156
ctx = sigutil.WithSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
150157
return p.Run(ctx)
151158
}
152159

160+
func runCommand(ctx context.Context, command, database, branch string, ready chan string) {
161+
args, err := shellwords.Parse(command)
162+
if err != nil {
163+
fmt.Fprintf(os.Stderr, "\nfailed to parse command, not running: %s", err)
164+
return
165+
}
166+
addr := <-ready
167+
168+
ctx = sigutil.WithSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
169+
child := exec.CommandContext(ctx, args[0], args[1:]...)
170+
child.Env = os.Environ()
171+
child.Stdout = os.Stdout
172+
child.Stderr = os.Stderr
173+
174+
connStr := fmt.Sprintf("DATABASE_URL=mysql2://root@%s/%s", addr, database)
175+
child.Env = append(child.Env, connStr)
176+
177+
hostEnv := fmt.Sprintf("PLANETSCALE_DATABASE_HOST=%s", addr)
178+
child.Env = append(child.Env, hostEnv)
179+
180+
dbName := fmt.Sprintf("PLANETSCALE_DATABASE_NAME=%s", database)
181+
child.Env = append(child.Env, dbName)
182+
183+
branchName := fmt.Sprintf("PLANETSCALE_BRANCH_NAME=%s", branch)
184+
child.Env = append(child.Env, branchName)
185+
186+
// todo(nickvanw): right now, this starts the process and then doesn't track what happens next
187+
// if the process exits (non-zero or otherwise) the proxy will remain running
188+
//
189+
// the right behavior is probably to at least allow the user to configure that the proxy should
190+
// exit when this process exits, and pass through the exit code
191+
if err := child.Start(); err != nil {
192+
fmt.Fprintf(os.Stderr, "\nfailed to execute command: %s", err)
193+
}
194+
}
195+
153196
// isAddrInUse returns an error if the error indicates that the given address
154197
// is already in use. Becaue different OS return different error messages, we
155198
// try to get the underlying error.

internal/cmd/root.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"io/fs"
2121
"log"
2222
"os"
23+
"strings"
2324

2425
"github.com/planetscale/cli/internal/cmd/auth"
2526
"github.com/planetscale/cli/internal/cmd/backup"
@@ -45,6 +46,7 @@ import (
4546
)
4647

4748
var cfgFile string
49+
var replacer = strings.NewReplacer("-", "_", ".", "_")
4850

4951
// rootCmd represents the base command when called without any subcommands
5052
var rootCmd = &cobra.Command{
@@ -119,8 +121,6 @@ func Execute(ver, commit, buildDate string) error {
119121
"service-token-name", "", "The Service Token name for authenticating.")
120122
rootCmd.PersistentFlags().StringVar(&cfg.ServiceToken,
121123
"service-token", "", "Service Token for authenticating.")
122-
_ = rootCmd.PersistentFlags().MarkHidden("service-token-name")
123-
_ = rootCmd.PersistentFlags().MarkHidden("service-token")
124124

125125
// We don't want to show the default value
126126
rootCmd.PersistentFlags().Lookup("api-token").DefValue = ""
@@ -184,6 +184,7 @@ func initConfig() {
184184
}
185185

186186
viper.SetEnvPrefix("planetscale")
187+
viper.SetEnvKeyReplacer(replacer)
187188
viper.AutomaticEnv() // read in environment variables that match
188189

189190
// If a config file is found, read it in.

0 commit comments

Comments
 (0)