diff --git a/packages/kn-plugin-workflow/e2e-tests/helper_test.go b/packages/kn-plugin-workflow/e2e-tests/helper_test.go index 392e959ee92..ad7f283e820 100644 --- a/packages/kn-plugin-workflow/e2e-tests/helper_test.go +++ b/packages/kn-plugin-workflow/e2e-tests/helper_test.go @@ -22,6 +22,7 @@ package e2e_tests import ( + "bufio" "bytes" "fmt" "io" @@ -32,6 +33,7 @@ import ( "syscall" "testing" + "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/command" "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/command/quarkus" "github.com/spf13/cobra" "github.com/stretchr/testify/require" @@ -63,6 +65,11 @@ func ExecuteKnWorkflowWithCmd(cmd *exec.Cmd, args ...string) (string, error) { return executeCommandWithOutput(cmd, args...) } +// ExecuteKnWorkflowWithCmdAndStopContainer executes the 'kn-workflow' CLI tool with the given arguments using the provided command and returns the containerID and possible error message. +func ExecuteKnWorkflowWithCmdAndStopContainer(cmd *exec.Cmd, args ...string) (string, error) { + return executeCommandWithOutputAndStopContainer(cmd, args...) +} + // ExecuteKnWorkflowQuarkusWithCmd executes the 'kn-workflow' CLI tool with 'quarkus' command with the given arguments using the provided command and returns the command's output and possible error message. func ExecuteKnWorkflowQuarkusWithCmd(cmd *exec.Cmd, args ...string) (string, error) { newArgs := append([]string{"quarkus"}, args...) @@ -89,6 +96,70 @@ func executeCommandWithOutput(cmd *exec.Cmd, args ...string) (string, error) { return stdout.String(), nil } +func executeCommandWithOutputAndStopContainer(cmd *exec.Cmd, args ...string) (string, error) { + cmd.Args = append([]string{cmd.Path}, args...) + + var containerId string + var stderr bytes.Buffer + + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return "", fmt.Errorf("failed to create stdout pipe: %w", err) + } + defer stdoutPipe.Close() + + stdinPipe, err := cmd.StdinPipe() + if err != nil { + return "", fmt.Errorf("failed to create stdin pipe: %w", err) + } + defer stdinPipe.Close() + + cmd.Stderr = &stderr + errorCh := make(chan error, 1) + + go func() { + defer close(errorCh) + scanner := bufio.NewScanner(stdoutPipe) + for scanner.Scan() { + line := scanner.Text() + + if strings.HasPrefix(line, "Created container with ID ") { + id, ok := strings.CutPrefix(line, "Created container with ID ") + if !ok || id == "" { + errorCh <- fmt.Errorf("failed to parse container ID from output: %q", line) + return + } + containerId = id + } + + if line == command.StopContainerMsg { + _, err := io.WriteString(stdinPipe, "any\n") + if err != nil { + errorCh <- fmt.Errorf("failed to write to stdin: %w", err) + return + } + } + } + + if err := scanner.Err(); err != nil { + errorCh <- fmt.Errorf("error reading from stdout: %w", err) + return + } + }() + + err = cmd.Run() + if err != nil { + return "", fmt.Errorf("command run error: %w (stderr: %s)", err, stderr.String()) + } + + readErr := <-errorCh + if readErr != nil { + return "", readErr + } + + return containerId, nil +} + // VerifyFileContent verifies that the content of a file matches the expected content. func VerifyFileContent(t *testing.T, filePath string, expected string) { actual, err := os.ReadFile(filePath) diff --git a/packages/kn-plugin-workflow/e2e-tests/run_test.go b/packages/kn-plugin-workflow/e2e-tests/run_test.go index 62305fde10c..2cabd4d35e7 100644 --- a/packages/kn-plugin-workflow/e2e-tests/run_test.go +++ b/packages/kn-plugin-workflow/e2e-tests/run_test.go @@ -32,6 +32,7 @@ import ( "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/command" "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -44,8 +45,8 @@ type cfgTestInputRun struct { } var cfgTestInputRun_Success = []cfgTestInputRun{ - {input: command.RunCmdConfig{PortMapping: "8081", OpenDevUI: false, StopContainerOnUserInput: false}}, - {input: command.RunCmdConfig{StopContainerOnUserInput: false}}, + {input: command.RunCmdConfig{PortMapping: "8081", OpenDevUI: false}}, + {input: command.RunCmdConfig{}}, } func transformRunCmdCfgToArgs(cfg command.RunCmdConfig) []string { @@ -56,9 +57,6 @@ func transformRunCmdCfgToArgs(cfg command.RunCmdConfig) []string { if cfg.PortMapping != "" { args = append(args, "--port", cfg.PortMapping) } - if cfg.StopContainerOnUserInput { - args = append(args, "--stop-container-on-user-input=false") - } return args } @@ -83,6 +81,7 @@ func TestRunCommand(t *testing.T) { func RunRunTest(t *testing.T, cfgTestInputPrepareCreate CfgTestInputCreate, test cfgTestInputRun) string { var err error + var containerId string // Create the project RunCreateTest(t, cfgTestInputPrepareCreate) @@ -102,7 +101,8 @@ func RunRunTest(t *testing.T, cfgTestInputPrepareCreate CfgTestInputCreate, test // Run the `run` command go func() { defer wg.Done() - _, err = ExecuteKnWorkflowWithCmd(cmd, transformRunCmdCfgToArgs(test.input)...) + containerId, err = ExecuteKnWorkflowWithCmdAndStopContainer(cmd, transformRunCmdCfgToArgs(test.input)...) + assert.NotNil(t, containerId, "Container ID is nil") require.Truef(t, err == nil || IsSignalInterrupt(err), "Expected nil error or signal interrupt, got %v", err) }() @@ -123,5 +123,18 @@ func RunRunTest(t *testing.T, cfgTestInputPrepareCreate CfgTestInputCreate, test wg.Wait() + stopped := make(chan bool) + t.Logf("Checking if container is stopped") + assert.NotNil(t, containerId, "Container ID is nil") + // Check if the container is stopped within a specified time limit. + go common.PollContainerStoppedCheck(containerId, pollInterval, stopped) + select { + case <-stopped: + fmt.Println("Project is stopped") + case <-time.After(timeout): + t.Fatalf("Test case timed out after %s. The project was not stopped within the specified time.", timeout) + cmd.Process.Signal(os.Interrupt) + } + return projectName } diff --git a/packages/kn-plugin-workflow/pkg/command/run.go b/packages/kn-plugin-workflow/pkg/command/run.go index b9218240e58..7cb275c632c 100644 --- a/packages/kn-plugin-workflow/pkg/command/run.go +++ b/packages/kn-plugin-workflow/pkg/command/run.go @@ -38,6 +38,9 @@ type RunCmdConfig struct { StopContainerOnUserInput bool } +const StopContainerMsg = "Press any key to stop the container" + + func NewRunCommand() *cobra.Command { cmd := &cobra.Command{ Use: "run", @@ -64,7 +67,7 @@ func NewRunCommand() *cobra.Command { `, SuggestFor: []string{"rnu", "start"}, //nolint:misspell - PreRunE: common.BindEnv("port", "open-dev-ui"), + PreRunE: common.BindEnv("port", "open-dev-ui", "stop-container-on-user-input"), } cmd.RunE = func(cmd *cobra.Command, args []string) error { @@ -156,7 +159,7 @@ func runSWFProjectDevMode(containerTool string, cfg RunCmdConfig) (err error) { } func stopContainer(containerTool string) error { - fmt.Println("Press ENTER to stop the container") + fmt.Println(StopContainerMsg) reader := bufio.NewReader(os.Stdin) diff --git a/packages/kn-plugin-workflow/pkg/common/containers.go b/packages/kn-plugin-workflow/pkg/common/containers.go index afb2482b69e..44d9e3ab883 100644 --- a/packages/kn-plugin-workflow/pkg/common/containers.go +++ b/packages/kn-plugin-workflow/pkg/common/containers.go @@ -389,3 +389,47 @@ func processOutputDuringContainerExecution(cli *client.Client, ctx context.Conte return nil } + + +func PollContainerStoppedCheck(containerID string, interval time.Duration, ready chan<- bool) { + for { + running, err := IsContainerRunning(containerID) + if err != nil { + fmt.Printf("Error checking if container %s is running: %s", containerID, err) + ready <- false + return + } + if !running { + ready <- true + return + } + time.Sleep(interval) + } +} + +func IsContainerRunning(containerID string) (bool, error) { + if errDocker := CheckDocker(); errDocker == nil { + cli, err := getDockerClient() + if err != nil { + return false, fmt.Errorf("unable to create docker client: %w", err) + } + containerJSON, err := cli.ContainerInspect(context.Background(), containerID) + if err != nil { + if client.IsErrNotFound(err) { + return false, nil + } + return false, fmt.Errorf("unable to inspect container %s with docker: %w", containerID, err) + } + return containerJSON.State.Running, nil + + } else if errPodman := CheckPodman(); errPodman == nil { + cmd := exec.Command("podman", "inspect", containerID, "--format", "{{.State.Running}}") + output, err := cmd.Output() + if err != nil { + return false, fmt.Errorf("unable to inspect container %s with podman: %w", containerID, err) + } + return strings.TrimSpace(string(output)) == "true", nil + } + + return false, fmt.Errorf("there is no docker or podman available") +}