forked from travis-ci/worker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
step_run_script.go
129 lines (106 loc) · 4.42 KB
/
step_run_script.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package worker
import (
"fmt"
"time"
gocontext "context"
"github.com/mitchellh/multistep"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/travis-ci/worker/backend"
"github.com/travis-ci/worker/context"
)
type runScriptReturn struct {
result *backend.RunResult
err error
}
type stepRunScript struct {
logTimeout time.Duration
hardTimeout time.Duration
skipShutdownOnLogTimeout bool
}
func (s *stepRunScript) Run(state multistep.StateBag) multistep.StepAction {
procCtx := state.Get("procCtx").(gocontext.Context)
ctx := state.Get("ctx").(gocontext.Context)
buildJob := state.Get("buildJob").(Job)
instance := state.Get("instance").(backend.Instance)
logWriter := state.Get("logWriter").(LogWriter)
cancelChan := state.Get("cancelChan").(<-chan struct{})
logger := context.LoggerFromContext(ctx).WithField("self", "step_run_script")
logger.Info("running script")
defer logger.Info("finished script")
resultChan := make(chan runScriptReturn, 1)
go func() {
result, err := instance.RunScript(ctx, logWriter)
resultChan <- runScriptReturn{
result: result,
err: err,
}
}()
select {
case r := <-resultChan:
if errors.Cause(r.err) == ErrWrotePastMaxLogLength {
logger.Info("wrote past maximum log length")
s.writeLogAndFinishWithState(procCtx, ctx, logWriter, buildJob, FinishStateErrored, "\n\nThe job exceeded the maximum log length, and has been terminated.\n\n")
return multistep.ActionHalt
}
// We need to check for this since it's possible that the RunScript
// implementation returns with the error too quickly for the ctx.Done()
// case branch below to catch it.
if errors.Cause(r.err) == gocontext.DeadlineExceeded {
logger.Info("hard timeout exceeded, terminating")
s.writeLogAndFinishWithState(procCtx, ctx, logWriter, buildJob, FinishStateErrored, "\n\nThe job exceeded the maximum time limit for jobs, and has been terminated.\n\n")
return multistep.ActionHalt
}
if r.err != nil {
if !r.result.Completed {
logger.WithFields(logrus.Fields{
"err": r.err,
"completed": r.result.Completed,
}).Error("couldn't run script, attempting requeue")
context.CaptureError(ctx, r.err)
err := buildJob.Requeue(procCtx)
if err != nil {
logger.WithField("err", err).Error("couldn't requeue job")
}
} else {
logger.WithField("err", r.err).WithField("completed", r.result.Completed).Error("couldn't run script")
err := buildJob.Finish(procCtx, FinishStateErrored)
if err != nil {
logger.WithField("err", err).Error("couldn't mark job errored")
}
}
return multistep.ActionHalt
}
state.Put("scriptResult", r.result)
return multistep.ActionContinue
case <-ctx.Done():
if ctx.Err() == gocontext.DeadlineExceeded {
logger.Info("hard timeout exceeded, terminating")
s.writeLogAndFinishWithState(procCtx, ctx, logWriter, buildJob, FinishStateErrored, "\n\nThe job exceeded the maximum time limit for jobs, and has been terminated.\n\n")
return multistep.ActionHalt
}
logger.Info("context was cancelled, stopping job")
return multistep.ActionHalt
case <-cancelChan:
s.writeLogAndFinishWithState(procCtx, ctx, logWriter, buildJob, FinishStateCancelled, "\n\nDone: Job Cancelled\n\n")
return multistep.ActionHalt
case <-logWriter.Timeout():
s.writeLogAndFinishWithState(procCtx, ctx, logWriter, buildJob, FinishStateErrored, fmt.Sprintf("\n\nNo output has been received in the last %v, this potentially indicates a stalled build or something wrong with the build itself.\nCheck the details on how to adjust your build configuration on: https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received\n\nThe build has been terminated\n\n", s.logTimeout))
if s.skipShutdownOnLogTimeout {
state.Put("skipShutdown", true)
}
return multistep.ActionHalt
}
}
func (s *stepRunScript) writeLogAndFinishWithState(procCtx, ctx gocontext.Context, logWriter LogWriter, buildJob Job, state FinishState, logMessage string) {
logger := context.LoggerFromContext(ctx).WithField("self", "step_run_script")
_, err := logWriter.WriteAndClose([]byte(logMessage))
if err != nil {
logger.WithField("err", err).Error("couldn't write final log message")
}
err = buildJob.Finish(procCtx, state)
if err != nil {
logger.WithField("err", err).WithField("state", state).Error("couldn't update job state")
}
}
func (s *stepRunScript) Cleanup(state multistep.StateBag) {}