Skip to content

[WIP] Update to Gitbase v0.20.0 #439

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

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2ca6e9b
Updated Gitbase version to v0.20.0-beta2
se7entyse7en Apr 12, 2019
cf1581d
Added docker_exec.go with functions for executing commands in running…
se7entyse7en Apr 12, 2019
08072c8
Modified srcd sql command to use the mysql cli embedded in gitbase
se7entyse7en Apr 12, 2019
57c3767
Removed MysqlCli component
se7entyse7en Apr 12, 2019
baf3b29
Removed references to MysqlCli component
se7entyse7en Apr 12, 2019
a8f3495
Fixed SQLREPLTestSuite
se7entyse7en Apr 12, 2019
535e6ce
Fixed components test
se7entyse7en Apr 18, 2019
cf6f9f3
Fixes in docker_exec.go
se7entyse7en Apr 18, 2019
8f602fc
Changed signature of Exec and ExecAttach to return status of exec pro…
se7entyse7en Apr 19, 2019
ea992d2
Added check for exit code of MySQL cli in sql command
se7entyse7en Apr 19, 2019
4304ec3
Updatead gitbase to v0.20.0-beta3
se7entyse7en Apr 19, 2019
d3a5a0c
Refactored components start cmd
se7entyse7en Apr 22, 2019
9ae3c1c
Reused startComponent function to start Gitbase component in sql command
se7entyse7en Apr 22, 2019
6508793
Refactored how stdio is attached
se7entyse7en Apr 22, 2019
a495ab9
Added test for interactive REPL ensuring correct exit with Ctrl-D
se7entyse7en Apr 23, 2019
15cd0be
Fixed typo in stdioAttacher name
se7entyse7en Apr 23, 2019
602fbe7
Fixed deadlock in withRawTerminal method
se7entyse7en Apr 23, 2019
4db06e2
Updated Gitbase version to v0.20.0-beta4
se7entyse7en Apr 26, 2019
0369669
Fixed TestSQLTestSuite/TestWrongQuery
se7entyse7en Apr 26, 2019
352d0f2
Update go.mod and vendor/modules.txt
se7entyse7en Apr 26, 2019
1926b78
Update gitbase component to v0.20.0-rc1
se7entyse7en May 1, 2019
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
26 changes: 17 additions & 9 deletions cmd/srcd/cmd/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,9 @@ func (c *componentsStartCmd) Execute(args []string) error {
return err
}

ctx := context.Background()
started := logAfterTimeoutWithServerLogs("this is taking a while, "+
"it might take a few more minutes while we install all the required images",
5*time.Second)
_, err = client.StartComponent(ctx, &api.StartComponentRequest{
Name: c.Name,
})
started()
err = startComponent(client, c)
if err != nil {
return humanizef(err, "could not start %s", c.Name)
return err
}
}

Expand Down Expand Up @@ -201,6 +194,21 @@ func getComponent(arg string, cmps []components.Component) (*components.Componen
return c, nil
}

func startComponent(client api.EngineClient, c *components.Component) error {
started := logAfterTimeoutWithServerLogs("this is taking a while, "+
"it might take a few more minutes while we install all the required images",
5*time.Second)
_, err := client.StartComponent(context.Background(), &api.StartComponentRequest{
Name: c.Name,
})
started()
if err != nil {
return humanizef(err, "could not start %s", c.Name)
}

return nil
}

func init() {
c := rootCmd.AddCommand(&componentsCmd{})
c.AddCommand(&componentsListCmd{})
Expand Down
137 changes: 19 additions & 118 deletions cmd/srcd/cmd/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,16 @@ package cmd
import (
"context"
"fmt"
"io"
"io/ioutil"

"os"
"os/signal"
"strings"
"time"

"github.com/src-d/engine/api"
"github.com/src-d/engine/cmd/srcd/daemon"
"github.com/src-d/engine/components"
"github.com/src-d/engine/docker"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/term"
"gopkg.in/src-d/go-log.v1"
)

// sqlCmd represents the sql command
Expand All @@ -55,7 +49,7 @@ func (c *sqlCmd) Execute(args []string) error {
return humanizef(err, "could not get daemon client")
}

if err := startGitbaseWithClient(client); err != nil {
if err := startComponent(client, &components.Gitbase); err != nil {
return err
}

Expand Down Expand Up @@ -86,35 +80,11 @@ func (c *sqlCmd) Execute(args []string) error {
}
}

resp, exit, err := runMysqlCli(context.Background(), query)
if err != nil {
return humanizef(err, "could not run mysql client")
}
defer resp.Close()
defer stopMysqlClient()

// in case of Ctrl-C or kill defer wouldn't work
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, os.Kill)
go func() {
<-ch
stopMysqlClient()
}()

if query != "" {
if _, err = io.Copy(os.Stdout, resp.Reader); err != nil {
return err
}

cd := int(<-exit)
if cd != 0 {
return fmt.Errorf("MySQL exited with status %d", cd)
}

return nil
if err = runMysqlCli(context.Background(), query); err != nil {
return err
}

return attachStdio(resp)
return nil
}

func ensureConnReady(client api.EngineClient) error {
Expand Down Expand Up @@ -175,98 +145,29 @@ func pingDB(ctx context.Context, client api.EngineClient, queryTimeoutSeconds ti
}
}

func startGitbaseWithClient(client api.EngineClient) error {
started := logAfterTimeoutWithServerLogs("this is taking a while, "+
"if this is the first time you launch sql client, "+
"it might take a few more minutes while we install all the required images",
5*time.Second)
defer started()

// Download & run dependencies
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
_, err := client.StartComponent(ctx, &api.StartComponentRequest{
Name: components.Gitbase.Name,
})
if err != nil {
return humanizef(err, "could not start gitbase")
}

if err := docker.EnsureInstalled(components.MysqlCli.Image, components.MysqlCli.Version); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function has name startGitbaseWithClient but it doesn't start client anymore. Just gitbase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done here and here.

return humanizef(err, "could not install mysql client")
}

return nil
}

func runMysqlCli(ctx context.Context, query string, opts ...docker.ConfigOption) (*types.HijackedResponse, chan int64, error) {
cmd := []string{"mysql", "-h", components.Gitbase.Name}
func runMysqlCli(ctx context.Context, query string) error {
interactive := true
cmd := []string{"mysql"}
if query != "" {
cmd = append(cmd, "-e", query)
}

config := &container.Config{
Image: components.MysqlCli.ImageWithVersion(),
Cmd: cmd,
cmd = append(cmd, "-t", "-e", query)
interactive = false
}
host := &container.HostConfig{}
docker.ApplyOptions(config, host, opts...)

return docker.Attach(context.Background(), config, host, components.MysqlCli.Name)
}

func attachStdio(resp *types.HijackedResponse) (err error) {
inputDone := make(chan error)
outputDone := make(chan error)
insResp, err := docker.ExecAndAttach(context.Background(), interactive, components.Gitbase.Name, cmd...)

in, out, _ := term.StdStreams()
// set terminal into raw mode to propagate special characters
fd, isTerminal := term.GetFdInfo(in)
if isTerminal {
var prevState *term.State
prevState, err = term.SetRawTerminal(fd)
if err != nil {
return err
}
defer func() {
err = term.RestoreTerminal(fd, prevState)
}()
if err != nil {
return humanizef(err, "a problem occurred while trying to run mysql client")
}

go func() {
_, err := io.Copy(out, resp.Reader)
outputDone <- err
resp.CloseWrite()
}()

go func() {
_, err := io.Copy(resp.Conn, in)

if err := resp.CloseWrite(); err != nil {
log.Debugf("Couldn't send EOF: %s", err)
}

inputDone <- err
}()

select {
case err := <-outputDone:
return err
case err := <-inputDone:
if err == nil {
// Wait for output to complete streaming.
return <-outputDone
}

return err
if insResp.Running {
return fmt.Errorf("MySQL cli is still running")
}
}

func stopMysqlClient() {
err := docker.RemoveContainer(components.MysqlCli.Name)
if err != nil {
log.Warningf("could not stop mysql client: %v", err)
if insResp.ExitCode != 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if we have tests for it. What happens when you exit with ctrl-c or ctrl-d? Does it exit correctly with exit code 0?

Copy link
Contributor Author

@se7entyse7en se7entyse7en Apr 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! Now it exits with 0 on ctrl-d but not on ctrl-c. Gonna try adding a test for these.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it's okay to exit with error on ctrl-c in this case

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a test for Ctrl-D here.

return fmt.Errorf("MySQL cli returned with exit code %d", insResp.ExitCode)
}

return nil
}

func init() {
Expand Down
1 change: 0 additions & 1 deletion cmdtests/components_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func (s *ComponentsTestSuite) TestListStopped() {
`^IMAGE +INSTALLED +RUNNING +PORT +CONTAINER NAME
bblfsh/bblfshd:\S+ +(yes|no) +no +(\d+)? +srcd-cli-bblfshd
bblfsh/web:\S+ +(yes|no) +no +(\d+)? +srcd-cli-bblfsh-web
mysql:\S+ +(yes|no) +no +(\d+)? +srcd-cli-mysql-cli
srcd/cli-daemon:\S+ +(yes|no) +no +(\d+)? +srcd-cli-daemon
srcd/gitbase-web:\S+ +(yes|no) +no +(\d+)? +srcd-cli-gitbase-web
srcd/gitbase:\S+ +(yes|no) +no +(\d+)? +srcd-cli-gitbase
Expand Down
1 change: 0 additions & 1 deletion cmdtests/prune_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ func (s *PruneTestSuite) requireNoImages() {
components.Daemon,
components.Gitbase,
components.GitbaseWeb,
components.MysqlCli,
components.Bblfshd,
components.BblfshWeb,
} {
Expand Down
95 changes: 52 additions & 43 deletions cmdtests/sql_interactive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,30 @@ import (
"io"
"os/exec"
"regexp"
"runtime"
"strings"
"testing"
"time"

"github.com/kr/pty"
"github.com/src-d/engine/cmdtests"
"github.com/src-d/engine/components"
"github.com/src-d/engine/docker"
"github.com/stretchr/testify/require"
)

func (s *SQLREPLTestSuite) TestInteractiveREPL() {
require := s.Require()

command, in, out, err := s.runInteractiveRepl()
require.NoError(err)

res := s.runInteractiveQuery(in, "show tables;\n", out)
require.Contains(res, showTablesOutput)

res = s.runInteractiveQuery(in, "describe table repositories;\n", out)
require.Contains(res, showRepoTableDescOutput)
type interactiveREPLExitMethod int

require.NoError(s.exitInteractiveAndWait(10*time.Second, in, out))
require.NoError(s.waitMysqlCliContainerStopped(10, 1*time.Second))
const (
exitCmd interactiveREPLExitMethod = iota
exitCtrlD
)

command.Wait()
type interativeREPL struct {
bin string
}

func (s *SQLREPLTestSuite) runInteractiveRepl() (*exec.Cmd, io.Writer, <-chan string, error) {
s.T().Helper()

func (r *interativeREPL) start() (*exec.Cmd, io.Writer, <-chan string, error) {
// cannot use `icmd` here, please see: https://github.com/gotestyourself/gotest.tools/issues/151
command := exec.Command(s.Bin(), "sql")
command := exec.Command(r.bin, "sql")

ch := make(chan string)
cr := cmdtests.NewChannelWriter(ch)
Expand All @@ -53,16 +45,17 @@ func (s *SQLREPLTestSuite) runInteractiveRepl() (*exec.Cmd, io.Writer, <-chan st

linifier := cmdtests.NewStreamLinifier(1 * time.Second)
out := linifier.Linify(ch)

for s := range out {
if strings.HasPrefix(s, "mysql>") {
if strings.Contains(s, "MySQL [(none)]>") {
return command, in, out, nil
}
}

return nil, nil, nil, fmt.Errorf("Mysql cli prompt never started")
}

func (s *SQLREPLTestSuite) runInteractiveQuery(in io.Writer, query string, out <-chan string) string {
func (r *interativeREPL) query(in io.Writer, query string, out <-chan string) string {
io.WriteString(in, query)

var res strings.Builder
Expand All @@ -72,16 +65,21 @@ func (s *SQLREPLTestSuite) runInteractiveQuery(in io.Writer, query string, out <
}

res.WriteString(c + "\r\n")
if s.containsSQLOutput(res.String()) {
if r.containsSQLOutput(res.String()) {
break
}
}

return res.String()
}

func (s *SQLREPLTestSuite) exitInteractiveAndWait(timeout time.Duration, in io.Writer, out <-chan string) error {
io.WriteString(in, "exit;\n")
func (r *interativeREPL) exitAndWait(exitMethod interactiveREPLExitMethod, timeout time.Duration, in io.Writer, out <-chan string) error {
switch exitMethod {
case exitCmd:
io.WriteString(in, "exit;\n")
case exitCtrlD:
io.WriteString(in, string('\004'))
}

done := make(chan struct{})
go func() {
Expand All @@ -101,23 +99,6 @@ func (s *SQLREPLTestSuite) exitInteractiveAndWait(timeout time.Duration, in io.W
}
}

func (s *SQLREPLTestSuite) waitMysqlCliContainerStopped(retries int, retryTimeout time.Duration) error {
for i := 0; i < retries; i++ {
running, err := docker.IsRunning(components.MysqlCli.Name, "")
if !running {
return nil
}

if err != nil {
return err
}

time.Sleep(retryTimeout)
}

return fmt.Errorf("maximum number of retries (%d) reached while waiting to stop container", retries)
}

// containsSQLOutput returns `true` if the given string is a SQL output table.
// To detect whether the `out` is a SQL output table, this checks that there
// are exactly 3 separators matching this regex ``\+-+\+`.
Expand All @@ -139,8 +120,36 @@ func (s *SQLREPLTestSuite) waitMysqlCliContainerStopped(retries int, retryTimeou
// | tree_entries |
// +--------------+ <-- third separator
//
func (s *SQLREPLTestSuite) containsSQLOutput(out string) bool {
func (r *interativeREPL) containsSQLOutput(out string) bool {
sep := regexp.MustCompile(`\+-+\+`)
matches := sep.FindAllStringIndex(out, -1)
return len(matches) == 3
}

func (s *SQLREPLTestSuite) TestInteractiveREPL() {
if runtime.GOOS == "windows" {
s.T().Skip("Testing interactive REPL on Windows is not supported")
}

testCasesNames := []string{"exit with 'exit' command", "exit with 'Ctrl-D'"}
for i, em := range []interactiveREPLExitMethod{exitCmd, exitCtrlD} {
s.T().Run(testCasesNames[i], func(t *testing.T) {
require := require.New(t)

repl := &interativeREPL{bin: s.Bin()}

command, in, out, err := repl.start()
require.NoError(err)

res := repl.query(in, "show tables;\n", out)
require.Contains(res, showTablesOutput)

res = repl.query(in, "describe table repositories;\n", out)
require.Contains(res, showRepoTableDescOutput)

require.NoError(repl.exitAndWait(em, 10*time.Second, in, out))

require.NoError(command.Wait())
})
}
}
Loading