Skip to content

feat: Add stage to build logs #577

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

Merged
merged 2 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Fix comments
  • Loading branch information
kylecarbs committed Mar 28, 2022
commit 13e303d076ddfcbfc0c3d5b7edabc421fb32276d
76 changes: 49 additions & 27 deletions cli/cliui/provisionerjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,34 @@ import (
"sync"
"time"

"github.com/google/uuid"
"github.com/spf13/cobra"
"golang.org/x/xerrors"

"github.com/coder/coder/coderd/database"
"github.com/coder/coder/codersdk"
)

func WorkspaceBuild(cmd *cobra.Command, client *codersdk.Client, build uuid.UUID, before time.Time) error {
return ProvisionerJob(cmd, ProvisionerJobOptions{
Fetch: func() (codersdk.ProvisionerJob, error) {
build, err := client.WorkspaceBuild(cmd.Context(), build)
return build.Job, err
},
Logs: func() (<-chan codersdk.ProvisionerJobLog, error) {
return client.WorkspaceBuildLogsAfter(cmd.Context(), build, before)
},
})
}

type ProvisionerJobOptions struct {
Fetch func() (codersdk.ProvisionerJob, error)
Cancel func() error
Logs func() (<-chan codersdk.ProvisionerJobLog, error)

FetchInterval time.Duration
// Verbose determines whether debug and trace logs will be shown.
Verbose bool
}

// ProvisionerJob renders a provisioner job with interactive cancellation.
Expand All @@ -35,7 +50,7 @@ func ProvisionerJob(cmd *cobra.Command, opts ProvisionerJobOptions) error {
didLogBetweenStage = false
ctx, cancelFunc = context.WithCancel(cmd.Context())

errChan = make(chan error)
errChan = make(chan error, 1)
job codersdk.ProvisionerJob
jobMutex sync.Mutex
)
Expand All @@ -44,7 +59,6 @@ func ProvisionerJob(cmd *cobra.Command, opts ProvisionerJobOptions) error {
printStage := func() {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), Styles.Prompt.Render("⧗")+"%s\n", Styles.Field.Render(currentStage))
}
printStage()

updateStage := func(stage string, startedAt time.Time) {
if currentStage != "" {
Expand Down Expand Up @@ -88,29 +102,32 @@ func ProvisionerJob(cmd *cobra.Command, opts ProvisionerJobOptions) error {
}
updateJob()

// Handles ctrl+c to cancel a job.
stopChan := make(chan os.Signal, 1)
defer signal.Stop(stopChan)
go func() {
if opts.Cancel != nil {
// Handles ctrl+c to cancel a job.
stopChan := make(chan os.Signal, 1)
signal.Notify(stopChan, os.Interrupt)
select {
case <-ctx.Done():
return
case _, ok := <-stopChan:
if !ok {
go func() {
defer signal.Stop(stopChan)
select {
case <-ctx.Done():
return
case _, ok := <-stopChan:
if !ok {
return
}
}
}
// Stop listening for signals so another one kills it!
signal.Stop(stopChan)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\033[2K\r\n"+Styles.FocusedPrompt.String()+Styles.Bold.Render("Gracefully canceling... wait for exit or data loss may occur!")+"\n\n")
err := opts.Cancel()
if err != nil {
errChan <- xerrors.Errorf("cancel: %w", err)
return
}
updateJob()
}()
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\033[2K\r\n"+Styles.FocusedPrompt.String()+Styles.Bold.Render("Gracefully canceling...")+"\n\n")
err := opts.Cancel()
if err != nil {
errChan <- xerrors.Errorf("cancel: %w", err)
return
}
updateJob()
}()
}

// The initial stage needs to print after the signal handler has been registered.
printStage()

logs, err := opts.Logs()
if err != nil {
Expand All @@ -128,8 +145,6 @@ func ProvisionerJob(cmd *cobra.Command, opts ProvisionerJobOptions) error {
updateJob()
case log, ok := <-logs:
if !ok {
// The logs stream will end when the job does,
// so it's safe to
updateJob()
jobMutex.Lock()
if job.CompletedAt != nil {
Expand All @@ -144,26 +159,33 @@ func ProvisionerJob(cmd *cobra.Command, opts ProvisionerJobOptions) error {
return nil
case codersdk.ProvisionerJobFailed:
}
err = xerrors.New(job.Error)
jobMutex.Unlock()
return xerrors.New(job.Error)
return err
}
output := ""
switch log.Level {
case database.LogLevelTrace, database.LogLevelDebug, database.LogLevelError:
case database.LogLevelTrace, database.LogLevelDebug:
if !opts.Verbose {
continue
}
output = Styles.Placeholder.Render(log.Output)
case database.LogLevelError:
output = defaultStyles.Error.Render(log.Output)
case database.LogLevelWarn:
output = Styles.Warn.Render(log.Output)
case database.LogLevelInfo:
output = log.Output
}
jobMutex.Lock()
if log.Stage != currentStage && log.Stage != "" {
jobMutex.Lock()
updateStage(log.Stage, log.CreatedAt)
jobMutex.Unlock()
continue
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", Styles.Placeholder.Render(" "), output)
didLogBetweenStage = true
jobMutex.Unlock()
}
}
}
8 changes: 4 additions & 4 deletions cli/cliui/provisionerjob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func TestProvisionerJob(t *testing.T) {
test.Job.Status = codersdk.ProvisionerJobSucceeded
now = database.Now()
test.Job.CompletedAt = &now
test.JobMutex.Unlock()
close(test.Logs)
test.JobMutex.Unlock()
}()
test.PTY.ExpectMatch("Queued")
test.Next <- struct{}{}
Expand Down Expand Up @@ -67,8 +67,8 @@ func TestProvisionerJob(t *testing.T) {
test.Job.Status = codersdk.ProvisionerJobSucceeded
now = database.Now()
test.Job.CompletedAt = &now
test.JobMutex.Unlock()
close(test.Logs)
test.JobMutex.Unlock()
}()
test.PTY.ExpectMatch("Queued")
test.Next <- struct{}{}
Expand All @@ -79,7 +79,7 @@ func TestProvisionerJob(t *testing.T) {
})

// This cannot be ran in parallel because it uses a signal.
//nolint:paralleltest
// nolint:paralleltest
t.Run("Cancel", func(t *testing.T) {
if runtime.GOOS == "windows" {
// Sending interrupt signal isn't supported on Windows!
Expand All @@ -98,8 +98,8 @@ func TestProvisionerJob(t *testing.T) {
test.Job.Status = codersdk.ProvisionerJobCanceled
now := database.Now()
test.Job.CompletedAt = &now
test.JobMutex.Unlock()
close(test.Logs)
test.JobMutex.Unlock()
}()
test.PTY.ExpectMatch("Queued")
test.Next <- struct{}{}
Expand Down
1 change: 1 addition & 0 deletions cmd/cliui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func main() {
go func() {
defer close(logs)
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
count := 0
for {
select {
Expand Down