*: implement task approval

This commit is contained in:
Simone Gotti 2019-04-08 17:29:57 +02:00
parent 81537f882f
commit 7d787c5f77
8 changed files with 91 additions and 10 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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"}},
},
},
},

View File

@ -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

View File

@ -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")

View File

@ -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 {

View File

@ -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

View File

@ -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)
}
}