Skip to content

Commit

Permalink
kie-issues#1647 [kn-plugin-workflow] Executing kn workflow run create…
Browse files Browse the repository at this point in the history
…s the container in the background (apache#2778)
  • Loading branch information
treblereel authored Jan 8, 2025
1 parent b3d2594 commit be87d4b
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 4 deletions.
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")
}

0 comments on commit be87d4b

Please sign in to comment.