@@ -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
2224func 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 , "\n failed 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 , "\n failed 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.
0 commit comments