*: 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"`
|
Task string `yaml:"task"`
|
||||||
Depends []interface{} `yaml:"depends"`
|
Depends []interface{} `yaml:"depends"`
|
||||||
IgnoreFailure bool `yaml:"ignore_failure"`
|
IgnoreFailure bool `yaml:"ignore_failure"`
|
||||||
|
Approval bool `yaml:"approval"`
|
||||||
When *when `yaml:"when"`
|
When *when `yaml:"when"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,6 +318,7 @@ func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
e.Name = te.Name
|
e.Name = te.Name
|
||||||
e.Task = te.Task
|
e.Task = te.Task
|
||||||
e.IgnoreFailure = te.IgnoreFailure
|
e.IgnoreFailure = te.IgnoreFailure
|
||||||
|
e.Approval = te.Approval
|
||||||
|
|
||||||
depends := make([]*Depend, len(te.Depends))
|
depends := make([]*Depend, len(te.Depends))
|
||||||
for i, dependEntry := range te.Depends {
|
for i, dependEntry := range te.Depends {
|
||||||
|
|
|
@ -182,6 +182,7 @@ func GenRunConfig(uuid util.UUIDGenerator, c *config.Config, pipelineName string
|
||||||
Steps: steps,
|
Steps: steps,
|
||||||
IgnoreFailure: cpe.IgnoreFailure,
|
IgnoreFailure: cpe.IgnoreFailure,
|
||||||
Skip: !include,
|
Skip: !include,
|
||||||
|
NeedsApproval: cpe.Approval,
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.Tasks[t.ID] = t
|
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"}}},
|
Tag: &types.WhenConditions{Include: []types.WhenCondition{{Match: "v1.x"}, {Match: "v2.x"}}},
|
||||||
Ref: &types.WhenConditions{
|
Ref: &types.WhenConditions{
|
||||||
Include: []types.WhenCondition{{Match: "master"}},
|
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"`
|
Level int `json:"level"`
|
||||||
Depends []*rstypes.RunConfigTaskDepend `json:"depends"`
|
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"`
|
StartTime *time.Time `json:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time"`
|
EndTime *time.Time `json:"end_time"`
|
||||||
}
|
}
|
||||||
|
@ -79,6 +83,10 @@ type RunTaskResponse struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status rstypes.RunTaskStatus `json:"status"`
|
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"`
|
SetupStep *RunTaskResponseSetupStep `json:"setup_step"`
|
||||||
Steps []*RunTaskResponseStep `json:"steps"`
|
Steps []*RunTaskResponseStep `json:"steps"`
|
||||||
|
|
||||||
|
@ -139,6 +147,10 @@ func createRunResponseTask(r *rstypes.Run, rt *rstypes.RunTask, rct *rstypes.Run
|
||||||
StartTime: rt.StartTime,
|
StartTime: rt.StartTime,
|
||||||
EndTime: rt.EndTime,
|
EndTime: rt.EndTime,
|
||||||
|
|
||||||
|
WaitingApproval: rt.WaitingApproval,
|
||||||
|
Approved: rt.Approved,
|
||||||
|
ApprovalAnnotations: rt.ApprovalAnnotations,
|
||||||
|
|
||||||
Level: rct.Level,
|
Level: rct.Level,
|
||||||
Depends: rct.Depends,
|
Depends: rct.Depends,
|
||||||
}
|
}
|
||||||
|
@ -151,6 +163,11 @@ func createRunTaskResponse(rt *rstypes.RunTask, rct *rstypes.RunConfigTask) *Run
|
||||||
ID: rt.ID,
|
ID: rt.ID,
|
||||||
Name: rct.Name,
|
Name: rct.Name,
|
||||||
Status: rt.Status,
|
Status: rt.Status,
|
||||||
|
|
||||||
|
WaitingApproval: rt.WaitingApproval,
|
||||||
|
Approved: rt.Approved,
|
||||||
|
ApprovalAnnotations: rt.ApprovalAnnotations,
|
||||||
|
|
||||||
Steps: make([]*RunTaskResponseStep, len(rt.Steps)),
|
Steps: make([]*RunTaskResponseStep, len(rt.Steps)),
|
||||||
|
|
||||||
StartTime: rt.StartTime,
|
StartTime: rt.StartTime,
|
||||||
|
@ -390,12 +407,12 @@ func (h *RunActionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
switch req.ActionType {
|
switch req.ActionType {
|
||||||
case RunActionTypeRestart:
|
case RunActionTypeRestart:
|
||||||
req := &rsapi.RunCreateRequest{
|
rsreq := &rsapi.RunCreateRequest{
|
||||||
RunID: runID,
|
RunID: runID,
|
||||||
FromStart: req.FromStart,
|
FromStart: req.FromStart,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := h.runserviceClient.CreateRun(ctx, req)
|
resp, err := h.runserviceClient.CreateRun(ctx, rsreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||||
http.Error(w, err.Error(), 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:
|
case RunActionTypeStop:
|
||||||
req := &rsapi.RunActionsRequest{
|
rsreq := &rsapi.RunActionsRequest{
|
||||||
ActionType: rsapi.RunActionTypeStop,
|
ActionType: rsapi.RunActionTypeStop,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := h.runserviceClient.RunActions(ctx, runID, req)
|
resp, err := h.runserviceClient.RunActions(ctx, runID, rsreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||||
http.Error(w, err.Error(), 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 {
|
type LogsHandler struct {
|
||||||
log *zap.SugaredLogger
|
log *zap.SugaredLogger
|
||||||
runserviceClient *rsapi.Client
|
runserviceClient *rsapi.Client
|
||||||
|
|
|
@ -191,6 +191,7 @@ func (g *Gateway) Run(ctx context.Context) error {
|
||||||
runsHandler := api.NewRunsHandler(logger, g.runserviceClient)
|
runsHandler := api.NewRunsHandler(logger, g.runserviceClient)
|
||||||
runtaskHandler := api.NewRuntaskHandler(logger, g.runserviceClient)
|
runtaskHandler := api.NewRuntaskHandler(logger, g.runserviceClient)
|
||||||
runActionsHandler := api.NewRunActionsHandler(logger, g.runserviceClient)
|
runActionsHandler := api.NewRunActionsHandler(logger, g.runserviceClient)
|
||||||
|
runTaskActionsHandler := api.NewRunTaskActionsHandler(logger, g.runserviceClient)
|
||||||
|
|
||||||
logsHandler := api.NewLogsHandler(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}", authForcedHandler(runHandler)).Methods("GET")
|
||||||
apirouter.Handle("/runs/{runid}/actions", authForcedHandler(runActionsHandler)).Methods("PUT")
|
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}", authForcedHandler(runtaskHandler)).Methods("GET")
|
||||||
|
apirouter.Handle("/runs/{runid}/tasks/{taskid}/actions", runTaskActionsHandler).Methods("PUT")
|
||||||
apirouter.Handle("/runs", authForcedHandler(runsHandler)).Methods("GET")
|
apirouter.Handle("/runs", authForcedHandler(runsHandler)).Methods("GET")
|
||||||
|
|
||||||
router.Handle("/login", loginUserHandler).Methods("POST")
|
router.Handle("/login", loginUserHandler).Methods("POST")
|
||||||
|
|
|
@ -585,7 +585,6 @@ func (h *RunTaskActionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
|
||||||
runID := vars["runid"]
|
runID := vars["runid"]
|
||||||
taskID := vars["taskid"]
|
taskID := vars["taskid"]
|
||||||
|
|
||||||
// TODO(sgotti) Check authorized call from client
|
|
||||||
var req RunTaskActionsRequest
|
var req RunTaskActionsRequest
|
||||||
d := json.NewDecoder(r.Body)
|
d := json.NewDecoder(r.Body)
|
||||||
if err := d.Decode(&req); err != nil {
|
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)
|
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)
|
return errors.Errorf("run %q, task %q is already approved", r.ID, req.TaskID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task.WaitingApproval = false
|
||||||
task.Approved = true
|
task.Approved = true
|
||||||
task.ApprovalAnnotations = req.ApprovalAnnotations
|
task.ApprovalAnnotations = req.ApprovalAnnotations
|
||||||
|
|
||||||
|
|
|
@ -117,9 +117,12 @@ func (s *Scheduler) advanceRunTasks(ctx context.Context, r *types.Run) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if canRun {
|
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
|
rt.WaitingApproval = true
|
||||||
} else {
|
}
|
||||||
|
// Run only if approved if needed
|
||||||
|
if !rct.NeedsApproval || (rct.NeedsApproval && rt.Approved) {
|
||||||
tasksToRun = append(tasksToRun, rt)
|
tasksToRun = append(tasksToRun, rt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue