github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/apiv3/route/task_routes.go (about)

     1  package route
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  
     9  	"github.com/evergreen-ci/evergreen"
    10  	"github.com/evergreen-ci/evergreen/apiv3"
    11  	"github.com/evergreen-ci/evergreen/apiv3/model"
    12  	"github.com/evergreen-ci/evergreen/apiv3/servicecontext"
    13  	"github.com/evergreen-ci/evergreen/auth"
    14  	serviceModel "github.com/evergreen-ci/evergreen/model"
    15  	"github.com/evergreen-ci/evergreen/model/task"
    16  	"github.com/evergreen-ci/evergreen/util"
    17  	"github.com/gorilla/mux"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  const (
    22  	incorrectArgsTypeErrorMessage = "programmer error: incorrect type for paginator args"
    23  )
    24  
    25  func getTaskRestartRouteManager(route string, version int) *RouteManager {
    26  	trh := &TaskRestartHandler{}
    27  	taskRestart := MethodHandler{
    28  		PrefetchFunctions: []PrefetchFunc{PrefetchUser, PrefetchProjectContext},
    29  		Authenticator:     &RequireUserAuthenticator{},
    30  		RequestHandler:    trh.Handler(),
    31  		MethodType:        evergreen.MethodPost,
    32  	}
    33  
    34  	taskRoute := RouteManager{
    35  		Route:   route,
    36  		Methods: []MethodHandler{taskRestart},
    37  		Version: version,
    38  	}
    39  	return &taskRoute
    40  }
    41  
    42  func getTasksByBuildRouteManager(route string, version int) *RouteManager {
    43  	tbh := &tasksByBuildHandler{}
    44  	tasksByBuild := MethodHandler{
    45  		PrefetchFunctions: []PrefetchFunc{PrefetchUser},
    46  		Authenticator:     &RequireUserAuthenticator{},
    47  		RequestHandler:    tbh.Handler(),
    48  		MethodType:        evergreen.MethodGet,
    49  	}
    50  
    51  	taskRoute := RouteManager{
    52  		Route:   route,
    53  		Methods: []MethodHandler{tasksByBuild},
    54  		Version: version,
    55  	}
    56  	return &taskRoute
    57  }
    58  
    59  func getTaskRouteManager(route string, version int) *RouteManager {
    60  	tep := &TaskExecutionPatchHandler{}
    61  	taskExecutionPatch := MethodHandler{
    62  		PrefetchFunctions: []PrefetchFunc{PrefetchProjectContext, PrefetchUser},
    63  		Authenticator:     &NoAuthAuthenticator{},
    64  		RequestHandler:    tep.Handler(),
    65  		MethodType:        evergreen.MethodPatch,
    66  	}
    67  
    68  	tgh := &taskGetHandler{}
    69  	taskGet := MethodHandler{
    70  		PrefetchFunctions: []PrefetchFunc{PrefetchUser},
    71  		Authenticator:     &RequireUserAuthenticator{},
    72  		RequestHandler:    tgh.Handler(),
    73  		MethodType:        evergreen.MethodGet,
    74  	}
    75  
    76  	taskRoute := RouteManager{
    77  		Route:   route,
    78  		Methods: []MethodHandler{taskExecutionPatch, taskGet},
    79  		Version: version,
    80  	}
    81  	return &taskRoute
    82  }
    83  
    84  func getTasksByProjectAndCommitRouteManager(route string, version int) *RouteManager {
    85  	tph := &tasksByProjectHandler{}
    86  	tasksByProj := MethodHandler{
    87  		PrefetchFunctions: []PrefetchFunc{PrefetchUser},
    88  		Authenticator:     &RequireUserAuthenticator{},
    89  		RequestHandler:    tph.Handler(),
    90  		MethodType:        evergreen.MethodGet,
    91  	}
    92  
    93  	taskRoute := RouteManager{
    94  		Route:   route,
    95  		Methods: []MethodHandler{tasksByProj},
    96  		Version: version,
    97  	}
    98  	return &taskRoute
    99  }
   100  
   101  // taskByProjectHandler implements the GET /projects/{project_id}/revisions/{commit_hash}/tasks.
   102  // It fetches the associated tasks and returns them to the user.
   103  type tasksByProjectHandler struct {
   104  	*PaginationExecutor
   105  }
   106  
   107  type tasksByProjectArgs struct {
   108  	projectId  string
   109  	commitHash string
   110  	status     string
   111  }
   112  
   113  // ParseAndValidate fetches the project context and task status from the request
   114  // and loads them into the arguments to be used by the execution.
   115  func (tph *tasksByProjectHandler) ParseAndValidate(r *http.Request) error {
   116  	args := tasksByProjectArgs{
   117  		projectId:  mux.Vars(r)["project_id"],
   118  		commitHash: mux.Vars(r)["commit_hash"],
   119  		status:     r.URL.Query().Get("status"),
   120  	}
   121  	if args.projectId == "" {
   122  		return apiv3.APIError{
   123  			Message:    "ProjectId cannot be empty",
   124  			StatusCode: http.StatusBadRequest,
   125  		}
   126  	}
   127  
   128  	if args.commitHash == "" {
   129  		return apiv3.APIError{
   130  			Message:    "Revision cannot be empty",
   131  			StatusCode: http.StatusBadRequest,
   132  		}
   133  	}
   134  	tph.Args = args
   135  	return tph.PaginationExecutor.ParseAndValidate(r)
   136  }
   137  
   138  func tasksByProjectPaginator(key string, limit int, args interface{}, sc servicecontext.ServiceContext) ([]model.Model,
   139  	*PageResult, error) {
   140  	ptArgs, ok := args.(tasksByProjectArgs)
   141  	if !ok {
   142  		panic("ARGS HAD WRONG TYPE!")
   143  	}
   144  	tasks, err := sc.FindTasksByProjectAndCommit(ptArgs.projectId, ptArgs.commitHash, key, ptArgs.status, limit*2, 1)
   145  	if err != nil {
   146  		if _, ok := err.(*apiv3.APIError); !ok {
   147  			err = errors.Wrap(err, "Database error")
   148  		}
   149  		return []model.Model{}, nil, err
   150  	}
   151  
   152  	// Make the previous page
   153  	prevTasks, err := sc.FindTasksByProjectAndCommit(ptArgs.projectId, ptArgs.commitHash, key, ptArgs.status, limit, -1)
   154  	if err != nil {
   155  		if apiErr, ok := err.(*apiv3.APIError); !ok || apiErr.StatusCode != http.StatusNotFound {
   156  			return []model.Model{}, nil, errors.Wrap(err, "Database error")
   157  		}
   158  	}
   159  
   160  	nextPage := makeNextTasksPage(tasks, limit)
   161  
   162  	pageResults := &PageResult{
   163  		Next: nextPage,
   164  		Prev: makePrevTasksPage(prevTasks),
   165  	}
   166  
   167  	lastIndex := len(tasks)
   168  	if nextPage != nil {
   169  		lastIndex = limit
   170  	}
   171  
   172  	// Truncate the tasks to just those that will be returned.
   173  	tasks = tasks[:lastIndex]
   174  
   175  	models := make([]model.Model, len(tasks))
   176  	for ix, st := range tasks {
   177  		taskModel := &model.APITask{}
   178  		err = taskModel.BuildFromService(&st)
   179  		if err != nil {
   180  			return []model.Model{}, nil, err
   181  		}
   182  		err = taskModel.BuildFromService(sc.GetURL())
   183  		if err != nil {
   184  			return []model.Model{}, nil, err
   185  		}
   186  		models[ix] = taskModel
   187  	}
   188  	return models, pageResults, nil
   189  }
   190  
   191  func makeNextTasksPage(tasks []task.Task, limit int) *Page {
   192  	var nextPage *Page
   193  	if len(tasks) > limit {
   194  		nextLimit := len(tasks) - limit
   195  		nextPage = &Page{
   196  			Relation: "next",
   197  			Key:      tasks[limit].Id,
   198  			Limit:    nextLimit,
   199  		}
   200  	}
   201  	return nextPage
   202  }
   203  
   204  func makePrevTasksPage(tasks []task.Task) *Page {
   205  	var prevPage *Page
   206  	if len(tasks) > 1 {
   207  		prevPage = &Page{
   208  			Relation: "prev",
   209  			Key:      tasks[0].Id,
   210  			Limit:    len(tasks),
   211  		}
   212  	}
   213  	return prevPage
   214  }
   215  
   216  func (tph *tasksByProjectHandler) Handler() RequestHandler {
   217  	taskPaginationExecutor := &PaginationExecutor{
   218  		KeyQueryParam:   "start_at",
   219  		LimitQueryParam: "limit",
   220  		Paginator:       tasksByProjectPaginator,
   221  
   222  		Args: tasksByProjectArgs{},
   223  	}
   224  
   225  	return &tasksByProjectHandler{taskPaginationExecutor}
   226  }
   227  
   228  // taskGetHandler implements the route GET /task/{task_id}. It fetches the associated
   229  // task and returns it to the user.
   230  type taskGetHandler struct {
   231  	taskId string
   232  }
   233  
   234  // ParseAndValidate fetches the taskId from the http request.
   235  func (tgh *taskGetHandler) ParseAndValidate(r *http.Request) error {
   236  	vars := mux.Vars(r)
   237  	tgh.taskId = vars["task_id"]
   238  	return nil
   239  }
   240  
   241  // Execute calls the servicecontext FindTaskById function and returns the task
   242  // from the provider.
   243  func (tgh *taskGetHandler) Execute(sc servicecontext.ServiceContext) (ResponseData, error) {
   244  	foundTask, err := sc.FindTaskById(tgh.taskId)
   245  	if err != nil {
   246  		if _, ok := err.(*apiv3.APIError); !ok {
   247  			err = errors.Wrap(err, "Database error")
   248  		}
   249  		return ResponseData{}, err
   250  	}
   251  
   252  	taskModel := &model.APITask{}
   253  	err = taskModel.BuildFromService(foundTask)
   254  	if err != nil {
   255  		if _, ok := err.(*apiv3.APIError); !ok {
   256  			err = errors.Wrap(err, "API model error")
   257  		}
   258  		return ResponseData{}, err
   259  	}
   260  
   261  	err = taskModel.BuildFromService(sc.GetURL())
   262  	if err != nil {
   263  		if _, ok := err.(*apiv3.APIError); !ok {
   264  			err = errors.Wrap(err, "API model error")
   265  		}
   266  		return ResponseData{}, err
   267  	}
   268  
   269  	return ResponseData{
   270  		Result: []model.Model{taskModel},
   271  	}, nil
   272  }
   273  
   274  func (trh *taskGetHandler) Handler() RequestHandler {
   275  	return &taskGetHandler{}
   276  }
   277  
   278  type tasksByBuildHandler struct {
   279  	*PaginationExecutor
   280  }
   281  
   282  type tasksByBuildArgs struct {
   283  	buildId string
   284  	status  string
   285  }
   286  
   287  func (tbh *tasksByBuildHandler) ParseAndValidate(r *http.Request) error {
   288  	args := tasksByBuildArgs{
   289  		buildId: mux.Vars(r)["build_id"],
   290  		status:  r.URL.Query().Get("status"),
   291  	}
   292  	if args.buildId == "" {
   293  		return apiv3.APIError{
   294  			Message:    "buildId cannot be empty",
   295  			StatusCode: http.StatusBadRequest,
   296  		}
   297  	}
   298  	tbh.Args = args
   299  	return tbh.PaginationExecutor.ParseAndValidate(r)
   300  }
   301  
   302  func tasksByBuildPaginator(key string, limit int, args interface{}, sc servicecontext.ServiceContext) ([]model.Model,
   303  	*PageResult, error) {
   304  	btArgs, ok := args.(tasksByBuildArgs)
   305  	if !ok {
   306  		panic(incorrectArgsTypeErrorMessage)
   307  	}
   308  	// Fetch all of the tasks to be returned in this page plus the tasks used for
   309  	// calculating information about the next page. Here the limit is multiplied
   310  	// by two to fetch the next page.
   311  	tasks, err := sc.FindTasksByBuildId(btArgs.buildId, key, btArgs.status, limit*2, 1)
   312  	if err != nil {
   313  		if _, ok := err.(*apiv3.APIError); !ok {
   314  			err = errors.Wrap(err, "Database error")
   315  		}
   316  		return []model.Model{}, nil, err
   317  	}
   318  
   319  	// Fetch tasks to get information about the previous page.
   320  	prevTasks, err := sc.FindTasksByBuildId(btArgs.buildId, key, btArgs.status, limit, -1)
   321  	if err != nil {
   322  		if apiErr, ok := err.(*apiv3.APIError); !ok || apiErr.StatusCode != http.StatusNotFound {
   323  			return []model.Model{}, nil, errors.Wrap(err, "Database error")
   324  		}
   325  	}
   326  
   327  	nextPage := makeNextTasksPage(tasks, limit)
   328  	pageResults := &PageResult{
   329  		Next: nextPage,
   330  		Prev: makePrevTasksPage(prevTasks),
   331  	}
   332  
   333  	lastIndex := len(tasks)
   334  	if nextPage != nil {
   335  		lastIndex = limit
   336  	}
   337  
   338  	// Truncate the tasks to just those that will be returned, removing the
   339  	// tasks that would be used to create the next page.
   340  	tasks = tasks[:lastIndex]
   341  
   342  	// Create an array of models which will be returned.
   343  	models := make([]model.Model, len(tasks))
   344  	for ix, st := range tasks {
   345  		taskModel := &model.APITask{}
   346  		// Build an APIModel from the task and place it into the array.
   347  		err = taskModel.BuildFromService(&st)
   348  		if err != nil {
   349  			return []model.Model{}, nil, errors.Wrap(err, "API model error")
   350  		}
   351  		err = taskModel.BuildFromService(sc.GetURL())
   352  		if err != nil {
   353  			return []model.Model{}, nil, errors.Wrap(err, "API model error")
   354  		}
   355  		models[ix] = taskModel
   356  	}
   357  	return models, pageResults, nil
   358  }
   359  
   360  func (hgh *tasksByBuildHandler) Handler() RequestHandler {
   361  	taskPaginationExecutor := &PaginationExecutor{
   362  		KeyQueryParam:   "start_at",
   363  		LimitQueryParam: "limit",
   364  		Paginator:       tasksByBuildPaginator,
   365  
   366  		Args: tasksByBuildArgs{},
   367  	}
   368  
   369  	return &tasksByBuildHandler{taskPaginationExecutor}
   370  }
   371  
   372  // TaskRestartHandler implements the route POST /task/{task_id}/restart. It
   373  // fetches the needed task and project and calls the service function to
   374  // set the proper fields when reseting the task.
   375  type TaskRestartHandler struct {
   376  	taskId  string
   377  	project *serviceModel.Project
   378  
   379  	username string
   380  }
   381  
   382  // ParseAndValidate fetches the taskId and Project from the request context and
   383  // sets them on the TaskRestartHandler to be used by Execute.
   384  func (trh *TaskRestartHandler) ParseAndValidate(r *http.Request) error {
   385  	projCtx := MustHaveProjectContext(r)
   386  	if projCtx.Task == nil {
   387  		return apiv3.APIError{
   388  			Message:    "Task not found",
   389  			StatusCode: http.StatusNotFound,
   390  		}
   391  	}
   392  	trh.taskId = projCtx.Task.Id
   393  	if projCtx.Project == nil {
   394  		return fmt.Errorf("Unable to fetch associated project")
   395  	}
   396  	trh.project = projCtx.Project
   397  	u := MustHaveUser(r)
   398  	trh.username = u.DisplayName()
   399  	return nil
   400  }
   401  
   402  // Execute calls the servicecontext ResetTask function and returns the refreshed
   403  // task from the service.
   404  func (trh *TaskRestartHandler) Execute(sc servicecontext.ServiceContext) (ResponseData, error) {
   405  	err := sc.ResetTask(trh.taskId, trh.username, trh.project)
   406  	if err != nil {
   407  		return ResponseData{},
   408  			apiv3.APIError{
   409  				Message:    err.Error(),
   410  				StatusCode: http.StatusBadRequest,
   411  			}
   412  	}
   413  
   414  	refreshedTask, err := sc.FindTaskById(trh.taskId)
   415  	if err != nil {
   416  		return ResponseData{}, err
   417  	}
   418  
   419  	taskModel := &model.APITask{}
   420  	err = taskModel.BuildFromService(refreshedTask)
   421  	if err != nil {
   422  		if _, ok := err.(*apiv3.APIError); !ok {
   423  			err = errors.Wrap(err, "Database error")
   424  		}
   425  		return ResponseData{}, err
   426  	}
   427  
   428  	return ResponseData{
   429  		Result: []model.Model{taskModel},
   430  	}, nil
   431  }
   432  
   433  func (trh *TaskRestartHandler) Handler() RequestHandler {
   434  	return &TaskRestartHandler{}
   435  }
   436  
   437  // TaskExecutionPatchHandler implements the route PATCH /task/{task_id}. It
   438  // fetches the changes from request, changes in activation and priority, and
   439  // calls out to functions in the servicecontext to change these values.
   440  type TaskExecutionPatchHandler struct {
   441  	Activated *bool  `json:"activated"`
   442  	Priority  *int64 `json:"priority"`
   443  
   444  	user auth.User
   445  	task *task.Task
   446  }
   447  
   448  // ParseAndValidate fetches the needed data from the request and errors otherwise.
   449  // It fetches the task and user from the request context and fetches the changes
   450  // in activation and priority from the request body.
   451  func (tep *TaskExecutionPatchHandler) ParseAndValidate(r *http.Request) error {
   452  	body := util.NewRequestReader(r)
   453  	defer body.Close()
   454  
   455  	decoder := json.NewDecoder(body)
   456  	if err := decoder.Decode(tep); err != nil {
   457  		if err == io.EOF {
   458  			return apiv3.APIError{
   459  				Message:    "No request body sent",
   460  				StatusCode: http.StatusBadRequest,
   461  			}
   462  		}
   463  		if e, ok := err.(*json.UnmarshalTypeError); ok {
   464  			return apiv3.APIError{
   465  				Message: fmt.Sprintf("Incorrect type given, expecting '%s' "+
   466  					"but receieved '%s'",
   467  					e.Type, e.Value),
   468  				StatusCode: http.StatusBadRequest,
   469  			}
   470  		}
   471  		return errors.Wrap(err, "JSON unmarshal error")
   472  	}
   473  
   474  	if tep.Activated == nil && tep.Priority == nil {
   475  		return apiv3.APIError{
   476  			Message:    "Must set 'activated' or 'priority'",
   477  			StatusCode: http.StatusBadRequest,
   478  		}
   479  	}
   480  	projCtx := MustHaveProjectContext(r)
   481  	if projCtx.Task == nil {
   482  		return apiv3.APIError{
   483  			Message:    "Task not found",
   484  			StatusCode: http.StatusNotFound,
   485  		}
   486  	}
   487  
   488  	tep.task = projCtx.Task
   489  	u := MustHaveUser(r)
   490  	tep.user = u
   491  	return nil
   492  }
   493  
   494  // Execute sets the Activated and Priority field of the given task and returns
   495  // an updated version of the task.
   496  func (tep *TaskExecutionPatchHandler) Execute(sc servicecontext.ServiceContext) (ResponseData, error) {
   497  	if tep.Priority != nil {
   498  		priority := *tep.Priority
   499  		if priority > evergreen.MaxTaskPriority &&
   500  			!auth.IsSuperUser(sc.GetSuperUsers(), tep.user) {
   501  			return ResponseData{}, apiv3.APIError{
   502  				Message: fmt.Sprintf("Insufficient privilege to set priority to %d, "+
   503  					"non-superusers can only set priority at or below %d", priority, evergreen.MaxTaskPriority),
   504  				StatusCode: http.StatusForbidden,
   505  			}
   506  		}
   507  		if err := sc.SetTaskPriority(tep.task, priority); err != nil {
   508  			return ResponseData{}, errors.Wrap(err, "Database error")
   509  		}
   510  	}
   511  	if tep.Activated != nil {
   512  		activated := *tep.Activated
   513  		if err := sc.SetTaskActivated(tep.task.Id, tep.user.Username(), activated); err != nil {
   514  			return ResponseData{}, errors.Wrap(err, "Database error")
   515  		}
   516  	}
   517  	refreshedTask, err := sc.FindTaskById(tep.task.Id)
   518  	if err != nil {
   519  		return ResponseData{}, errors.Wrap(err, "Database error")
   520  	}
   521  
   522  	taskModel := &model.APITask{}
   523  	err = taskModel.BuildFromService(refreshedTask)
   524  	if err != nil {
   525  		if _, ok := err.(*apiv3.APIError); !ok {
   526  			err = errors.Wrap(err, "Database error")
   527  		}
   528  		return ResponseData{}, err
   529  	}
   530  
   531  	return ResponseData{
   532  		Result: []model.Model{taskModel},
   533  	}, nil
   534  }
   535  
   536  func (tep *TaskExecutionPatchHandler) Handler() RequestHandler {
   537  	return &TaskExecutionPatchHandler{}
   538  }