Skip to content
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

kie-issues#1647 [kn-plugin-workflow] Executing kn workflow run creates the container in the background #2778

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 71 additions & 0 deletions packages/kn-plugin-workflow/e2e-tests/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
package e2e_tests

import (
"bufio"
"bytes"
"fmt"
"io"
Expand All @@ -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"
Expand Down Expand Up @@ -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...)
Expand All @@ -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)
Expand Down
18 changes: 17 additions & 1 deletion packages/kn-plugin-workflow/e2e-tests/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -80,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)
Expand All @@ -99,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)
}()

Expand All @@ -120,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
}
47 changes: 44 additions & 3 deletions packages/kn-plugin-workflow/pkg/command/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package command

import (
"bufio"
"fmt"
"os"
"sync"
Expand All @@ -34,8 +35,12 @@ import (
type RunCmdConfig struct {
PortMapping string
OpenDevUI bool
StopContainerOnUserInput bool
}

const StopContainerMsg = "Press any key to stop the container"


func NewRunCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "run",
Expand All @@ -56,9 +61,13 @@ 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"),
PreRunE: common.BindEnv("port", "open-dev-ui", "stop-container-on-user-input"),
}

cmd.RunE = func(cmd *cobra.Command, args []string) error {
Expand All @@ -67,6 +76,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
Expand All @@ -92,8 +102,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
}
Expand Down Expand Up @@ -137,6 +148,36 @@ func runSWFProjectDevMode(containerTool string, cfg RunCmdConfig) (err error) {
pollInterval := 5 * time.Second
common.ReadyCheck(readyCheckURL, pollInterval, cfg.PortMapping, cfg.OpenDevUI)

if cfg.StopContainerOnUserInput {
if err := stopContainer(containerTool); err != nil {
return err
}
}

wg.Wait()
return err
}

func stopContainer(containerTool string) error {
fmt.Println(StopContainerMsg)

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
}


44 changes: 44 additions & 0 deletions packages/kn-plugin-workflow/pkg/common/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Loading