From 12649e2582bd2aaaca23eb9cbda0a3112e6b413f Mon Sep 17 00:00:00 2001 From: Dmitrii Tikhomirov Date: Sat, 30 Nov 2024 14:42:16 -0800 Subject: [PATCH 1/4] [kn-plugin-workflow] Executing kn workflow run creates the container in the background (cherry picked from commit 9dda6685455e3b3c8b748d46c3d4861e89133e3c) --- .../kn-plugin-workflow/pkg/command/run.go | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/kn-plugin-workflow/pkg/command/run.go b/packages/kn-plugin-workflow/pkg/command/run.go index 27ed3ae5e1c..ca0c462f789 100644 --- a/packages/kn-plugin-workflow/pkg/command/run.go +++ b/packages/kn-plugin-workflow/pkg/command/run.go @@ -20,6 +20,7 @@ package command import ( + "bufio" "fmt" "os" "sync" @@ -137,6 +138,34 @@ func runSWFProjectDevMode(containerTool string, cfg RunCmdConfig) (err error) { pollInterval := 5 * time.Second common.ReadyCheck(readyCheckURL, pollInterval, cfg.PortMapping, cfg.OpenDevUI) + if err := stopContainer(containerTool); err != nil { + return err + } + wg.Wait() return err } + +func stopContainer(containerTool string) error { + fmt.Println("Press ENTER to stop the container") + + reader := bufio.NewReader(os.Stdin) + + _, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("error reading from stdin: %w", err) + } + + fmt.Println("⏳ Stopping the container...") + + containerID, err := common.GetContainerID(containerTool) + if err != nil { + return err + } + if err := common.StopContainer(containerTool, containerID); err != nil { + return err + } + return nil +} + + From 8ea0a51b975484ab67725662c048ab159e5b271d Mon Sep 17 00:00:00 2001 From: Dmitrii Tikhomirov Date: Sat, 30 Nov 2024 20:36:47 -0800 Subject: [PATCH 2/4] fix tests --- .../kn-plugin-workflow/e2e-tests/run_test.go | 7 +++++-- packages/kn-plugin-workflow/pkg/command/run.go | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/kn-plugin-workflow/e2e-tests/run_test.go b/packages/kn-plugin-workflow/e2e-tests/run_test.go index f3f371b4b21..62305fde10c 100644 --- a/packages/kn-plugin-workflow/e2e-tests/run_test.go +++ b/packages/kn-plugin-workflow/e2e-tests/run_test.go @@ -44,8 +44,8 @@ type cfgTestInputRun struct { } var cfgTestInputRun_Success = []cfgTestInputRun{ - {input: command.RunCmdConfig{PortMapping: "8081", OpenDevUI: false}}, - {input: command.RunCmdConfig{}}, + {input: command.RunCmdConfig{PortMapping: "8081", OpenDevUI: false, StopContainerOnUserInput: false}}, + {input: command.RunCmdConfig{StopContainerOnUserInput: false}}, } func transformRunCmdCfgToArgs(cfg command.RunCmdConfig) []string { @@ -56,6 +56,9 @@ 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 } diff --git a/packages/kn-plugin-workflow/pkg/command/run.go b/packages/kn-plugin-workflow/pkg/command/run.go index ca0c462f789..b9218240e58 100644 --- a/packages/kn-plugin-workflow/pkg/command/run.go +++ b/packages/kn-plugin-workflow/pkg/command/run.go @@ -35,6 +35,7 @@ import ( type RunCmdConfig struct { PortMapping string OpenDevUI bool + StopContainerOnUserInput bool } func NewRunCommand() *cobra.Command { @@ -57,6 +58,10 @@ func NewRunCommand() *cobra.Command { # Disable automatic browser launch of SonataFlow Dev UI {{.Name}} run --open-dev-ui=false + + # Stop the container when the user presses any key + {{.Name}} run --stop-container-on-user-input=false + `, SuggestFor: []string{"rnu", "start"}, //nolint:misspell PreRunE: common.BindEnv("port", "open-dev-ui"), @@ -68,6 +73,7 @@ func NewRunCommand() *cobra.Command { cmd.Flags().StringP("port", "p", "8080", "Maps a different host port to the running container port.") cmd.Flags().Bool("open-dev-ui", true, "Disable automatic browser launch of SonataFlow Dev UI") + cmd.Flags().Bool("stop-container-on-user-input", true, "Stop the container when the user presses any key") cmd.SetHelpFunc(common.DefaultTemplatedHelp) return cmd @@ -93,8 +99,9 @@ func run() error { func runDevCmdConfig() (cfg RunCmdConfig, err error) { cfg = RunCmdConfig{ - PortMapping: viper.GetString("port"), - OpenDevUI: viper.GetBool("open-dev-ui"), + PortMapping: viper.GetString("port"), + OpenDevUI: viper.GetBool("open-dev-ui"), + StopContainerOnUserInput: viper.GetBool("stop-container-on-user-input"), } return } @@ -138,8 +145,10 @@ func runSWFProjectDevMode(containerTool string, cfg RunCmdConfig) (err error) { pollInterval := 5 * time.Second common.ReadyCheck(readyCheckURL, pollInterval, cfg.PortMapping, cfg.OpenDevUI) - if err := stopContainer(containerTool); err != nil { - return err + if cfg.StopContainerOnUserInput { + if err := stopContainer(containerTool); err != nil { + return err + } } wg.Wait() From fba0eb9c5c76bd15dd2d1f64128bca4ba2736fc9 Mon Sep 17 00:00:00 2001 From: Dmitrii Tikhomirov Date: Thu, 5 Dec 2024 09:40:19 -0800 Subject: [PATCH 3/4] tests --- .../e2e-tests/helper_test.go | 71 +++++++++++++++++++ .../kn-plugin-workflow/e2e-tests/run_test.go | 25 +++++-- .../kn-plugin-workflow/pkg/command/run.go | 7 +- .../pkg/common/containers.go | 44 ++++++++++++ 4 files changed, 139 insertions(+), 8 deletions(-) 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") +} From d5a7ca096187e557bdd9f9596b08a81f42e62ea0 Mon Sep 17 00:00:00 2001 From: Dmitrii Tikhomirov Date: Thu, 5 Dec 2024 15:44:20 -0800 Subject: [PATCH 4/4] retest this