Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b33dec9

Browse files
authored
feat: Add stage to build logs (#577)
* feat: Add stage to build logs This adds a stage property to logs, and refactors the job logs cliui. It also adds tests to the cliui for build logs! * Fix comments
1 parent eb18925 commit b33dec9

29 files changed

+604
-262
lines changed

cli/cliui/cliui.go

+4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ func ValidateNotEmpty(s string) error {
2323
// Styles compose visual elements of the UI!
2424
var Styles = struct {
2525
Bold,
26+
Checkmark,
2627
Code,
28+
Crossmark,
2729
Field,
2830
Keyword,
2931
Paragraph,
@@ -36,7 +38,9 @@ var Styles = struct {
3638
Wrap lipgloss.Style
3739
}{
3840
Bold: lipgloss.NewStyle().Bold(true),
41+
Checkmark: defaultStyles.Checkmark,
3942
Code: defaultStyles.Code,
43+
Crossmark: defaultStyles.Error.Copy().SetString("✘"),
4044
Field: defaultStyles.Code.Copy().Foreground(lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}),
4145
Keyword: defaultStyles.Keyword,
4246
Paragraph: defaultStyles.Paragraph,

cli/cliui/job.go

-157
This file was deleted.

cli/cliui/provisionerjob.go

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package cliui
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/signal"
8+
"sync"
9+
"time"
10+
11+
"github.com/google/uuid"
12+
"github.com/spf13/cobra"
13+
"golang.org/x/xerrors"
14+
15+
"github.com/coder/coder/coderd/database"
16+
"github.com/coder/coder/codersdk"
17+
)
18+
19+
func WorkspaceBuild(cmd *cobra.Command, client *codersdk.Client, build uuid.UUID, before time.Time) error {
20+
return ProvisionerJob(cmd, ProvisionerJobOptions{
21+
Fetch: func() (codersdk.ProvisionerJob, error) {
22+
build, err := client.WorkspaceBuild(cmd.Context(), build)
23+
return build.Job, err
24+
},
25+
Logs: func() (<-chan codersdk.ProvisionerJobLog, error) {
26+
return client.WorkspaceBuildLogsAfter(cmd.Context(), build, before)
27+
},
28+
})
29+
}
30+
31+
type ProvisionerJobOptions struct {
32+
Fetch func() (codersdk.ProvisionerJob, error)
33+
Cancel func() error
34+
Logs func() (<-chan codersdk.ProvisionerJobLog, error)
35+
36+
FetchInterval time.Duration
37+
// Verbose determines whether debug and trace logs will be shown.
38+
Verbose bool
39+
}
40+
41+
// ProvisionerJob renders a provisioner job with interactive cancellation.
42+
func ProvisionerJob(cmd *cobra.Command, opts ProvisionerJobOptions) error {
43+
if opts.FetchInterval == 0 {
44+
opts.FetchInterval = time.Second
45+
}
46+
47+
var (
48+
currentStage = "Queued"
49+
currentStageStartedAt = time.Now().UTC()
50+
didLogBetweenStage = false
51+
ctx, cancelFunc = context.WithCancel(cmd.Context())
52+
53+
errChan = make(chan error, 1)
54+
job codersdk.ProvisionerJob
55+
jobMutex sync.Mutex
56+
)
57+
defer cancelFunc()
58+
59+
printStage := func() {
60+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), Styles.Prompt.Render("⧗")+"%s\n", Styles.Field.Render(currentStage))
61+
}
62+
63+
updateStage := func(stage string, startedAt time.Time) {
64+
if currentStage != "" {
65+
prefix := ""
66+
if !didLogBetweenStage {
67+
prefix = "\033[1A\r"
68+
}
69+
mark := Styles.Checkmark
70+
if job.CompletedAt != nil && job.Status != codersdk.ProvisionerJobSucceeded {
71+
mark = Styles.Crossmark
72+
}
73+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), prefix+mark.String()+Styles.Placeholder.Render(" %s [%dms]")+"\n", currentStage, startedAt.Sub(currentStageStartedAt).Milliseconds())
74+
}
75+
if stage == "" {
76+
return
77+
}
78+
currentStage = stage
79+
currentStageStartedAt = startedAt
80+
didLogBetweenStage = false
81+
printStage()
82+
}
83+
84+
updateJob := func() {
85+
var err error
86+
jobMutex.Lock()
87+
defer jobMutex.Unlock()
88+
job, err = opts.Fetch()
89+
if err != nil {
90+
errChan <- xerrors.Errorf("fetch: %w", err)
91+
return
92+
}
93+
if job.StartedAt == nil {
94+
return
95+
}
96+
if currentStage != "Queued" {
97+
// If another stage is already running, there's no need
98+
// for us to notify the user we're running!
99+
return
100+
}
101+
updateStage("Running", *job.StartedAt)
102+
}
103+
updateJob()
104+
105+
if opts.Cancel != nil {
106+
// Handles ctrl+c to cancel a job.
107+
stopChan := make(chan os.Signal, 1)
108+
signal.Notify(stopChan, os.Interrupt)
109+
go func() {
110+
defer signal.Stop(stopChan)
111+
select {
112+
case <-ctx.Done():
113+
return
114+
case _, ok := <-stopChan:
115+
if !ok {
116+
return
117+
}
118+
}
119+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\033[2K\r\n"+Styles.FocusedPrompt.String()+Styles.Bold.Render("Gracefully canceling...")+"\n\n")
120+
err := opts.Cancel()
121+
if err != nil {
122+
errChan <- xerrors.Errorf("cancel: %w", err)
123+
return
124+
}
125+
updateJob()
126+
}()
127+
}
128+
129+
// The initial stage needs to print after the signal handler has been registered.
130+
printStage()
131+
132+
logs, err := opts.Logs()
133+
if err != nil {
134+
return xerrors.Errorf("logs: %w", err)
135+
}
136+
137+
ticker := time.NewTicker(opts.FetchInterval)
138+
for {
139+
select {
140+
case err = <-errChan:
141+
return err
142+
case <-ctx.Done():
143+
return ctx.Err()
144+
case <-ticker.C:
145+
updateJob()
146+
case log, ok := <-logs:
147+
if !ok {
148+
updateJob()
149+
jobMutex.Lock()
150+
if job.CompletedAt != nil {
151+
updateStage("", *job.CompletedAt)
152+
}
153+
switch job.Status {
154+
case codersdk.ProvisionerJobCanceled:
155+
jobMutex.Unlock()
156+
return Canceled
157+
case codersdk.ProvisionerJobSucceeded:
158+
jobMutex.Unlock()
159+
return nil
160+
case codersdk.ProvisionerJobFailed:
161+
}
162+
err = xerrors.New(job.Error)
163+
jobMutex.Unlock()
164+
return err
165+
}
166+
output := ""
167+
switch log.Level {
168+
case database.LogLevelTrace, database.LogLevelDebug:
169+
if !opts.Verbose {
170+
continue
171+
}
172+
output = Styles.Placeholder.Render(log.Output)
173+
case database.LogLevelError:
174+
output = defaultStyles.Error.Render(log.Output)
175+
case database.LogLevelWarn:
176+
output = Styles.Warn.Render(log.Output)
177+
case database.LogLevelInfo:
178+
output = log.Output
179+
}
180+
jobMutex.Lock()
181+
if log.Stage != currentStage && log.Stage != "" {
182+
updateStage(log.Stage, log.CreatedAt)
183+
jobMutex.Unlock()
184+
continue
185+
}
186+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", Styles.Placeholder.Render(" "), output)
187+
didLogBetweenStage = true
188+
jobMutex.Unlock()
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)