*: implement task approval
This commit is contained in:
parent
81537f882f
commit
7d787c5f77
|
@ -305,6 +305,7 @@ func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
Task string `yaml:"task"`
|
||||
Depends []interface{} `yaml:"depends"`
|
||||
IgnoreFailure bool `yaml:"ignore_failure"`
|
||||
Approval bool `yaml:"approval"`
|
||||
When *when `yaml:"when"`
|
||||
}
|
||||
|
||||
|
@ -317,6 +318,7 @@ func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
e.Name = te.Name
|
||||
e.Task = te.Task
|
||||
e.IgnoreFailure = te.IgnoreFailure
|
||||
e.Approval = te.Approval
|
||||
|
||||
depends := make([]*Depend, len(te.Depends))
|
||||
for i, dependEntry := range te.Depends {
|
||||
|
|
|
@ -182,6 +182,7 @@ func GenRunConfig(uuid util.UUIDGenerator, c *config.Config, pipelineName string
|
|||
Steps: steps,
|
||||
IgnoreFailure: cpe.IgnoreFailure,
|
||||
Skip: !include,
|
||||
NeedsApproval: cpe.Approval,
|
||||
}
|
||||
|
||||
rc.Tasks[t.ID] = t
|
||||
|
|
|
@ -771,7 +771,7 @@ func TestGenRunConfig(t *testing.T) {
|
|||
Tag: &types.WhenConditions{Include: []types.WhenCondition{{Match: "v1.x"}, {Match: "v2.x"}}},
|
||||
Ref: &types.WhenConditions{
|
||||
Include: []types.WhenCondition{{Match: "master"}},
|
||||
Exclude: []types.WhenCondition{{Match: "/branch01/", Type: types.WhenConditionTypeRegExp}, {Match: "branch02"}},
|
||||
Exclude: []types.WhenCondition{{Match: "branch01", Type: types.WhenConditionTypeRegExp}, {Match: "branch02"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -70,6 +70,10 @@ type RunResponseTask struct {
|
|||
Level int `json:"level"`
|
||||
Depends []*rstypes.RunConfigTaskDepend `json:"depends"`
|
||||
|
||||
WaitingApproval bool `json:"waiting_approval"`
|
||||
Approved bool `json:"approved"`
|
||||
ApprovalAnnotations map[string]string `json:"approval_annotations"`
|
||||
|
||||
StartTime *time.Time `json:"start_time"`
|
||||
EndTime *time.Time `json:"end_time"`
|
||||
}
|
||||
|
@ -79,6 +83,10 @@ type RunTaskResponse struct {
|
|||
Name string `json:"name"`
|
||||
Status rstypes.RunTaskStatus `json:"status"`
|
||||
|
||||
WaitingApproval bool `json:"waiting_approval"`
|
||||
Approved bool `json:"approved"`
|
||||
ApprovalAnnotations map[string]string `json:"approval_annotations"`
|
||||
|
||||
SetupStep *RunTaskResponseSetupStep `json:"setup_step"`
|
||||
Steps []*RunTaskResponseStep `json:"steps"`
|
||||
|
||||
|
@ -139,6 +147,10 @@ func createRunResponseTask(r *rstypes.Run, rt *rstypes.RunTask, rct *rstypes.Run
|
|||
StartTime: rt.StartTime,
|
||||
EndTime: rt.EndTime,
|
||||
|
||||
WaitingApproval: rt.WaitingApproval,
|
||||
Approved: rt.Approved,
|
||||
ApprovalAnnotations: rt.ApprovalAnnotations,
|
||||
|
||||
Level: rct.Level,
|
||||
Depends: rct.Depends,
|
||||
}
|
||||
|
@ -151,6 +163,11 @@ func createRunTaskResponse(rt *rstypes.RunTask, rct *rstypes.RunConfigTask) *Run
|
|||
ID: rt.ID,
|
||||
Name: rct.Name,
|
||||
Status: rt.Status,
|
||||
|
||||
WaitingApproval: rt.WaitingApproval,
|
||||
Approved: rt.Approved,
|
||||
ApprovalAnnotations: rt.ApprovalAnnotations,
|
||||
|
||||
Steps: make([]*RunTaskResponseStep, len(rt.Steps)),
|
||||
|
||||
StartTime: rt.StartTime,
|
||||
|
@ -390,12 +407,12 @@ func (h *RunActionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
switch req.ActionType {
|
||||
case RunActionTypeRestart:
|
||||
req := &rsapi.RunCreateRequest{
|
||||
rsreq := &rsapi.RunCreateRequest{
|
||||
RunID: runID,
|
||||
FromStart: req.FromStart,
|
||||
}
|
||||
|
||||
resp, err := h.runserviceClient.CreateRun(ctx, req)
|
||||
resp, err := h.runserviceClient.CreateRun(ctx, rsreq)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
|
@ -407,11 +424,11 @@ func (h *RunActionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
case RunActionTypeStop:
|
||||
req := &rsapi.RunActionsRequest{
|
||||
rsreq := &rsapi.RunActionsRequest{
|
||||
ActionType: rsapi.RunActionTypeStop,
|
||||
}
|
||||
|
||||
resp, err := h.runserviceClient.RunActions(ctx, runID, req)
|
||||
resp, err := h.runserviceClient.RunActions(ctx, runID, rsreq)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
|
@ -424,6 +441,62 @@ func (h *RunActionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
type RunTaskActionType string
|
||||
|
||||
const (
|
||||
RunTaskActionTypeApprove RunTaskActionType = "approve"
|
||||
)
|
||||
|
||||
type RunTaskActionsRequest struct {
|
||||
ActionType RunTaskActionType `json:"action_type"`
|
||||
ApprovalAnnotations map[string]string `json:"approval_annotations,omitempty"`
|
||||
}
|
||||
|
||||
type RunTaskActionsHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
runserviceClient *rsapi.Client
|
||||
}
|
||||
|
||||
func NewRunTaskActionsHandler(logger *zap.Logger, runserviceClient *rsapi.Client) *RunTaskActionsHandler {
|
||||
return &RunTaskActionsHandler{log: logger.Sugar(), runserviceClient: runserviceClient}
|
||||
}
|
||||
|
||||
func (h *RunTaskActionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vars := mux.Vars(r)
|
||||
runID := vars["runid"]
|
||||
taskID := vars["taskid"]
|
||||
|
||||
var req RunTaskActionsRequest
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
switch req.ActionType {
|
||||
case RunTaskActionTypeApprove:
|
||||
rsreq := &rsapi.RunTaskActionsRequest{
|
||||
ActionType: rsapi.RunTaskActionTypeApprove,
|
||||
ApprovalAnnotations: req.ApprovalAnnotations,
|
||||
}
|
||||
|
||||
resp, err := h.runserviceClient.RunTaskActions(ctx, runID, taskID, rsreq)
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
h.log.Errorf("err: %+v", err)
|
||||
httpError(w, err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type LogsHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
runserviceClient *rsapi.Client
|
||||
|
|
|
@ -191,6 +191,7 @@ func (g *Gateway) Run(ctx context.Context) error {
|
|||
runsHandler := api.NewRunsHandler(logger, g.runserviceClient)
|
||||
runtaskHandler := api.NewRuntaskHandler(logger, g.runserviceClient)
|
||||
runActionsHandler := api.NewRunActionsHandler(logger, g.runserviceClient)
|
||||
runTaskActionsHandler := api.NewRunTaskActionsHandler(logger, g.runserviceClient)
|
||||
|
||||
logsHandler := api.NewLogsHandler(logger, g.runserviceClient)
|
||||
|
||||
|
@ -263,6 +264,7 @@ func (g *Gateway) Run(ctx context.Context) error {
|
|||
apirouter.Handle("/runs/{runid}", authForcedHandler(runHandler)).Methods("GET")
|
||||
apirouter.Handle("/runs/{runid}/actions", authForcedHandler(runActionsHandler)).Methods("PUT")
|
||||
apirouter.Handle("/runs/{runid}/tasks/{taskid}", authForcedHandler(runtaskHandler)).Methods("GET")
|
||||
apirouter.Handle("/runs/{runid}/tasks/{taskid}/actions", runTaskActionsHandler).Methods("PUT")
|
||||
apirouter.Handle("/runs", authForcedHandler(runsHandler)).Methods("GET")
|
||||
|
||||
router.Handle("/login", loginUserHandler).Methods("POST")
|
||||
|
|
|
@ -585,7 +585,6 @@ func (h *RunTaskActionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
|
|||
runID := vars["runid"]
|
||||
taskID := vars["taskid"]
|
||||
|
||||
// TODO(sgotti) Check authorized call from client
|
||||
var req RunTaskActionsRequest
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&req); err != nil {
|
||||
|
|
|
@ -421,10 +421,11 @@ func (s *CommandHandler) ApproveRunTask(ctx context.Context, req *RunTaskApprove
|
|||
return errors.Errorf("run %q, task %q is not in waiting approval state", r.ID, req.TaskID)
|
||||
}
|
||||
|
||||
if !task.Approved {
|
||||
if task.Approved {
|
||||
return errors.Errorf("run %q, task %q is already approved", r.ID, req.TaskID)
|
||||
}
|
||||
|
||||
task.WaitingApproval = false
|
||||
task.Approved = true
|
||||
task.ApprovalAnnotations = req.ApprovalAnnotations
|
||||
|
||||
|
|
|
@ -117,9 +117,12 @@ func (s *Scheduler) advanceRunTasks(ctx context.Context, r *types.Run) error {
|
|||
}
|
||||
|
||||
if canRun {
|
||||
if !rt.WaitingApproval && rct.NeedsApproval {
|
||||
// now that the task can run set it to waiting approval if needed
|
||||
if rct.NeedsApproval && !rt.WaitingApproval && !rt.Approved {
|
||||
rt.WaitingApproval = true
|
||||
} else {
|
||||
}
|
||||
// Run only if approved if needed
|
||||
if !rct.NeedsApproval || (rct.NeedsApproval && rt.Approved) {
|
||||
tasksToRun = append(tasksToRun, rt)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue