Skip to content

Commit

Permalink
[feat]: add support for docker start command (#1586)
Browse files Browse the repository at this point in the history
* fixed docker start command bug

Signed-off-by: Yaxhveer <[email protected]>
---------

Signed-off-by: Yaxhveer <[email protected]>
Co-authored-by: Ahmed Lotfy <[email protected]>
Co-authored-by: naoki kuroda <[email protected]>
Co-authored-by: Gourav kumar <[email protected]>
  • Loading branch information
4 people committed May 21, 2024
1 parent 33f40bf commit 8f0abd6
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 22 deletions.
24 changes: 18 additions & 6 deletions pkg/core/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ func (a *App) Setup(_ context.Context) error {
}
a.docker = d

if (a.kind == utils.Docker || a.kind == utils.DockerCompose) && IsDetachMode(a.cmd) {
return fmt.Errorf("detach mode is not allowed in Keploy command")
if utils.IsDockerKind(a.kind) && isDetachMode(a.logger, a.cmd, a.kind) {
return fmt.Errorf("application could not be started in detached mode")
}

switch a.kind {
case utils.Docker:
case utils.DockerRun, utils.DockerStart:
err := a.SetupDocker()
if err != nil {
return err
Expand All @@ -100,7 +100,9 @@ func (a *App) ContainerIPv4Addr() string {

func (a *App) SetupDocker() error {
var err error
cont, net, err := ParseDockerCmd(a.cmd)

cont, net, err := ParseDockerCmd(a.cmd, a.kind, a.docker)

if err != nil {
utils.LogError(a.logger, err, "failed to parse container name from given docker command", zap.String("cmd", a.cmd))
return err
Expand All @@ -117,6 +119,16 @@ func (a *App) SetupDocker() error {
a.logger.Warn(fmt.Sprintf("given docker network:(%v) is different from parsed docker network:(%v)", a.containerNetwork, net))
}

if a.kind == utils.DockerStart {
running, err := a.docker.IsContainerRunning(cont)
if err != nil {
return err
}
if running {
return fmt.Errorf("docker container is already in running state")
}
}

//injecting appNetwork to keploy.
err = a.injectNetwork(a.containerNetwork)
if err != nil {
Expand Down Expand Up @@ -420,7 +432,7 @@ func (a *App) runDocker(ctx context.Context) models.AppError {
func (a *App) Run(ctx context.Context, inodeChan chan uint64) models.AppError {
a.inodeChan = inodeChan

if a.kind == utils.DockerCompose || a.kind == utils.Docker {
if utils.IsDockerKind(a.kind) {
return a.runDocker(ctx)
}
return a.run(ctx)
Expand All @@ -431,7 +443,7 @@ func (a *App) run(ctx context.Context) models.AppError {
userCmd := a.cmd
username := os.Getenv("SUDO_USER")

if utils.FindDockerCmd(a.cmd) == utils.Docker {
if utils.FindDockerCmd(a.cmd) == utils.DockerRun {
userCmd = utils.EnsureRmBeforeName(userCmd)
}

Expand Down
17 changes: 17 additions & 0 deletions pkg/core/app/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,3 +518,20 @@ func (idc *Impl) SetKeployNetwork(c *Compose) (*NetworkInfo, error) {
}
return networkInfo, nil
}

// IsContainerRunning check if the container is already running or not, required for docker start command.
func (idc *Impl) IsContainerRunning(containerName string) (bool, error) {

ctx, cancel := context.WithTimeout(context.Background(), idc.timeoutForDockerQuery)
defer cancel()

containerJSON, err := idc.ContainerInspect(ctx, containerName)
if err != nil {
return false, err
}

if containerJSON.State.Running {
return true, nil
}
return false, nil
}
2 changes: 2 additions & 0 deletions pkg/core/app/docker/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type Client interface {
SetKeployNetwork(c *Compose) (*NetworkInfo, error)
ReadComposeFile(filePath string) (*Compose, error)
WriteComposeFile(compose *Compose, path string) error

IsContainerRunning(containerName string) (bool, error)
}

type NetworkInfo struct {
Expand Down
44 changes: 41 additions & 3 deletions pkg/core/app/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import (
"os"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
"syscall"

"go.keploy.io/server/v2/pkg/core/app/docker"
"go.keploy.io/server/v2/utils"
"go.uber.org/zap"
)

func findComposeFile() string {
Expand Down Expand Up @@ -47,9 +52,17 @@ func modifyDockerComposeCommand(appCmd, newComposeFile string) string {
return fmt.Sprintf("%s -f %s", appCmd, newComposeFile)
}

func ParseDockerCmd(cmd string) (string, string, error) {
func ParseDockerCmd(cmd string, kind utils.CmdType, idc docker.Client) (string, string, error) {

// Regular expression patterns
containerNamePattern := `--name\s+([^\s]+)`
var containerNamePattern string
switch kind {
case utils.DockerStart:
containerNamePattern = `start\s+(?:-[^\s]+\s+)*([^\s]*)`
default:
containerNamePattern = `--name\s+([^\s]+)`
}

networkNamePattern := `(--network|--net)\s+([^\s]+)`

// Extract container name
Expand All @@ -60,6 +73,17 @@ func ParseDockerCmd(cmd string) (string, string, error) {
}
containerName := containerNameMatches[1]

if kind == utils.DockerStart {
networks, err := idc.ExtractNetworksForContainer(containerName)
if err != nil {
return containerName, "", err
}
for i := range networks {
return containerName, i, nil
}
return containerName, "", fmt.Errorf("failed to parse network name")
}

// Extract network name
networkNameRegex := regexp.MustCompile(networkNamePattern)
networkNameMatches := networkNameRegex.FindStringSubmatch(cmd)
Expand All @@ -86,10 +110,24 @@ func getInode(pid int) (uint64, error) {
return i, nil
}

func IsDetachMode(command string) bool {
func isDetachMode(logger *zap.Logger, command string, kind utils.CmdType) bool {
args := strings.Fields(command)

if kind == utils.DockerStart {
flags := []string{"-a", "--attach", "-i", "--interactive"}

for _, arg := range args {
if slices.Contains(flags, arg) {
return false
}
}
utils.LogError(logger, fmt.Errorf("docker start require --attach/-a or --interactive/-i flag"), "failed to start command")
return true
}

for _, arg := range args {
if arg == "-d" || arg == "--detach" {
utils.LogError(logger, fmt.Errorf("detach mode is not allowed in Keploy command"), "failed to start command")
return true
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (c *Core) Hook(ctx context.Context, id uint64, opts models.HookOptions) err
isDocker := false
appKind := a.Kind(ctx)
//check if the app is docker/docker-compose or native
if appKind == utils.Docker || appKind == utils.DockerCompose {
if utils.IsDockerKind(appKind) {
isDocker = true
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/service/record/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,8 @@ func (r *Recorder) ReRecord(ctx context.Context, appID uint64) error {
return nil

}
cmdType := utils.FindDockerCmd(r.config.Command)
if cmdType == utils.Docker || cmdType == utils.DockerCompose {
cmdType := utils.CmdType(r.config.CommandType)
if utils.IsDockerKind(cmdType) {
host = r.config.ContainerName
}

Expand All @@ -360,7 +360,7 @@ func (r *Recorder) ReRecord(ctx context.Context, appID uint64) error {

allTestCasesRecorded := true
for _, tc := range tcs {
if cmdType == utils.Docker || cmdType == utils.DockerCompose {
if utils.IsDockerKind(cmdType) {

userIP, err := r.instrumentation.GetContainerIP(ctx, appID)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions pkg/service/replay/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,9 @@ func (r *Replayer) RunTestSet(ctx context.Context, testSetID string, testRunID s
return models.TestSetStatusUserAbort, context.Canceled
}

cmdType := utils.FindDockerCmd(r.config.Command)
cmdType := utils.CmdType(r.config.CommandType)
var userIP string
if cmdType == utils.Docker || cmdType == utils.DockerCompose {
if utils.IsDockerKind(cmdType) {
userIP, err = r.instrumentation.GetContainerIP(ctx, appID)
if err != nil {
return models.TestSetStatusFailed, err
Expand Down Expand Up @@ -410,7 +410,7 @@ func (r *Replayer) RunTestSet(ctx context.Context, testSetID string, testRunID s

started := time.Now().UTC()

if cmdType == utils.Docker || cmdType == utils.DockerCompose {
if utils.IsDockerKind(cmdType) {

testCase.HTTPReq.URL, err = utils.ReplaceHostToIP(testCase.HTTPReq.URL, userIP)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion utils/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func StartInDocker(ctx context.Context, logger *zap.Logger, conf *config.Config)
// If it does, then we would run the docker version of keploy and
// pass the command and control to it.
cmdType := FindDockerCmd(conf.Command)
if conf.InDocker || !(cmdType == Docker || cmdType == DockerCompose) {
if conf.InDocker || !(IsDockerKind(cmdType)) {
return nil
}
// pass the all the commands and args to the docker version of Keploy
Expand Down
22 changes: 17 additions & 5 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ func FindDockerCmd(cmd string) CmdType {
cmdLower := strings.TrimSpace(strings.ToLower(cmd))

// Define patterns for Docker and Docker Compose
dockerPatterns := []string{"docker", "sudo docker"}
dockerRunPatterns := []string{"docker run", "sudo docker run"}
dockerStartPatterns := []string{"docker start", "sudo docker start"}
dockerComposePatterns := []string{"docker-compose", "sudo docker-compose", "docker compose", "sudo docker compose"}

// Check for Docker Compose command patterns and file extensions
Expand All @@ -342,10 +343,16 @@ func FindDockerCmd(cmd string) CmdType {
return DockerCompose
}
}
// Check for Docker command patterns
for _, pattern := range dockerPatterns {
// Check for Docker start command patterns
for _, pattern := range dockerStartPatterns {
if strings.HasPrefix(cmdLower, pattern) {
return Docker
return DockerStart
}
}
// Check for Docker run command patterns
for _, pattern := range dockerRunPatterns {
if strings.HasPrefix(cmdLower, pattern) {
return DockerRun
}
}
return Native
Expand All @@ -355,7 +362,8 @@ type CmdType string

// CmdType constants
const (
Docker CmdType = "docker"
DockerRun CmdType = "docker-run"
DockerStart CmdType = "docker-start"
DockerCompose CmdType = "docker-compose"
Native CmdType = "native"
)
Expand Down Expand Up @@ -712,3 +720,7 @@ func EnsureRmBeforeName(cmd string) string {

return strings.Join(parts, " ")
}

func IsDockerKind(kind CmdType) bool {
return (kind == DockerRun || kind == DockerStart || kind == DockerCompose)
}

0 comments on commit 8f0abd6

Please sign in to comment.