2018-06-05 23:47:32 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
2018-10-23 17:51:13 +00:00
|
|
|
"bytes"
|
2018-06-05 23:47:32 +00:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2018-10-23 17:51:13 +00:00
|
|
|
"errors"
|
2018-10-24 20:44:39 +00:00
|
|
|
"fmt"
|
2018-06-05 23:47:32 +00:00
|
|
|
"net/http"
|
2018-10-23 17:51:13 +00:00
|
|
|
"net/url"
|
|
|
|
"path"
|
2018-06-08 23:41:26 +00:00
|
|
|
"strconv"
|
2018-12-21 17:04:08 +00:00
|
|
|
"strings"
|
2018-10-25 23:43:11 +00:00
|
|
|
"time"
|
2018-06-05 23:47:32 +00:00
|
|
|
|
2019-01-08 00:37:16 +00:00
|
|
|
platform "github.com/influxdata/influxdb"
|
|
|
|
pcontext "github.com/influxdata/influxdb/context"
|
|
|
|
kerrors "github.com/influxdata/influxdb/kit/errors"
|
|
|
|
"github.com/influxdata/influxdb/task/backend"
|
2018-06-05 23:47:32 +00:00
|
|
|
"github.com/julienschmidt/httprouter"
|
2018-08-20 21:15:04 +00:00
|
|
|
"go.uber.org/zap"
|
2018-06-05 23:47:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// TaskHandler represents an HTTP API handler for tasks.
|
|
|
|
type TaskHandler struct {
|
|
|
|
*httprouter.Router
|
2018-10-01 16:53:06 +00:00
|
|
|
logger *zap.Logger
|
|
|
|
|
2018-10-09 18:29:04 +00:00
|
|
|
TaskService platform.TaskService
|
|
|
|
AuthorizationService platform.AuthorizationService
|
|
|
|
OrganizationService platform.OrganizationService
|
|
|
|
UserResourceMappingService platform.UserResourceMappingService
|
2018-12-03 16:07:08 +00:00
|
|
|
LabelService platform.LabelService
|
2018-11-17 15:54:21 +00:00
|
|
|
UserService platform.UserService
|
2018-06-05 23:47:32 +00:00
|
|
|
}
|
|
|
|
|
2018-10-09 18:23:42 +00:00
|
|
|
const (
|
|
|
|
tasksPath = "/api/v2/tasks"
|
2018-11-26 07:03:27 +00:00
|
|
|
tasksIDPath = "/api/v2/tasks/:id"
|
|
|
|
tasksIDLogsPath = "/api/v2/tasks/:id/logs"
|
|
|
|
tasksIDMembersPath = "/api/v2/tasks/:id/members"
|
|
|
|
tasksIDMembersIDPath = "/api/v2/tasks/:id/members/:userID"
|
|
|
|
tasksIDOwnersPath = "/api/v2/tasks/:id/owners"
|
|
|
|
tasksIDOwnersIDPath = "/api/v2/tasks/:id/owners/:userID"
|
|
|
|
tasksIDRunsPath = "/api/v2/tasks/:id/runs"
|
|
|
|
tasksIDRunsIDPath = "/api/v2/tasks/:id/runs/:rid"
|
|
|
|
tasksIDRunsIDLogsPath = "/api/v2/tasks/:id/runs/:rid/logs"
|
|
|
|
tasksIDRunsIDRetryPath = "/api/v2/tasks/:id/runs/:rid/retry"
|
|
|
|
tasksIDLabelsPath = "/api/v2/tasks/:id/labels"
|
2019-01-18 19:03:36 +00:00
|
|
|
tasksIDLabelsIDPath = "/api/v2/tasks/:id/labels/:lid"
|
2018-10-09 18:23:42 +00:00
|
|
|
)
|
|
|
|
|
2018-06-05 23:47:32 +00:00
|
|
|
// NewTaskHandler returns a new instance of TaskHandler.
|
2018-12-17 12:43:06 +00:00
|
|
|
func NewTaskHandler(mappingService platform.UserResourceMappingService, labelService platform.LabelService, logger *zap.Logger, userService platform.UserService) *TaskHandler {
|
2018-06-05 23:47:32 +00:00
|
|
|
h := &TaskHandler{
|
2018-10-24 20:44:39 +00:00
|
|
|
logger: logger,
|
2018-12-15 15:33:54 +00:00
|
|
|
Router: NewRouter(),
|
2018-10-24 20:44:39 +00:00
|
|
|
|
2018-10-16 21:49:35 +00:00
|
|
|
UserResourceMappingService: mappingService,
|
2018-12-03 16:07:08 +00:00
|
|
|
LabelService: labelService,
|
2018-12-17 12:43:06 +00:00
|
|
|
UserService: userService,
|
2018-06-05 23:47:32 +00:00
|
|
|
}
|
|
|
|
|
2018-10-09 18:23:42 +00:00
|
|
|
h.HandlerFunc("GET", tasksPath, h.handleGetTasks)
|
|
|
|
h.HandlerFunc("POST", tasksPath, h.handlePostTask)
|
2018-06-08 23:41:26 +00:00
|
|
|
|
2018-10-09 18:23:42 +00:00
|
|
|
h.HandlerFunc("GET", tasksIDPath, h.handleGetTask)
|
|
|
|
h.HandlerFunc("PATCH", tasksIDPath, h.handleUpdateTask)
|
|
|
|
h.HandlerFunc("DELETE", tasksIDPath, h.handleDeleteTask)
|
2018-06-08 23:41:26 +00:00
|
|
|
|
2018-10-09 18:23:42 +00:00
|
|
|
h.HandlerFunc("GET", tasksIDLogsPath, h.handleGetLogs)
|
|
|
|
h.HandlerFunc("GET", tasksIDRunsIDLogsPath, h.handleGetLogs)
|
2018-06-13 17:11:08 +00:00
|
|
|
|
2019-01-15 16:09:58 +00:00
|
|
|
h.HandlerFunc("POST", tasksIDMembersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.TasksResourceType, platform.Member))
|
|
|
|
h.HandlerFunc("GET", tasksIDMembersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.TasksResourceType, platform.Member))
|
2018-10-09 18:29:04 +00:00
|
|
|
h.HandlerFunc("DELETE", tasksIDMembersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Member))
|
|
|
|
|
2019-01-15 16:09:58 +00:00
|
|
|
h.HandlerFunc("POST", tasksIDOwnersPath, newPostMemberHandler(h.UserResourceMappingService, h.UserService, platform.TasksResourceType, platform.Owner))
|
|
|
|
h.HandlerFunc("GET", tasksIDOwnersPath, newGetMembersHandler(h.UserResourceMappingService, h.UserService, platform.TasksResourceType, platform.Owner))
|
2018-10-09 18:29:04 +00:00
|
|
|
h.HandlerFunc("DELETE", tasksIDOwnersIDPath, newDeleteMemberHandler(h.UserResourceMappingService, platform.Owner))
|
|
|
|
|
2018-10-09 18:23:42 +00:00
|
|
|
h.HandlerFunc("GET", tasksIDRunsPath, h.handleGetRuns)
|
2018-12-21 17:04:08 +00:00
|
|
|
h.HandlerFunc("POST", tasksIDRunsPath, h.handleForceRun)
|
2018-10-09 18:23:42 +00:00
|
|
|
h.HandlerFunc("GET", tasksIDRunsIDPath, h.handleGetRun)
|
|
|
|
h.HandlerFunc("POST", tasksIDRunsIDRetryPath, h.handleRetryRun)
|
2018-10-04 17:12:43 +00:00
|
|
|
h.HandlerFunc("DELETE", tasksIDRunsIDPath, h.handleCancelRun)
|
2018-06-08 23:41:26 +00:00
|
|
|
|
2018-12-03 16:07:08 +00:00
|
|
|
h.HandlerFunc("GET", tasksIDLabelsPath, newGetLabelsHandler(h.LabelService))
|
|
|
|
h.HandlerFunc("POST", tasksIDLabelsPath, newPostLabelHandler(h.LabelService))
|
2019-01-18 19:03:36 +00:00
|
|
|
h.HandlerFunc("DELETE", tasksIDLabelsIDPath, newDeleteLabelHandler(h.LabelService))
|
2018-12-03 16:07:08 +00:00
|
|
|
|
2018-06-05 23:47:32 +00:00
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
type taskResponse struct {
|
2019-01-02 19:17:28 +00:00
|
|
|
Links map[string]string `json:"links"`
|
|
|
|
Labels []platform.Label `json:"labels"`
|
2018-11-20 10:47:53 +00:00
|
|
|
platform.Task
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
|
|
|
|
2019-01-02 19:17:28 +00:00
|
|
|
func newTaskResponse(t platform.Task, labels []*platform.Label) taskResponse {
|
|
|
|
response := taskResponse{
|
2018-10-24 20:44:39 +00:00
|
|
|
Links: map[string]string{
|
|
|
|
"self": fmt.Sprintf("/api/v2/tasks/%s", t.ID),
|
|
|
|
"members": fmt.Sprintf("/api/v2/tasks/%s/members", t.ID),
|
|
|
|
"owners": fmt.Sprintf("/api/v2/tasks/%s/owners", t.ID),
|
2019-01-02 19:17:28 +00:00
|
|
|
"labels": fmt.Sprintf("/api/v2/tasks/%s/labels", t.ID),
|
2018-10-24 20:44:39 +00:00
|
|
|
"runs": fmt.Sprintf("/api/v2/tasks/%s/runs", t.ID),
|
2018-11-20 10:47:53 +00:00
|
|
|
"logs": fmt.Sprintf("/api/v2/tasks/%s/logs", t.ID),
|
2018-10-24 20:44:39 +00:00
|
|
|
},
|
2019-01-02 19:17:28 +00:00
|
|
|
Task: t,
|
|
|
|
Labels: []platform.Label{},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, l := range labels {
|
|
|
|
response.Labels = append(response.Labels, *l)
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
2019-01-02 19:17:28 +00:00
|
|
|
|
|
|
|
return response
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type tasksResponse struct {
|
|
|
|
Links map[string]string `json:"links"`
|
2018-11-20 10:47:53 +00:00
|
|
|
Tasks []taskResponse `json:"tasks"`
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
|
|
|
|
2019-01-02 19:17:28 +00:00
|
|
|
func newTasksResponse(ctx context.Context, ts []*platform.Task, labelService platform.LabelService) tasksResponse {
|
2018-11-20 10:47:53 +00:00
|
|
|
// TODO: impl paging links
|
|
|
|
/*
|
2018-11-21 04:45:50 +00:00
|
|
|
In swagger, paging links are embedded in a map, like this:
|
|
|
|
"links": {
|
|
|
|
"next": {
|
|
|
|
"href": "string"
|
|
|
|
},
|
|
|
|
"self": {
|
|
|
|
"href": "string"
|
|
|
|
},
|
|
|
|
"prev": {
|
|
|
|
"href": "string"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
But in http services (auth, org, bucket...), links are flat:
|
|
|
|
"links": {
|
|
|
|
"self": "string"
|
|
|
|
}
|
|
|
|
|
|
|
|
Them need to be unified.
|
2018-11-20 10:47:53 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
rs := tasksResponse{
|
2018-10-24 20:44:39 +00:00
|
|
|
Links: map[string]string{
|
|
|
|
"self": tasksPath,
|
|
|
|
},
|
2018-11-20 10:47:53 +00:00
|
|
|
Tasks: make([]taskResponse, len(ts)),
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
2018-11-20 10:47:53 +00:00
|
|
|
|
2018-11-28 22:52:11 +00:00
|
|
|
for i := range ts {
|
2019-01-18 19:03:36 +00:00
|
|
|
labels, _ := labelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: ts[i].ID})
|
2019-01-02 19:17:28 +00:00
|
|
|
rs.Tasks[i] = newTaskResponse(*ts[i], labels)
|
2018-11-20 10:47:53 +00:00
|
|
|
}
|
|
|
|
return rs
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type runResponse struct {
|
2018-11-20 21:53:52 +00:00
|
|
|
Links map[string]string `json:"links,omitempty"`
|
2018-12-04 23:10:03 +00:00
|
|
|
platform.Run
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newRunResponse(r platform.Run) runResponse {
|
|
|
|
return runResponse{
|
|
|
|
Links: map[string]string{
|
|
|
|
"self": fmt.Sprintf("/api/v2/tasks/%s/runs/%s", r.TaskID, r.ID),
|
|
|
|
"task": fmt.Sprintf("/api/v2/tasks/%s", r.TaskID),
|
|
|
|
"logs": fmt.Sprintf("/api/v2/tasks/%s/runs/%s/logs", r.TaskID, r.ID),
|
|
|
|
"retry": fmt.Sprintf("/api/v2/tasks/%s/runs/%s/retry", r.TaskID, r.ID),
|
|
|
|
},
|
|
|
|
Run: r,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type runsResponse struct {
|
|
|
|
Links map[string]string `json:"links"`
|
2018-12-04 23:10:03 +00:00
|
|
|
Runs []*runResponse `json:"runs"`
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newRunsResponse(rs []*platform.Run, taskID platform.ID) runsResponse {
|
2018-12-04 23:10:03 +00:00
|
|
|
r := runsResponse{
|
2018-10-24 20:44:39 +00:00
|
|
|
Links: map[string]string{
|
|
|
|
"self": fmt.Sprintf("/api/v2/tasks/%s/runs", taskID),
|
|
|
|
"task": fmt.Sprintf("/api/v2/tasks/%s", taskID),
|
|
|
|
},
|
2018-12-04 23:10:03 +00:00
|
|
|
Runs: make([]*runResponse, len(rs)),
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
2018-12-04 23:10:03 +00:00
|
|
|
|
|
|
|
for i := range rs {
|
|
|
|
rs := newRunResponse(*rs[i])
|
|
|
|
r.Runs[i] = &rs
|
|
|
|
}
|
|
|
|
return r
|
2018-10-24 20:44:39 +00:00
|
|
|
}
|
|
|
|
|
2018-06-07 18:45:43 +00:00
|
|
|
func (h *TaskHandler) handleGetTasks(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeGetTasksRequest(ctx, r)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-07 18:45:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tasks, _, err := h.TaskService.FindTasks(ctx, req.filter)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to find tasks",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-07 18:45:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-18 16:10:14 +00:00
|
|
|
for _, task := range tasks {
|
|
|
|
if err := h.populateOrg(ctx, task); err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Msg: "could not identify organization",
|
|
|
|
}
|
2019-01-18 16:10:14 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-02 19:17:28 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newTasksResponse(ctx, tasks, h.LabelService)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.logger, r, err)
|
2018-06-07 18:45:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getTasksRequest struct {
|
|
|
|
filter platform.TaskFilter
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeGetTasksRequest(ctx context.Context, r *http.Request) (*getTasksRequest, error) {
|
|
|
|
qp := r.URL.Query()
|
|
|
|
req := &getTasksRequest{}
|
|
|
|
|
2018-10-10 19:18:29 +00:00
|
|
|
if after := qp.Get("after"); after != "" {
|
|
|
|
id, err := platform.IDFromString(after)
|
2018-07-20 10:24:07 +00:00
|
|
|
if err != nil {
|
2018-06-07 18:45:43 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-10 19:18:29 +00:00
|
|
|
req.filter.After = id
|
2018-06-07 18:45:43 +00:00
|
|
|
}
|
|
|
|
|
2018-10-10 19:18:29 +00:00
|
|
|
if orgID := qp.Get("organization"); orgID != "" {
|
|
|
|
id, err := platform.IDFromString(orgID)
|
2018-07-20 10:24:07 +00:00
|
|
|
if err != nil {
|
2018-06-07 18:45:43 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-10 19:18:29 +00:00
|
|
|
req.filter.Organization = id
|
2018-06-07 18:45:43 +00:00
|
|
|
}
|
|
|
|
|
2018-10-10 19:18:29 +00:00
|
|
|
if userID := qp.Get("user"); userID != "" {
|
|
|
|
id, err := platform.IDFromString(userID)
|
2018-07-20 10:24:07 +00:00
|
|
|
if err != nil {
|
2018-06-07 18:45:43 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-10 19:18:29 +00:00
|
|
|
req.filter.User = id
|
2018-06-07 18:45:43 +00:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:28:17 +00:00
|
|
|
if limit := qp.Get("limit"); limit != "" {
|
|
|
|
lim, err := strconv.Atoi(limit)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if lim < 1 || lim > platform.TaskMaxPageSize {
|
|
|
|
return nil, kerrors.InvalidDataf("limit must be between 1 and %d", platform.TaskMaxPageSize)
|
|
|
|
}
|
|
|
|
req.filter.Limit = lim
|
|
|
|
} else {
|
|
|
|
req.filter.Limit = platform.TaskDefaultPageSize
|
|
|
|
}
|
|
|
|
|
2018-06-07 18:45:43 +00:00
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2018-06-05 23:47:32 +00:00
|
|
|
func (h *TaskHandler) handlePostTask(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
2018-10-23 17:51:13 +00:00
|
|
|
auth, err := pcontext.GetAuthorizer(ctx)
|
|
|
|
if err != nil {
|
2019-01-19 00:45:17 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EUnauthorized,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to get authorizer",
|
2019-01-19 00:45:17 +00:00
|
|
|
}
|
2018-10-23 17:51:13 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-06-05 23:47:32 +00:00
|
|
|
req, err := decodePostTaskRequest(ctx, r)
|
|
|
|
if err != nil {
|
2019-01-19 00:45:17 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-19 00:45:17 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-05 23:47:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-18 16:10:14 +00:00
|
|
|
if err := h.populateOrg(ctx, req.Task); err != nil {
|
2019-01-19 00:45:17 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Msg: "could not identify organization",
|
|
|
|
}
|
2019-01-18 16:10:14 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !req.Task.OrganizationID.Valid() {
|
2018-11-20 12:13:12 +00:00
|
|
|
err := &platform.Error{
|
|
|
|
Code: platform.EInvalid,
|
|
|
|
Msg: "invalid organization id",
|
|
|
|
}
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-23 17:51:13 +00:00
|
|
|
if !req.Task.Owner.ID.Valid() {
|
2018-11-21 04:45:50 +00:00
|
|
|
req.Task.Owner.ID = auth.GetUserID()
|
2018-10-23 17:51:13 +00:00
|
|
|
}
|
|
|
|
|
2018-06-05 23:47:32 +00:00
|
|
|
if err := h.TaskService.CreateTask(ctx, req.Task); err != nil {
|
2018-08-20 21:15:04 +00:00
|
|
|
if e, ok := err.(AuthzError); ok {
|
|
|
|
h.logger.Error("failed authentication", zap.Errors("error messages", []error{err, e.AuthzError()}))
|
|
|
|
}
|
2019-01-19 00:45:17 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Msg: "failed to create task",
|
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-05 23:47:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-02 19:17:28 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusCreated, newTaskResponse(*req.Task, []*platform.Label{})); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.logger, r, err)
|
2018-06-05 23:47:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type postTaskRequest struct {
|
|
|
|
Task *platform.Task
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodePostTaskRequest(ctx context.Context, r *http.Request) (*postTaskRequest, error) {
|
|
|
|
task := &platform.Task{}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(task); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-11-16 00:04:16 +00:00
|
|
|
|
2018-06-05 23:47:32 +00:00
|
|
|
return &postTaskRequest{
|
|
|
|
Task: task,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-06-07 18:45:43 +00:00
|
|
|
func (h *TaskHandler) handleGetTask(w http.ResponseWriter, r *http.Request) {
|
2018-06-05 23:47:32 +00:00
|
|
|
ctx := r.Context()
|
|
|
|
|
2018-06-07 18:45:43 +00:00
|
|
|
req, err := decodeGetTaskRequest(ctx, r)
|
2018-06-05 23:47:32 +00:00
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-05 23:47:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-06-07 18:45:43 +00:00
|
|
|
task, err := h.TaskService.FindTaskByID(ctx, req.TaskID)
|
2018-06-05 23:47:32 +00:00
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.ENotFound,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to find task",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-05 23:47:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-18 16:10:14 +00:00
|
|
|
if err := h.populateOrg(ctx, task); err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Msg: "could not identify organization",
|
|
|
|
}
|
2019-01-18 16:10:14 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-18 19:03:36 +00:00
|
|
|
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: task.ID})
|
2019-01-02 19:17:28 +00:00
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to find resource labels",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2019-01-02 19:17:28 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newTaskResponse(*task, labels)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.logger, r, err)
|
2018-06-05 23:47:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-07 18:45:43 +00:00
|
|
|
type getTaskRequest struct {
|
|
|
|
TaskID platform.ID
|
2018-06-05 23:47:32 +00:00
|
|
|
}
|
|
|
|
|
2018-06-07 18:45:43 +00:00
|
|
|
func decodeGetTaskRequest(ctx context.Context, r *http.Request) (*getTaskRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
2018-11-26 07:03:27 +00:00
|
|
|
id := params.ByName("id")
|
2018-06-07 18:45:43 +00:00
|
|
|
if id == "" {
|
|
|
|
return nil, kerrors.InvalidDataf("url missing id")
|
2018-06-05 23:47:32 +00:00
|
|
|
}
|
|
|
|
|
2018-06-07 18:45:43 +00:00
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
2018-06-05 23:47:32 +00:00
|
|
|
}
|
|
|
|
|
2018-06-07 18:45:43 +00:00
|
|
|
req := &getTaskRequest{
|
|
|
|
TaskID: i,
|
2018-06-05 23:47:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
2018-06-07 18:45:43 +00:00
|
|
|
|
|
|
|
func (h *TaskHandler) handleUpdateTask(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeUpdateTaskRequest(ctx, r)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-07 18:45:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
task, err := h.TaskService.UpdateTask(ctx, req.TaskID, req.Update)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to update task",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-07 18:45:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-18 19:03:36 +00:00
|
|
|
labels, err := h.LabelService.FindResourceLabels(ctx, platform.LabelMappingFilter{ResourceID: task.ID})
|
2019-01-02 19:17:28 +00:00
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to find resource labels",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2019-01-02 19:17:28 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-18 16:10:14 +00:00
|
|
|
if err := h.populateOrg(ctx, task); err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Msg: "could not identify organization",
|
|
|
|
}
|
2019-01-18 16:10:14 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-02 19:17:28 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newTaskResponse(*task, labels)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.logger, r, err)
|
2018-06-07 18:45:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type updateTaskRequest struct {
|
|
|
|
Update platform.TaskUpdate
|
|
|
|
TaskID platform.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeUpdateTaskRequest(ctx context.Context, r *http.Request) (*updateTaskRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
2018-11-26 07:03:27 +00:00
|
|
|
id := params.ByName("id")
|
2018-06-07 18:45:43 +00:00
|
|
|
if id == "" {
|
2018-06-13 03:29:13 +00:00
|
|
|
return nil, kerrors.InvalidDataf("you must provide a task ID")
|
2018-06-07 18:45:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var upd platform.TaskUpdate
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&upd); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &updateTaskRequest{
|
|
|
|
Update: upd,
|
|
|
|
TaskID: i,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *TaskHandler) handleDeleteTask(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeDeleteTaskRequest(ctx, r)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-07 18:45:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := h.TaskService.DeleteTask(ctx, req.TaskID); err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to delete task",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-07 18:45:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
w.WriteHeader(http.StatusNoContent)
|
2018-06-07 18:45:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type deleteTaskRequest struct {
|
|
|
|
TaskID platform.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeDeleteTaskRequest(ctx context.Context, r *http.Request) (*deleteTaskRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
2018-11-26 07:03:27 +00:00
|
|
|
id := params.ByName("id")
|
2018-06-07 18:45:43 +00:00
|
|
|
if id == "" {
|
2018-06-13 03:29:13 +00:00
|
|
|
return nil, kerrors.InvalidDataf("you must provide a task ID")
|
2018-06-07 18:45:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(id); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &deleteTaskRequest{
|
|
|
|
TaskID: i,
|
|
|
|
}, nil
|
|
|
|
}
|
2018-06-08 23:41:26 +00:00
|
|
|
|
|
|
|
func (h *TaskHandler) handleGetLogs(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
2018-10-10 18:37:11 +00:00
|
|
|
req, err := decodeGetLogsRequest(ctx, r, h.OrganizationService)
|
2018-06-08 23:41:26 +00:00
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
logs, _, err := h.TaskService.FindLogs(ctx, req.filter)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to find task logs",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, logs); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.logger, r, err)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getLogsRequest struct {
|
|
|
|
filter platform.LogFilter
|
|
|
|
}
|
|
|
|
|
2018-10-10 18:37:11 +00:00
|
|
|
func decodeGetLogsRequest(ctx context.Context, r *http.Request, orgs platform.OrganizationService) (*getLogsRequest, error) {
|
2018-06-13 03:29:13 +00:00
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
2018-11-26 07:03:27 +00:00
|
|
|
id := params.ByName("id")
|
2018-06-13 03:29:13 +00:00
|
|
|
if id == "" {
|
|
|
|
return nil, kerrors.InvalidDataf("you must provide a task ID")
|
|
|
|
}
|
2018-06-08 23:41:26 +00:00
|
|
|
|
2018-06-13 03:29:13 +00:00
|
|
|
req := &getLogsRequest{}
|
2018-07-20 10:24:07 +00:00
|
|
|
taskID, err := platform.IDFromString(id)
|
|
|
|
if err != nil {
|
2018-06-13 03:29:13 +00:00
|
|
|
return nil, err
|
2018-06-08 23:41:26 +00:00
|
|
|
}
|
2018-07-20 10:24:07 +00:00
|
|
|
req.filter.Task = taskID
|
2018-06-08 23:41:26 +00:00
|
|
|
|
2018-10-10 18:37:11 +00:00
|
|
|
qp := r.URL.Query()
|
|
|
|
|
|
|
|
if orgName := qp.Get("org"); orgName != "" {
|
|
|
|
o, err := orgs.FindOrganization(ctx, platform.OrganizationFilter{Name: &orgName})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.filter.Org = &o.ID
|
2018-11-07 01:20:31 +00:00
|
|
|
} else if oid := qp.Get("orgID"); oid != "" {
|
|
|
|
orgID, err := platform.IDFromString(oid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.filter.Org = orgID
|
2018-10-10 18:37:11 +00:00
|
|
|
}
|
|
|
|
|
2018-11-07 20:15:53 +00:00
|
|
|
if runID := params.ByName("rid"); runID != "" {
|
2018-10-10 19:18:29 +00:00
|
|
|
id, err := platform.IDFromString(runID)
|
2018-07-20 10:24:07 +00:00
|
|
|
if err != nil {
|
2018-06-08 23:41:26 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-10 19:18:29 +00:00
|
|
|
req.filter.Run = id
|
2018-06-08 23:41:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *TaskHandler) handleGetRuns(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
2018-10-01 16:53:06 +00:00
|
|
|
req, err := decodeGetRunsRequest(ctx, r, h.OrganizationService)
|
2018-06-08 23:41:26 +00:00
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
runs, _, err := h.TaskService.FindRuns(ctx, req.filter)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to find runs",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newRunsResponse(runs, *req.filter.Task)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.logger, r, err)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getRunsRequest struct {
|
|
|
|
filter platform.RunFilter
|
|
|
|
}
|
|
|
|
|
2018-10-01 16:53:06 +00:00
|
|
|
func decodeGetRunsRequest(ctx context.Context, r *http.Request, orgs platform.OrganizationService) (*getRunsRequest, error) {
|
2018-06-13 03:29:13 +00:00
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
2018-11-26 07:03:27 +00:00
|
|
|
id := params.ByName("id")
|
2018-06-13 03:29:13 +00:00
|
|
|
if id == "" {
|
|
|
|
return nil, kerrors.InvalidDataf("you must provide a task ID")
|
|
|
|
}
|
2018-06-08 23:41:26 +00:00
|
|
|
|
2018-06-13 03:29:13 +00:00
|
|
|
req := &getRunsRequest{}
|
2018-07-20 10:24:07 +00:00
|
|
|
taskID, err := platform.IDFromString(id)
|
|
|
|
if err != nil {
|
2018-06-13 03:29:13 +00:00
|
|
|
return nil, err
|
2018-06-08 23:41:26 +00:00
|
|
|
}
|
2018-07-20 10:24:07 +00:00
|
|
|
req.filter.Task = taskID
|
2018-06-08 23:41:26 +00:00
|
|
|
|
2018-06-13 03:29:13 +00:00
|
|
|
qp := r.URL.Query()
|
|
|
|
|
2018-10-01 16:53:06 +00:00
|
|
|
if orgName := qp.Get("org"); orgName != "" {
|
|
|
|
o, err := orgs.FindOrganization(ctx, platform.OrganizationFilter{Name: &orgName})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.filter.Org = &o.ID
|
2018-10-23 17:51:13 +00:00
|
|
|
} else if orgID := qp.Get("orgID"); orgID != "" {
|
|
|
|
oid, err := platform.IDFromString(orgID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.filter.Org = oid
|
2018-10-01 16:53:06 +00:00
|
|
|
}
|
|
|
|
|
2018-06-08 23:41:26 +00:00
|
|
|
if id := qp.Get("after"); id != "" {
|
2018-07-20 10:24:07 +00:00
|
|
|
afterID, err := platform.IDFromString(id)
|
|
|
|
if err != nil {
|
2018-06-08 23:41:26 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-20 10:24:07 +00:00
|
|
|
req.filter.After = afterID
|
2018-06-08 23:41:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if limit := qp.Get("limit"); limit != "" {
|
|
|
|
i, err := strconv.Atoi(limit)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if i < 1 || i > 100 {
|
|
|
|
return nil, kerrors.InvalidDataf("limit must be between 1 and 100")
|
|
|
|
}
|
|
|
|
|
|
|
|
req.filter.Limit = i
|
|
|
|
}
|
|
|
|
|
2018-11-28 12:45:25 +00:00
|
|
|
var at, bt string
|
2018-11-25 12:06:20 +00:00
|
|
|
var afterTime, beforeTime time.Time
|
2018-11-28 12:45:25 +00:00
|
|
|
if at = qp.Get("afterTime"); at != "" {
|
2018-11-25 12:06:20 +00:00
|
|
|
afterTime, err = time.Parse(time.RFC3339, at)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.filter.AfterTime = at
|
|
|
|
}
|
|
|
|
|
2018-11-28 12:45:25 +00:00
|
|
|
if bt = qp.Get("beforeTime"); bt != "" {
|
2018-11-25 12:06:20 +00:00
|
|
|
beforeTime, err = time.Parse(time.RFC3339, bt)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.filter.BeforeTime = bt
|
2018-06-08 23:41:26 +00:00
|
|
|
}
|
|
|
|
|
2018-11-29 14:30:37 +00:00
|
|
|
if at != "" && bt != "" && !beforeTime.After(afterTime) {
|
2018-11-29 14:26:11 +00:00
|
|
|
return nil, kerrors.InvalidDataf("beforeTime must be later than afterTime")
|
2018-06-08 23:41:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2018-12-21 17:04:08 +00:00
|
|
|
func (h *TaskHandler) handleForceRun(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeForceRunRequest(ctx, r)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-12-21 17:04:08 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
run, err := h.TaskService.ForceRun(ctx, req.TaskID, req.Timestamp)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to force run",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-12-21 17:04:08 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newRunResponse(*run)); err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
logEncodingError(h.logger, r, err)
|
2018-12-21 17:04:08 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type forceRunRequest struct {
|
|
|
|
TaskID platform.ID
|
|
|
|
Timestamp int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeForceRunRequest(ctx context.Context, r *http.Request) (forceRunRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
tid := params.ByName("id")
|
|
|
|
if tid == "" {
|
|
|
|
return forceRunRequest{}, kerrors.InvalidDataf("you must provide a task ID")
|
|
|
|
}
|
|
|
|
|
|
|
|
var ti platform.ID
|
|
|
|
if err := ti.DecodeFromString(tid); err != nil {
|
|
|
|
return forceRunRequest{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var req struct {
|
|
|
|
ScheduledFor string `json:"scheduledFor"`
|
|
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
|
|
return forceRunRequest{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var t time.Time
|
|
|
|
if req.ScheduledFor == "" {
|
|
|
|
t = time.Now()
|
|
|
|
} else {
|
|
|
|
var err error
|
|
|
|
t, err = time.Parse(time.RFC3339, req.ScheduledFor)
|
|
|
|
if err != nil {
|
|
|
|
return forceRunRequest{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return forceRunRequest{
|
|
|
|
TaskID: ti,
|
|
|
|
Timestamp: t.Unix(),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-06-08 23:41:26 +00:00
|
|
|
func (h *TaskHandler) handleGetRun(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
2018-10-25 23:43:11 +00:00
|
|
|
req, err := decodeGetRunRequest(ctx, r)
|
2018-06-08 23:41:26 +00:00
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-25 23:43:11 +00:00
|
|
|
run, err := h.TaskService.FindRunByID(ctx, req.TaskID, req.RunID)
|
2018-06-08 23:41:26 +00:00
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.ENotFound,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to find run",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newRunResponse(*run)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.logger, r, err)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type getRunRequest struct {
|
2018-10-25 23:43:11 +00:00
|
|
|
TaskID platform.ID
|
|
|
|
RunID platform.ID
|
2018-06-08 23:41:26 +00:00
|
|
|
}
|
|
|
|
|
2018-10-25 23:43:11 +00:00
|
|
|
func decodeGetRunRequest(ctx context.Context, r *http.Request) (*getRunRequest, error) {
|
2018-06-08 23:41:26 +00:00
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
2018-11-26 07:03:27 +00:00
|
|
|
tid := params.ByName("id")
|
2018-10-25 23:43:11 +00:00
|
|
|
if tid == "" {
|
|
|
|
return nil, kerrors.InvalidDataf("you must provide a task ID")
|
|
|
|
}
|
|
|
|
rid := params.ByName("rid")
|
|
|
|
if rid == "" {
|
2018-06-08 23:41:26 +00:00
|
|
|
return nil, kerrors.InvalidDataf("you must provide a run ID")
|
|
|
|
}
|
|
|
|
|
2018-10-25 23:43:11 +00:00
|
|
|
var ti, ri platform.ID
|
|
|
|
if err := ti.DecodeFromString(tid); err != nil {
|
|
|
|
return nil, err
|
2018-10-08 20:07:08 +00:00
|
|
|
}
|
2018-10-25 23:43:11 +00:00
|
|
|
if err := ri.DecodeFromString(rid); err != nil {
|
2018-06-08 23:41:26 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &getRunRequest{
|
2018-10-25 23:43:11 +00:00
|
|
|
RunID: ri,
|
|
|
|
TaskID: ti,
|
2018-06-08 23:41:26 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-10-04 17:12:43 +00:00
|
|
|
type cancelRunRequest struct {
|
|
|
|
RunID platform.ID
|
|
|
|
TaskID platform.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeCancelRunRequest(ctx context.Context, r *http.Request) (*cancelRunRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
|
|
rid := params.ByName("rid")
|
|
|
|
if rid == "" {
|
|
|
|
return nil, kerrors.InvalidDataf("you must provide a run ID")
|
|
|
|
}
|
2018-11-26 07:03:27 +00:00
|
|
|
tid := params.ByName("id")
|
2018-10-04 17:12:43 +00:00
|
|
|
if tid == "" {
|
|
|
|
return nil, kerrors.InvalidDataf("you must provide a task ID")
|
|
|
|
}
|
|
|
|
|
|
|
|
var i platform.ID
|
|
|
|
if err := i.DecodeFromString(rid); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var t platform.ID
|
|
|
|
if err := t.DecodeFromString(tid); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &cancelRunRequest{
|
|
|
|
RunID: i,
|
|
|
|
TaskID: t,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *TaskHandler) handleCancelRun(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeCancelRunRequest(ctx, r)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-10-04 17:12:43 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = h.TaskService.CancelRun(ctx, req.TaskID, req.RunID)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to cancel run",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-10-04 17:12:43 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-08 23:41:26 +00:00
|
|
|
func (h *TaskHandler) handleRetryRun(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
req, err := decodeRetryRunRequest(ctx, r)
|
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
|
|
|
Code: platform.EInvalid,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to decode request",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-06-28 19:32:16 +00:00
|
|
|
EncodeError(ctx, err, w)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-11-27 22:32:25 +00:00
|
|
|
run, err := h.TaskService.RetryRun(ctx, req.TaskID, req.RunID)
|
2018-11-20 21:53:52 +00:00
|
|
|
if err != nil {
|
2019-01-23 20:07:00 +00:00
|
|
|
err = &platform.Error{
|
|
|
|
Err: err,
|
2019-01-25 00:28:32 +00:00
|
|
|
Msg: "failed to retry run",
|
2019-01-23 20:07:00 +00:00
|
|
|
}
|
2018-11-20 21:53:52 +00:00
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newRunResponse(*run)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.logger, r, err)
|
2018-06-08 23:41:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type retryRunRequest struct {
|
2018-10-25 23:43:11 +00:00
|
|
|
RunID, TaskID platform.ID
|
2018-06-08 23:41:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func decodeRetryRunRequest(ctx context.Context, r *http.Request) (*retryRunRequest, error) {
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
2018-11-26 07:03:27 +00:00
|
|
|
tid := params.ByName("id")
|
2018-10-25 23:43:11 +00:00
|
|
|
if tid == "" {
|
|
|
|
return nil, kerrors.InvalidDataf("you must provide a task ID")
|
|
|
|
}
|
|
|
|
rid := params.ByName("rid")
|
|
|
|
if rid == "" {
|
2018-06-08 23:41:26 +00:00
|
|
|
return nil, kerrors.InvalidDataf("you must provide a run ID")
|
|
|
|
}
|
|
|
|
|
2018-10-25 23:43:11 +00:00
|
|
|
var ti, ri platform.ID
|
|
|
|
if err := ti.DecodeFromString(tid); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := ri.DecodeFromString(rid); err != nil {
|
2018-06-08 23:41:26 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &retryRunRequest{
|
2018-11-27 22:32:25 +00:00
|
|
|
RunID: ri,
|
|
|
|
TaskID: ti,
|
2018-06-08 23:41:26 +00:00
|
|
|
}, nil
|
|
|
|
}
|
2018-10-23 17:51:13 +00:00
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// TaskService connects to Influx via HTTP using tokens to manage tasks.
|
2018-10-23 17:51:13 +00:00
|
|
|
type TaskService struct {
|
|
|
|
Addr string
|
|
|
|
Token string
|
|
|
|
InsecureSkipVerify bool
|
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// FindTaskByID returns a single task
|
2018-10-23 17:51:13 +00:00
|
|
|
func (t TaskService) FindTaskByID(ctx context.Context, id platform.ID) (*platform.Task, error) {
|
|
|
|
u, err := newURL(t.Addr, taskIDPath(id))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-24 20:44:39 +00:00
|
|
|
defer resp.Body.Close()
|
2018-10-23 17:51:13 +00:00
|
|
|
|
2019-01-23 20:07:00 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
if err.Error() == backend.ErrTaskNotFound.Error() {
|
|
|
|
// ErrTaskNotFound is expected as part of the FindTaskByID contract,
|
|
|
|
// so return that actual error instead of a different error that looks like it.
|
|
|
|
return nil, backend.ErrTaskNotFound
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
var tr taskResponse
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&tr); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
return &tr.Task, nil
|
2018-10-23 17:51:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// FindTasks returns a list of tasks that match a filter (limit 100) and the total count
|
|
|
|
// of matching tasks.
|
2018-10-23 17:51:13 +00:00
|
|
|
func (t TaskService) FindTasks(ctx context.Context, filter platform.TaskFilter) ([]*platform.Task, int, error) {
|
|
|
|
u, err := newURL(t.Addr, tasksPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
val := url.Values{}
|
|
|
|
if filter.After != nil {
|
|
|
|
val.Add("after", filter.After.String())
|
|
|
|
}
|
|
|
|
if filter.Organization != nil {
|
|
|
|
val.Add("organization", filter.Organization.String())
|
|
|
|
}
|
|
|
|
if filter.User != nil {
|
|
|
|
val.Add("user", filter.User.String())
|
|
|
|
}
|
2018-11-29 16:28:17 +00:00
|
|
|
if filter.Limit != 0 {
|
|
|
|
val.Add("limit", strconv.Itoa(filter.Limit))
|
|
|
|
}
|
2018-10-23 17:51:13 +00:00
|
|
|
|
|
|
|
u.RawQuery = val.Encode()
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
2018-10-24 20:44:39 +00:00
|
|
|
defer resp.Body.Close()
|
2018-10-23 17:51:13 +00:00
|
|
|
|
2019-01-23 20:07:00 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
var tr tasksResponse
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&tr); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
2018-11-21 06:11:48 +00:00
|
|
|
tasks := make([]*platform.Task, len(tr.Tasks))
|
2018-11-28 22:52:11 +00:00
|
|
|
for i := range tr.Tasks {
|
2018-11-21 06:11:48 +00:00
|
|
|
tasks[i] = &tr.Tasks[i].Task
|
2018-11-20 10:47:53 +00:00
|
|
|
}
|
2018-10-23 17:51:13 +00:00
|
|
|
return tasks, len(tasks), nil
|
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// CreateTask creates a new task.
|
2018-10-23 17:51:13 +00:00
|
|
|
func (t TaskService) CreateTask(ctx context.Context, tsk *platform.Task) error {
|
|
|
|
u, err := newURL(t.Addr, tasksPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
taskBytes, err := json.Marshal(tsk)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(taskBytes))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-10-24 20:44:39 +00:00
|
|
|
defer resp.Body.Close()
|
2018-10-23 17:51:13 +00:00
|
|
|
|
2019-01-18 17:18:54 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
var tr taskResponse
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&tr); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
return err
|
|
|
|
}
|
2018-10-24 20:44:39 +00:00
|
|
|
*tsk = tr.Task
|
2018-10-23 17:51:13 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// UpdateTask updates a single task with changeset.
|
2018-10-23 17:51:13 +00:00
|
|
|
func (t TaskService) UpdateTask(ctx context.Context, id platform.ID, upd platform.TaskUpdate) (*platform.Task, error) {
|
|
|
|
u, err := newURL(t.Addr, taskIDPath(id))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
taskBytes, err := json.Marshal(upd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(taskBytes))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2019-01-23 20:07:00 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
var tr taskResponse
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&tr); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
return &tr.Task, nil
|
2018-10-23 17:51:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// DeleteTask removes a task by ID and purges all associated data and scheduled runs.
|
2018-10-23 17:51:13 +00:00
|
|
|
func (t TaskService) DeleteTask(ctx context.Context, id platform.ID) error {
|
|
|
|
u, err := newURL(t.Addr, taskIDPath(id))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("DELETE", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
return CheckErrorStatus(http.StatusNoContent, resp)
|
2018-10-23 17:51:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// FindLogs returns logs for a run.
|
2018-10-23 17:51:13 +00:00
|
|
|
func (t TaskService) FindLogs(ctx context.Context, filter platform.LogFilter) ([]*platform.Log, int, error) {
|
2018-11-07 01:20:31 +00:00
|
|
|
if filter.Task == nil {
|
|
|
|
return nil, 0, errors.New("task ID required")
|
|
|
|
}
|
|
|
|
|
|
|
|
var urlPath string
|
|
|
|
if filter.Run == nil {
|
|
|
|
urlPath = path.Join(taskIDPath(*filter.Task), "logs")
|
|
|
|
} else {
|
|
|
|
urlPath = path.Join(taskIDRunIDPath(*filter.Task, *filter.Run), "logs")
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := newURL(t.Addr, urlPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
val := url.Values{}
|
|
|
|
if filter.Org != nil {
|
|
|
|
val.Set("orgID", filter.Org.String())
|
|
|
|
}
|
|
|
|
u.RawQuery = val.Encode()
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2019-01-23 20:07:00 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-11-07 01:20:31 +00:00
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var logs []*platform.Log
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&logs); err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return logs, len(logs), nil
|
2018-10-23 17:51:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// FindRuns returns a list of runs that match a filter and the total count of returned runs.
|
2018-10-23 17:51:13 +00:00
|
|
|
func (t TaskService) FindRuns(ctx context.Context, filter platform.RunFilter) ([]*platform.Run, int, error) {
|
|
|
|
if filter.Task == nil {
|
|
|
|
return nil, 0, errors.New("task ID required")
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := newURL(t.Addr, taskIDRunsPath(*filter.Task))
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
val := url.Values{}
|
|
|
|
if filter.Org != nil {
|
|
|
|
val.Set("orgID", filter.Org.String())
|
|
|
|
}
|
|
|
|
if filter.After != nil {
|
|
|
|
val.Set("after", filter.After.String())
|
|
|
|
}
|
2019-01-23 00:25:38 +00:00
|
|
|
if filter.Limit > 0 {
|
|
|
|
val.Set("limit", strconv.Itoa(filter.Limit))
|
|
|
|
}
|
2018-10-23 17:51:13 +00:00
|
|
|
u.RawQuery = val.Encode()
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2019-01-23 20:07:00 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
2018-10-24 20:44:39 +00:00
|
|
|
var rs runsResponse
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&rs); err != nil {
|
2018-10-23 17:51:13 +00:00
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
2018-12-04 23:10:03 +00:00
|
|
|
runs := make([]*platform.Run, len(rs.Runs))
|
|
|
|
for i := range rs.Runs {
|
|
|
|
runs[i] = &rs.Runs[i].Run
|
|
|
|
}
|
|
|
|
|
2018-10-23 17:51:13 +00:00
|
|
|
return runs, len(runs), nil
|
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// FindRunByID returns a single run of a specific task.
|
2018-10-25 23:43:11 +00:00
|
|
|
func (t TaskService) FindRunByID(ctx context.Context, taskID, runID platform.ID) (*platform.Run, error) {
|
|
|
|
u, err := newURL(t.Addr, taskIDRunIDPath(taskID, runID))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2019-01-23 20:07:00 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-10-25 23:43:11 +00:00
|
|
|
if err.Error() == backend.ErrRunNotFound.Error() {
|
|
|
|
// ErrRunNotFound is expected as part of the FindRunByID contract,
|
|
|
|
// so return that actual error instead of a different error that looks like it.
|
|
|
|
return nil, backend.ErrRunNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-11-20 21:53:52 +00:00
|
|
|
var rs = &runResponse{}
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(rs); err != nil {
|
2018-10-25 23:43:11 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-11-20 21:53:52 +00:00
|
|
|
return &rs.Run, nil
|
2018-10-23 17:51:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-29 19:10:33 +00:00
|
|
|
// RetryRun creates and returns a new run (which is a retry of another run).
|
2018-11-27 22:32:25 +00:00
|
|
|
func (t TaskService) RetryRun(ctx context.Context, taskID, runID platform.ID) (*platform.Run, error) {
|
2018-10-25 23:43:11 +00:00
|
|
|
p := path.Join(taskIDRunIDPath(taskID, runID), "retry")
|
|
|
|
u, err := newURL(t.Addr, p)
|
|
|
|
if err != nil {
|
2018-11-20 21:53:52 +00:00
|
|
|
return nil, err
|
2018-10-25 23:43:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", u.String(), nil)
|
|
|
|
if err != nil {
|
2018-11-20 21:53:52 +00:00
|
|
|
return nil, err
|
2018-10-25 23:43:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
2018-11-20 21:53:52 +00:00
|
|
|
return nil, err
|
2018-10-25 23:43:11 +00:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2019-01-23 20:07:00 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-10-25 23:43:11 +00:00
|
|
|
if err.Error() == backend.ErrRunNotFound.Error() {
|
|
|
|
// ErrRunNotFound is expected as part of the RetryRun contract,
|
|
|
|
// so return that actual error instead of a different error that looks like it.
|
2018-11-20 21:53:52 +00:00
|
|
|
return nil, backend.ErrRunNotFound
|
2018-10-25 23:43:11 +00:00
|
|
|
}
|
|
|
|
|
2018-12-21 17:04:08 +00:00
|
|
|
// RequestStillQueuedError is also part of the contract.
|
|
|
|
if e := backend.ParseRequestStillQueuedError(err.Error()); e != nil {
|
|
|
|
return nil, *e
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rs := &runResponse{}
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(rs); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &rs.Run, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t TaskService) ForceRun(ctx context.Context, taskID platform.ID, scheduledFor int64) (*platform.Run, error) {
|
|
|
|
u, err := newURL(t.Addr, taskIDRunsPath(taskID))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
body := fmt.Sprintf(`{"scheduledFor": %q}`, time.Unix(scheduledFor, 0).UTC().Format(time.RFC3339))
|
|
|
|
req, err := http.NewRequest("POST", u.String(), strings.NewReader(body))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2019-01-23 20:07:00 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-12-21 17:04:08 +00:00
|
|
|
if err.Error() == backend.ErrRunNotFound.Error() {
|
|
|
|
// ErrRunNotFound is expected as part of the RetryRun contract,
|
|
|
|
// so return that actual error instead of a different error that looks like it.
|
|
|
|
return nil, backend.ErrRunNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
// RequestStillQueuedError is also part of the contract.
|
|
|
|
if e := backend.ParseRequestStillQueuedError(err.Error()); e != nil {
|
2018-11-20 21:53:52 +00:00
|
|
|
return nil, *e
|
2018-10-25 23:43:11 +00:00
|
|
|
}
|
|
|
|
|
2018-11-20 21:53:52 +00:00
|
|
|
return nil, err
|
2018-10-25 23:43:11 +00:00
|
|
|
}
|
|
|
|
|
2018-11-20 21:53:52 +00:00
|
|
|
rs := &runResponse{}
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(rs); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &rs.Run, nil
|
2018-10-23 17:51:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 17:12:43 +00:00
|
|
|
func cancelPath(taskID, runID platform.ID) string {
|
|
|
|
return path.Join(taskID.String(), runID.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t TaskService) CancelRun(ctx context.Context, taskID, runID platform.ID) error {
|
|
|
|
u, err := newURL(t.Addr, cancelPath(taskID, runID))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("DELETE", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
SetToken(t.Token, req)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, t.InsecureSkipVerify)
|
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2019-01-23 20:07:00 +00:00
|
|
|
if err := CheckError(resp, true); err != nil {
|
2018-10-04 17:12:43 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-10-23 17:51:13 +00:00
|
|
|
func taskIDPath(id platform.ID) string {
|
|
|
|
return path.Join(tasksPath, id.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func taskIDRunsPath(id platform.ID) string {
|
|
|
|
return path.Join(tasksPath, id.String(), "runs")
|
|
|
|
}
|
2018-10-25 23:43:11 +00:00
|
|
|
|
|
|
|
func taskIDRunIDPath(taskID, runID platform.ID) string {
|
|
|
|
return path.Join(tasksPath, taskID.String(), "runs", runID.String())
|
|
|
|
}
|
2019-01-18 16:10:14 +00:00
|
|
|
|
|
|
|
func (h *TaskHandler) populateOrg(ctx context.Context, t *platform.Task) error {
|
|
|
|
if t.OrganizationID.Valid() && t.Organization != "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if !t.OrganizationID.Valid() && t.Organization == "" {
|
|
|
|
return errors.New("missing orgID and organization name")
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.OrganizationID.Valid() {
|
|
|
|
o, err := h.OrganizationService.FindOrganizationByID(ctx, t.OrganizationID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.Organization = o.Name
|
|
|
|
} else {
|
|
|
|
o, err := h.OrganizationService.FindOrganization(ctx, platform.OrganizationFilter{Name: &t.Organization})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.OrganizationID = o.ID
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|