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

feat: stream output for custom workflows #2261

Merged
merged 24 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f7cd127
Start threading job output to RunStepRunner
ascandella May 17, 2022
0200c62
Strip ANSI
ascandella May 17, 2022
22388bc
Fix lint
ascandella May 17, 2022
efb38a8
Use waitgroup to avoid test flakiness
ascandella May 17, 2022
831a8f6
Move waitgroup higher
ascandella May 17, 2022
589b43c
Add ANSI test and use strings.Builder
ascandella May 17, 2022
51cfc39
Fix lint
ascandella May 17, 2022
bdae433
Use errors.Wrap per style guide
ascandella May 17, 2022
f1fda9d
Create ShellCommandRunner to encapsulate streaming
ascandella May 17, 2022
a6567b5
WIP: shell command runner
ascandella May 17, 2022
ad6a941
Update signatures to propagate error finding version
ascandella May 17, 2022
4e894c4
Fix log output
ascandella May 17, 2022
d87edd8
Fix error checking
ascandella May 17, 2022
2b5e5e9
Merge branch 'master' into stream-all-output
ascandella May 17, 2022
c9bc5df
Fix accidental whitespace stripping
ascandella May 17, 2022
427c5de
Remove unused struct field
ascandella May 17, 2022
7fe7e3e
Fix error checking in terraform client
ascandella May 17, 2022
d39b3b5
Add unit tests to verify command output handler was called
ascandella May 17, 2022
795ac51
Remove err from async interface
ascandella May 18, 2022
58dd23e
Remove duplicative log now that shell command runner does it
ascandella May 18, 2022
dddd74e
Hide output in stream for env/multienv
ascandella May 18, 2022
86c5edd
Add comment explaining goroutines
ascandella May 18, 2022
bdcbcd0
Use printf for better macOS compatibility
ascandella May 18, 2022
45aacf3
Merge branch 'master' into stream-all-output
ascandella Jun 7, 2022
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
5 changes: 3 additions & 2 deletions server/controllers/events/events_controller_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -973,8 +973,9 @@ func setupE2E(t *testing.T, repoDir string) (events_controllers.VCSEventsControl
TerraformExecutor: terraformClient,
},
RunStepRunner: &runtime.RunStepRunner{
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTFVersion,
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTFVersion,
ProjectCmdOutputHandler: projectCmdOutputHandler,
},
WorkingDir: workingDir,
Webhooks: &mockWebhookSender{},
Expand Down
7 changes: 5 additions & 2 deletions server/core/runtime/env_step_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/runatlantis/atlantis/server/core/terraform/mocks"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/models"
jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks"
"github.com/runatlantis/atlantis/server/logging"

. "github.com/petergtz/pegomock"
Expand Down Expand Up @@ -40,9 +41,11 @@ func TestEnvStepRunner_Run(t *testing.T) {
tfClient := mocks.NewMockClient()
tfVersion, err := version.NewVersion("0.12.0")
Ok(t, err)
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
runStepRunner := runtime.RunStepRunner{
TerraformExecutor: tfClient,
DefaultTFVersion: tfVersion,
TerraformExecutor: tfClient,
DefaultTFVersion: tfVersion,
ProjectCmdOutputHandler: projectCmdOutputHandler,
}
envRunner := runtime.EnvStepRunner{
RunStepRunner: &runStepRunner,
Expand Down
7 changes: 5 additions & 2 deletions server/core/runtime/multienv_step_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/runatlantis/atlantis/server/core/terraform/mocks"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/models"
jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks"
"github.com/runatlantis/atlantis/server/logging"
. "github.com/runatlantis/atlantis/testing"
)
Expand All @@ -31,9 +32,11 @@ func TestMultiEnvStepRunner_Run(t *testing.T) {
tfClient := mocks.NewMockClient()
tfVersion, err := version.NewVersion("0.12.0")
Ok(t, err)
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
runStepRunner := runtime.RunStepRunner{
TerraformExecutor: tfClient,
DefaultTFVersion: tfVersion,
TerraformExecutor: tfClient,
DefaultTFVersion: tfVersion,
ProjectCmdOutputHandler: projectCmdOutputHandler,
}
multiEnvStepRunner := runtime.MultiEnvStepRunner{
RunStepRunner: &runStepRunner,
Expand Down
47 changes: 43 additions & 4 deletions server/core/runtime/run_step_runner.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
package runtime

import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"

"github.com/hashicorp/go-version"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/terraform/ansi"
"github.com/runatlantis/atlantis/server/jobs"
)

// RunStepRunner runs custom commands.
type RunStepRunner struct {
TerraformExecutor TerraformExec
DefaultTFVersion *version.Version
// TerraformBinDir is the directory where Atlantis downloads Terraform binaries.
TerraformBinDir string
TerraformBinDir string
ProjectCmdOutputHandler jobs.ProjectCommandOutputHandler
}

func (r *RunStepRunner) Run(ctx command.ProjectContext, command string, path string, envs map[string]string) (string, error) {
Expand Down Expand Up @@ -66,13 +73,45 @@ func (r *RunStepRunner) Run(ctx command.ProjectContext, command string, path str
finalEnvVars = append(finalEnvVars, fmt.Sprintf("%s=%s", key, val))
}
cmd.Env = finalEnvVars
out, err := cmd.CombinedOutput()
stdout, err := cmd.StdoutPipe()
if err != nil {
err = fmt.Errorf("%s: unable to create stdout buffer", err)
ctx.Log.Debug("error: %s", err)
return "", err
}
stderr, err := cmd.StderrPipe()
if err != nil {
err = fmt.Errorf("%s: unable to create stderr buffer", err)
ctx.Log.Debug("error: %s", err)
return "", err
}
cmd.Start()

var output bytes.Buffer
var mutex sync.Mutex

go r.streamOutput(ctx, stdout, &output, &mutex)
go r.streamOutput(ctx, stderr, &output, &mutex)

err = cmd.Wait()

if err != nil {
err = fmt.Errorf("%s: running %q in %q: \n%s", err, command, path, out)
err = fmt.Errorf("%s: running %q in %q: \n%s", err, command, path, output.String())
ctx.Log.Debug("error: %s", err)
return "", err
}
ctx.Log.Info("successfully ran %q in %q", command, path)
return string(out), nil
return ansi.Strip(output.String()), nil
}

func (r RunStepRunner) streamOutput(ctx command.ProjectContext, reader io.Reader, buffer *bytes.Buffer, mutex *sync.Mutex) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
r.ProjectCmdOutputHandler.Send(ctx, line, false)
mutex.Lock()
buffer.WriteString(line)
buffer.WriteString("\n")
mutex.Unlock()
}
}
13 changes: 8 additions & 5 deletions server/core/runtime/run_step_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/mocks/matchers"
"github.com/runatlantis/atlantis/server/events/models"
jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks"
"github.com/runatlantis/atlantis/server/logging"
. "github.com/runatlantis/atlantis/testing"
)
Expand All @@ -38,11 +39,11 @@ func TestRunStepRunner_Run(t *testing.T) {
},
{
Command: `printf \'your main.tf file does not provide default region.\\ncheck\'`,
ExpOut: `'your`,
ExpOut: "'your\n",
},
{
Command: `printf 'your main.tf file does not provide default region.\ncheck'`,
ExpOut: "your main.tf file does not provide default region.\ncheck",
ExpOut: "your main.tf file does not provide default region.\ncheck\n",
},
{
Command: "echo 'a",
Expand Down Expand Up @@ -104,11 +105,13 @@ func TestRunStepRunner_Run(t *testing.T) {
ThenReturn(nil)

logger := logging.NewNoopLogger(t)
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()

r := runtime.RunStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: defaultVersion,
TerraformBinDir: "/bin/dir",
TerraformExecutor: terraform,
DefaultTFVersion: defaultVersion,
TerraformBinDir: "/bin/dir",
ProjectCmdOutputHandler: projectCmdOutputHandler,
}
t.Run(c.Command, func(t *testing.T) {
tmpDir, cleanup := TempDir(t)
Expand Down
7 changes: 5 additions & 2 deletions server/events/project_command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
eventmocks "github.com/runatlantis/atlantis/server/events/mocks"
"github.com/runatlantis/atlantis/server/events/mocks/matchers"
"github.com/runatlantis/atlantis/server/events/models"
jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks"
"github.com/runatlantis/atlantis/server/logging"
. "github.com/runatlantis/atlantis/testing"
)
Expand Down Expand Up @@ -537,9 +538,11 @@ func TestDefaultProjectCommandRunner_RunEnvSteps(t *testing.T) {
tfClient := tmocks.NewMockClient()
tfVersion, err := version.NewVersion("0.12.0")
Ok(t, err)
projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
run := runtime.RunStepRunner{
TerraformExecutor: tfClient,
DefaultTFVersion: tfVersion,
TerraformExecutor: tfClient,
DefaultTFVersion: tfVersion,
ProjectCmdOutputHandler: projectCmdOutputHandler,
}
env := runtime.EnvStepRunner{
RunStepRunner: &run,
Expand Down
7 changes: 4 additions & 3 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,10 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
defaultTfVersion := terraformClient.DefaultVersion()
pendingPlanFinder := &events.DefaultPendingPlanFinder{}
runStepRunner := &runtime.RunStepRunner{
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTfVersion,
TerraformBinDir: terraformClient.TerraformBinDir(),
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTfVersion,
TerraformBinDir: terraformClient.TerraformBinDir(),
ProjectCmdOutputHandler: projectCmdOutputHandler,
}
drainer := &events.Drainer{}
statusController := &controllers.StatusController{
Expand Down