github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/service/api.go (about)

     1  package service
     2  
     3  import (
     4  	"crypto/tls"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net"
     8  	"net/http"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/codegangsta/negroni"
    13  	"github.com/evergreen-ci/evergreen"
    14  	"github.com/evergreen-ci/evergreen/apimodels"
    15  	"github.com/evergreen-ci/evergreen/auth"
    16  	"github.com/evergreen-ci/evergreen/cloud/providers"
    17  	"github.com/evergreen-ci/evergreen/db"
    18  	"github.com/evergreen-ci/evergreen/model"
    19  	"github.com/evergreen-ci/evergreen/model/artifact"
    20  	"github.com/evergreen-ci/evergreen/model/event"
    21  	"github.com/evergreen-ci/evergreen/model/host"
    22  	"github.com/evergreen-ci/evergreen/model/task"
    23  	"github.com/evergreen-ci/evergreen/model/version"
    24  	"github.com/evergreen-ci/evergreen/notify"
    25  	"github.com/evergreen-ci/evergreen/plugin"
    26  	"github.com/evergreen-ci/evergreen/rest/route"
    27  	"github.com/evergreen-ci/evergreen/util"
    28  	"github.com/evergreen-ci/evergreen/validator"
    29  	"github.com/evergreen-ci/render"
    30  	"github.com/gorilla/context"
    31  	"github.com/gorilla/mux"
    32  	"github.com/mongodb/grip"
    33  	"github.com/mongodb/grip/message"
    34  	"github.com/pkg/errors"
    35  )
    36  
    37  type (
    38  	taskKey       int
    39  	hostKey       int
    40  	projectKey    int
    41  	projectRefKey int
    42  )
    43  
    44  const (
    45  	apiTaskKey       taskKey       = 0
    46  	apiHostKey       hostKey       = 0
    47  	apiProjectKey    projectKey    = 0
    48  	apiProjectRefKey projectRefKey = 0
    49  
    50  	APIServerLockTitle = evergreen.APIServerTaskActivator
    51  	PatchLockTitle     = "patches"
    52  	TaskStartCaller    = "start task"
    53  	EndTaskCaller      = "end task"
    54  )
    55  
    56  // ErrLockTimeout is returned when the database lock takes too long to be acquired.
    57  var ErrLockTimeout = errors.New("Timed out acquiring global lock")
    58  
    59  // APIServer handles communication with Evergreen agents and other back-end requests.
    60  type APIServer struct {
    61  	*render.Render
    62  	UserManager  auth.UserManager
    63  	Settings     evergreen.Settings
    64  	plugins      []plugin.APIPlugin
    65  	clientConfig *evergreen.ClientConfig
    66  }
    67  
    68  // NewAPIServer returns an APIServer initialized with the given settings and plugins.
    69  func NewAPIServer(settings *evergreen.Settings, plugins []plugin.APIPlugin) (*APIServer, error) {
    70  	authManager, err := auth.LoadUserManager(settings.AuthConfig)
    71  	if err != nil {
    72  		return nil, errors.WithStack(err)
    73  	}
    74  
    75  	clientConfig, err := getClientConfig(settings)
    76  	if err != nil {
    77  		return nil, errors.WithStack(err)
    78  	}
    79  
    80  	as := &APIServer{
    81  		Render:       render.New(render.Options{}),
    82  		UserManager:  authManager,
    83  		Settings:     *settings,
    84  		plugins:      plugins,
    85  		clientConfig: clientConfig,
    86  	}
    87  
    88  	return as, nil
    89  }
    90  
    91  // MustHaveTask gets the task from an HTTP Request.
    92  // Panics if the task is not in request context.
    93  func MustHaveTask(r *http.Request) *task.Task {
    94  	t := GetTask(r)
    95  	if t == nil {
    96  		panic("no task attached to request")
    97  	}
    98  	return t
    99  }
   100  
   101  // MustHaveHost gets the host from the HTTP Request
   102  // Panics if the host is not in the request context
   103  func MustHaveHost(r *http.Request) *host.Host {
   104  	h := GetHost(r)
   105  	if h == nil {
   106  		panic("no host attached to request")
   107  	}
   108  	return h
   109  }
   110  
   111  // MustHaveProject gets the project from the HTTP request and panics
   112  // if there is no project specified
   113  func MustHaveProject(r *http.Request) (*model.ProjectRef, *model.Project) {
   114  	pref, p := GetProject(r)
   115  	if pref == nil || p == nil {
   116  		panic("no project attached to request")
   117  	}
   118  	return pref, p
   119  }
   120  
   121  // GetListener creates a network listener on the given address.
   122  func GetListener(addr string) (net.Listener, error) {
   123  	return net.Listen("tcp", addr)
   124  }
   125  
   126  // GetTLSListener creates an encrypted listener with the given TLS config and address.
   127  func GetTLSListener(addr string, conf *tls.Config) (net.Listener, error) {
   128  	l, err := net.Listen("tcp", addr)
   129  	if err != nil {
   130  		return nil, errors.WithStack(err)
   131  	}
   132  	return tls.NewListener(l, conf), nil
   133  }
   134  
   135  // Serve serves the handler on the given listener.
   136  func Serve(l net.Listener, handler http.Handler) error {
   137  	return (&http.Server{Handler: handler}).Serve(l)
   138  }
   139  
   140  // checkTask get the task from the request header and ensures that there is a task. It checks the secret
   141  // in the header with the secret in the db to ensure that they are the same.
   142  func (as *APIServer) checkTask(checkSecret bool, next http.HandlerFunc) http.HandlerFunc {
   143  	return func(w http.ResponseWriter, r *http.Request) {
   144  		taskId := mux.Vars(r)["taskId"]
   145  		if taskId == "" {
   146  			as.LoggedError(w, r, http.StatusBadRequest, errors.New("missing task id"))
   147  			return
   148  		}
   149  		t, err := task.FindOne(task.ById(taskId))
   150  		if err != nil {
   151  			as.LoggedError(w, r, http.StatusInternalServerError, err)
   152  			return
   153  		}
   154  		if t == nil {
   155  			as.LoggedError(w, r, http.StatusNotFound, errors.New("task not found"))
   156  			return
   157  		}
   158  
   159  		if checkSecret {
   160  			secret := r.Header.Get(evergreen.TaskSecretHeader)
   161  
   162  			// Check the secret - if it doesn't match, write error back to the client
   163  			if secret != t.Secret {
   164  				grip.Errorf("Wrong secret sent for task %s: Expected %s but got %s",
   165  					taskId, t.Secret, secret)
   166  				http.Error(w, "wrong secret!", http.StatusConflict)
   167  				return
   168  			}
   169  		}
   170  
   171  		context.Set(r, apiTaskKey, t)
   172  		// also set the task in the context visible to plugins
   173  		plugin.SetTask(r, t)
   174  		next(w, r)
   175  	}
   176  }
   177  
   178  // checkProject finds the projectId in the request and adds the
   179  // project and project ref to the request context.
   180  func (as *APIServer) checkProject(next http.HandlerFunc) http.HandlerFunc {
   181  	return func(w http.ResponseWriter, r *http.Request) {
   182  		projectId := mux.Vars(r)["projectId"]
   183  		if projectId == "" {
   184  			as.LoggedError(w, r, http.StatusBadRequest, errors.New("missing project Id"))
   185  			return
   186  		}
   187  
   188  		projectRef, err := model.FindOneProjectRef(projectId)
   189  		if err != nil {
   190  			as.LoggedError(w, r, http.StatusInternalServerError, err)
   191  		}
   192  		if projectRef == nil {
   193  			as.LoggedError(w, r, http.StatusNotFound, errors.New("project not found"))
   194  			return
   195  		}
   196  
   197  		p, err := model.FindProject("", projectRef)
   198  		if err != nil {
   199  			as.LoggedError(w, r, http.StatusInternalServerError,
   200  				errors.Wrap(err, "Error getting patch"))
   201  			return
   202  		}
   203  		if p == nil {
   204  			as.LoggedError(w, r, http.StatusNotFound,
   205  				errors.Errorf("can't find project: %s", p.Identifier))
   206  			return
   207  		}
   208  
   209  		context.Set(r, apiProjectRefKey, projectRef)
   210  		context.Set(r, apiProjectKey, p)
   211  		next(w, r)
   212  	}
   213  }
   214  
   215  func (as *APIServer) checkHost(next http.HandlerFunc) http.HandlerFunc {
   216  	return func(w http.ResponseWriter, r *http.Request) {
   217  		hostId := mux.Vars(r)["hostId"]
   218  		if hostId == "" {
   219  			// fall back to the host header if host ids are not part of the path
   220  			hostId = r.Header.Get(evergreen.HostHeader)
   221  			if hostId == "" {
   222  				message := fmt.Sprintf("Request %s is missing host information", r.URL)
   223  				grip.Errorf(message)
   224  				// skip all host logic and just go on to the route
   225  				as.LoggedError(w, r, http.StatusBadRequest, errors.New(message))
   226  				return
   227  			}
   228  		}
   229  		secret := r.Header.Get(evergreen.HostSecretHeader)
   230  
   231  		h, err := host.FindOne(host.ById(hostId))
   232  		if h == nil {
   233  			as.LoggedError(w, r, http.StatusBadRequest, errors.Errorf("Host %v not found", hostId))
   234  			return
   235  		}
   236  		if err != nil {
   237  			as.LoggedError(w, r, http.StatusInternalServerError,
   238  				errors.Wrapf(err, "Error loading context for host %v", hostId))
   239  			return
   240  		}
   241  		// if there is a secret, ensure we are using the correct one -- fail if we arent
   242  		if secret != "" && secret != h.Secret {
   243  			// TODO (EVG-1283) error if secret is not attached as well
   244  			as.LoggedError(w, r, http.StatusConflict, errors.Errorf("Invalid host secret for host %v", h.Id))
   245  			return
   246  		}
   247  
   248  		// if the task is attached to the context, check host-task relationship
   249  		if ctxTask := context.Get(r, apiTaskKey); ctxTask != nil {
   250  			if t, ok := ctxTask.(*task.Task); ok {
   251  				if h.RunningTask != t.Id {
   252  					as.LoggedError(w, r, http.StatusConflict,
   253  						errors.Errorf("Host %v should be running %v, not %v", h.Id, h.RunningTask, t.Id))
   254  					return
   255  				}
   256  			}
   257  		}
   258  		// update host access time
   259  		if err := h.UpdateLastCommunicated(); err != nil {
   260  			grip.Warningf("Could not update host last communication time for %s: %+v", h.Id, err)
   261  		}
   262  
   263  		context.Set(r, apiHostKey, h) // TODO is this worth doing?
   264  		next(w, r)
   265  	}
   266  }
   267  
   268  func (as *APIServer) GetVersion(w http.ResponseWriter, r *http.Request) {
   269  	t := MustHaveTask(r)
   270  
   271  	// Get the version for this task, so we can get its config data
   272  	v, err := version.FindOne(version.ById(t.Version))
   273  	if err != nil {
   274  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   275  		return
   276  	}
   277  
   278  	if v == nil {
   279  		http.Error(w, "version not found", http.StatusNotFound)
   280  		return
   281  	}
   282  
   283  	as.WriteJSON(w, http.StatusOK, v)
   284  }
   285  
   286  func (as *APIServer) GetProjectRef(w http.ResponseWriter, r *http.Request) {
   287  	t := MustHaveTask(r)
   288  
   289  	p, err := model.FindOneProjectRef(t.Project)
   290  
   291  	if err != nil {
   292  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   293  		return
   294  	}
   295  
   296  	if p == nil {
   297  		http.Error(w, "project ref not found", http.StatusNotFound)
   298  		return
   299  	}
   300  
   301  	as.WriteJSON(w, http.StatusOK, p)
   302  }
   303  
   304  // AttachTestLog is the API Server hook for getting
   305  // the test logs and storing them in the test_logs collection.
   306  func (as *APIServer) AttachTestLog(w http.ResponseWriter, r *http.Request) {
   307  	t := MustHaveTask(r)
   308  	log := &model.TestLog{}
   309  	err := util.ReadJSONInto(util.NewRequestReader(r), log)
   310  	if err != nil {
   311  		as.LoggedError(w, r, http.StatusBadRequest, err)
   312  		return
   313  	}
   314  
   315  	// enforce proper taskID and Execution
   316  	log.Task = t.Id
   317  	log.TaskExecution = t.Execution
   318  
   319  	if err := log.Insert(); err != nil {
   320  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   321  		return
   322  	}
   323  	logReply := struct {
   324  		Id string `json:"_id"`
   325  	}{log.Id}
   326  	as.WriteJSON(w, http.StatusOK, logReply)
   327  }
   328  
   329  // AttachResults attaches the received results to the task in the database.
   330  func (as *APIServer) AttachResults(w http.ResponseWriter, r *http.Request) {
   331  	t := MustHaveTask(r)
   332  	results := &task.TestResults{}
   333  	err := util.ReadJSONInto(util.NewRequestReader(r), results)
   334  	if err != nil {
   335  		as.LoggedError(w, r, http.StatusBadRequest, err)
   336  		return
   337  	}
   338  	// set test result of task
   339  	if err := t.SetResults(results.Results); err != nil {
   340  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   341  		return
   342  	}
   343  	as.WriteJSON(w, http.StatusOK, "test results successfully attached")
   344  }
   345  
   346  // FetchProjectVars is an API hook for returning the project variables
   347  // associated with a task's project.
   348  func (as *APIServer) FetchProjectVars(w http.ResponseWriter, r *http.Request) {
   349  	t := MustHaveTask(r)
   350  	projectVars, err := model.FindOneProjectVars(t.Project)
   351  	if err != nil {
   352  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   353  		return
   354  	}
   355  	if projectVars == nil {
   356  		as.WriteJSON(w, http.StatusOK, apimodels.ExpansionVars{})
   357  		return
   358  	}
   359  
   360  	as.WriteJSON(w, http.StatusOK, projectVars.Vars)
   361  }
   362  
   363  // AttachFiles updates file mappings for a task or build
   364  func (as *APIServer) AttachFiles(w http.ResponseWriter, r *http.Request) {
   365  	t := MustHaveTask(r)
   366  	grip.Infoln("Attaching files to task:", t.Id)
   367  
   368  	entry := &artifact.Entry{
   369  		TaskId:          t.Id,
   370  		TaskDisplayName: t.DisplayName,
   371  		BuildId:         t.BuildId,
   372  	}
   373  
   374  	err := util.ReadJSONInto(util.NewRequestReader(r), &entry.Files)
   375  	if err != nil {
   376  		message := fmt.Sprintf("Error reading file definitions for task  %v: %v", t.Id, err)
   377  		grip.Error(message)
   378  		as.WriteJSON(w, http.StatusBadRequest, message)
   379  		return
   380  	}
   381  
   382  	if err := entry.Upsert(); err != nil {
   383  		message := fmt.Sprintf("Error updating artifact file info for task %v: %v", t.Id, err)
   384  		grip.Error(message)
   385  		as.WriteJSON(w, http.StatusInternalServerError, message)
   386  		return
   387  	}
   388  	as.WriteJSON(w, http.StatusOK, fmt.Sprintf("Artifact files for task %v successfully attached", t.Id))
   389  }
   390  
   391  // AppendTaskLog appends the received logs to the task's internal logs.
   392  func (as *APIServer) AppendTaskLog(w http.ResponseWriter, r *http.Request) {
   393  	t := MustHaveTask(r)
   394  	taskLog := &model.TaskLog{}
   395  	if err := util.ReadJSONInto(util.NewRequestReader(r), taskLog); err != nil {
   396  		http.Error(w, "unable to read logs from request", http.StatusBadRequest)
   397  		return
   398  	}
   399  
   400  	taskLog.TaskId = t.Id
   401  	taskLog.Execution = t.Execution
   402  
   403  	if err := taskLog.Insert(); err != nil {
   404  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   405  		return
   406  	}
   407  
   408  	as.WriteJSON(w, http.StatusOK, "Logs added")
   409  }
   410  
   411  // FetchTask loads the task from the database and sends it to the requester.
   412  func (as *APIServer) FetchTask(w http.ResponseWriter, r *http.Request) {
   413  	t := MustHaveTask(r)
   414  	as.WriteJSON(w, http.StatusOK, t)
   415  }
   416  
   417  // Heartbeat handles heartbeat pings from Evergreen agents. If the heartbeating
   418  // task is marked to be aborted, the abort response is sent.
   419  func (as *APIServer) Heartbeat(w http.ResponseWriter, r *http.Request) {
   420  	t := MustHaveTask(r)
   421  
   422  	heartbeatResponse := apimodels.HeartbeatResponse{}
   423  	if t.Aborted {
   424  		grip.Noticef("Sending abort signal for task %s", t.Id)
   425  		heartbeatResponse.Abort = true
   426  	}
   427  
   428  	if err := t.UpdateHeartbeat(); err != nil {
   429  		grip.Warningf("Error updating heartbeat for task %s: %+v", t.Id, err)
   430  	}
   431  	as.WriteJSON(w, http.StatusOK, heartbeatResponse)
   432  }
   433  
   434  // TaskSystemInfo is the handler for the system info collector, which
   435  // reads grip/message.SystemInfo objects from the request body.
   436  func (as *APIServer) TaskSystemInfo(w http.ResponseWriter, r *http.Request) {
   437  	t := MustHaveTask(r)
   438  	info := &message.SystemInfo{}
   439  
   440  	if err := util.ReadJSONInto(util.NewRequestReader(r), info); err != nil {
   441  		as.LoggedError(w, r, http.StatusBadRequest, err)
   442  		return
   443  	}
   444  
   445  	event.LogTaskSystemData(t.Id, info)
   446  
   447  	as.WriteJSON(w, http.StatusOK, struct{}{})
   448  }
   449  
   450  // TaskProcessInfo is the handler for the process info collector, which
   451  // reads slices of grip/message.ProcessInfo objects from the request body.
   452  func (as *APIServer) TaskProcessInfo(w http.ResponseWriter, r *http.Request) {
   453  	t := MustHaveTask(r)
   454  	procs := []*message.ProcessInfo{}
   455  
   456  	if err := util.ReadJSONInto(util.NewRequestReader(r), &procs); err != nil {
   457  		as.LoggedError(w, r, http.StatusBadRequest, err)
   458  		return
   459  	}
   460  
   461  	event.LogTaskProcessData(t.Id, procs)
   462  	as.WriteJSON(w, http.StatusOK, struct{}{})
   463  }
   464  
   465  func home(w http.ResponseWriter, r *http.Request) {
   466  	fmt.Fprintf(w, "Welcome to the API server's home :)\n")
   467  }
   468  
   469  func (as *APIServer) serviceStatusWithAuth(w http.ResponseWriter, r *http.Request) {
   470  	out := struct {
   471  		BuildId    string              `json:"build_revision"`
   472  		SystemInfo *message.SystemInfo `json:"sys_info"`
   473  		Pid        int                 `json:"pid"`
   474  	}{
   475  		BuildId:    evergreen.BuildRevision,
   476  		SystemInfo: message.CollectSystemInfo().(*message.SystemInfo),
   477  		Pid:        os.Getpid(),
   478  	}
   479  
   480  	as.WriteJSON(w, http.StatusOK, &out)
   481  }
   482  
   483  func (as *APIServer) serviceStatusSimple(w http.ResponseWriter, r *http.Request) {
   484  	out := struct {
   485  		BuildId string `json:"build_revision"`
   486  	}{
   487  		BuildId: evergreen.BuildRevision,
   488  	}
   489  
   490  	as.WriteJSON(w, http.StatusOK, &out)
   491  }
   492  
   493  // GetTask loads the task attached to a request.
   494  func GetTask(r *http.Request) *task.Task {
   495  	if rv := context.Get(r, apiTaskKey); rv != nil {
   496  		return rv.(*task.Task)
   497  	}
   498  	return nil
   499  }
   500  
   501  // GetHost loads the host attached to a request
   502  func GetHost(r *http.Request) *host.Host {
   503  	if rv := context.Get(r, apiHostKey); rv != nil {
   504  		return rv.(*host.Host)
   505  	}
   506  	return nil
   507  }
   508  
   509  // GetProject loads the project attached to a request into request
   510  // context.
   511  func GetProject(r *http.Request) (*model.ProjectRef, *model.Project) {
   512  	pref := context.Get(r, apiProjectRefKey)
   513  	if pref == nil {
   514  		return nil, nil
   515  	}
   516  
   517  	p := context.Get(r, apiProjectKey)
   518  	if p == nil {
   519  		return nil, nil
   520  	}
   521  
   522  	return pref.(*model.ProjectRef), p.(*model.Project)
   523  }
   524  
   525  func (as *APIServer) getUserSession(w http.ResponseWriter, r *http.Request) {
   526  	userCredentials := struct {
   527  		Username string `json:"username"`
   528  		Password string `json:"password"`
   529  	}{}
   530  
   531  	if err := util.ReadJSONInto(util.NewRequestReader(r), &userCredentials); err != nil {
   532  		as.LoggedError(w, r, http.StatusBadRequest, errors.Wrap(err, "Error reading user credentials"))
   533  		return
   534  	}
   535  	userToken, err := as.UserManager.CreateUserToken(userCredentials.Username, userCredentials.Password)
   536  	if err != nil {
   537  		as.WriteJSON(w, http.StatusUnauthorized, err.Error())
   538  		return
   539  	}
   540  
   541  	dataOut := struct {
   542  		User struct {
   543  			Name string `json:"name"`
   544  		} `json:"user"`
   545  		Token string `json:"token"`
   546  	}{}
   547  	dataOut.User.Name = userCredentials.Username
   548  	dataOut.Token = userToken
   549  	as.WriteJSON(w, http.StatusOK, dataOut)
   550  
   551  }
   552  
   553  // Get the host with the id specified in the request
   554  func getHostFromRequest(r *http.Request) (*host.Host, error) {
   555  	// get id and secret from the request.
   556  	vars := mux.Vars(r)
   557  	tag := vars["tag"]
   558  	if len(tag) == 0 {
   559  		return nil, errors.New("no host tag supplied")
   560  	}
   561  	// find the host
   562  	host, err := host.FindOne(host.ById(tag))
   563  	if host == nil {
   564  		return nil, errors.Errorf("no host with tag: %v", tag)
   565  	}
   566  	if err != nil {
   567  		return nil, err
   568  	}
   569  	return host, nil
   570  }
   571  
   572  func (as *APIServer) hostReady(w http.ResponseWriter, r *http.Request) {
   573  	hostObj, err := getHostFromRequest(r)
   574  	if err != nil {
   575  		grip.Error(err)
   576  		http.Error(w, err.Error(), http.StatusBadRequest)
   577  		return
   578  	}
   579  
   580  	// if the host failed
   581  	setupSuccess := mux.Vars(r)["status"]
   582  	if setupSuccess == evergreen.HostStatusFailed {
   583  		grip.Infof("Initializing host %s failed", hostObj.Id)
   584  		// send notification to the Evergreen team about this provisioning failure
   585  		subject := fmt.Sprintf("%v Evergreen provisioning failure on %v", notify.ProvisionFailurePreface, hostObj.Distro.Id)
   586  
   587  		hostLink := fmt.Sprintf("%v/host/%v", as.Settings.Ui.Url, hostObj.Id)
   588  		message := fmt.Sprintf("Provisioning failed on %v host -- %v (%v). %v",
   589  			hostObj.Distro.Id, hostObj.Id, hostObj.Host, hostLink)
   590  		if err = notify.NotifyAdmins(subject, message, &as.Settings); err != nil {
   591  			grip.Errorln("Error sending email:", err)
   592  		}
   593  
   594  		// get/store setup logs
   595  		var setupLog []byte
   596  		body := util.NewRequestReader(r)
   597  		defer body.Close()
   598  
   599  		setupLog, err = ioutil.ReadAll(body)
   600  		if err != nil {
   601  			as.LoggedError(w, r, http.StatusInternalServerError, err)
   602  			return
   603  		}
   604  
   605  		event.LogProvisionFailed(hostObj.Id, string(setupLog))
   606  
   607  		err = hostObj.SetUnprovisioned()
   608  		if err != nil {
   609  			as.LoggedError(w, r, http.StatusInternalServerError, err)
   610  			return
   611  		}
   612  
   613  		as.WriteJSON(w, http.StatusOK, fmt.Sprintf("Initializing host %v failed", hostObj.Id))
   614  		return
   615  	}
   616  
   617  	cloudManager, err := providers.GetCloudManager(hostObj.Provider, &as.Settings)
   618  	if err != nil {
   619  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   620  		subject := fmt.Sprintf("%v Evergreen provisioning completion failure on %v",
   621  			notify.ProvisionFailurePreface, hostObj.Distro.Id)
   622  		message := fmt.Sprintf("Failed to get cloud manager for host %v with provider %v: %v",
   623  			hostObj.Id, hostObj.Provider, err)
   624  		if err = notify.NotifyAdmins(subject, message, &as.Settings); err != nil {
   625  			grip.Errorln("Error sending email:", err)
   626  		}
   627  		return
   628  	}
   629  
   630  	dns, err := cloudManager.GetDNSName(hostObj)
   631  	if err != nil {
   632  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   633  		return
   634  	}
   635  
   636  	// mark host as provisioned
   637  	if err := hostObj.MarkAsProvisioned(); err != nil {
   638  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   639  		return
   640  	}
   641  
   642  	grip.Infof("Successfully marked host '%s' with dns '%s' as provisioned", hostObj.Id, dns)
   643  }
   644  
   645  // fetchProjectRef returns a project ref given the project identifier
   646  func (as *APIServer) fetchProjectRef(w http.ResponseWriter, r *http.Request) {
   647  	vars := mux.Vars(r)
   648  	id := vars["identifier"]
   649  	projectRef, err := model.FindOneProjectRef(id)
   650  	if err != nil {
   651  		as.LoggedError(w, r, http.StatusInternalServerError, err)
   652  		return
   653  	}
   654  	if projectRef == nil {
   655  		http.Error(w, fmt.Sprintf("no project found named '%v'", id), http.StatusNotFound)
   656  		return
   657  	}
   658  	as.WriteJSON(w, http.StatusOK, projectRef)
   659  }
   660  
   661  func (as *APIServer) listProjects(w http.ResponseWriter, r *http.Request) {
   662  	allProjs, err := model.FindAllTrackedProjectRefs()
   663  	if err != nil {
   664  		http.Error(w, err.Error(), http.StatusInternalServerError)
   665  		return
   666  	}
   667  	as.WriteJSON(w, http.StatusOK, allProjs)
   668  }
   669  
   670  func (as *APIServer) listTasks(w http.ResponseWriter, r *http.Request) {
   671  	_, project := MustHaveProject(r)
   672  
   673  	// zero out the depends on and commands fields because they are
   674  	// unnecessary and may not get marshaled properly
   675  	for i := range project.Tasks {
   676  		project.Tasks[i].DependsOn = []model.TaskDependency{}
   677  		project.Tasks[i].Commands = []model.PluginCommandConf{}
   678  
   679  	}
   680  	as.WriteJSON(w, http.StatusOK, project.Tasks)
   681  }
   682  func (as *APIServer) listVariants(w http.ResponseWriter, r *http.Request) {
   683  	_, project := MustHaveProject(r)
   684  
   685  	as.WriteJSON(w, http.StatusOK, project.BuildVariants)
   686  }
   687  
   688  // validateProjectConfig returns a slice containing a list of any errors
   689  // found in validating the given project configuration
   690  func (as *APIServer) validateProjectConfig(w http.ResponseWriter, r *http.Request) {
   691  	body := util.NewRequestReader(r)
   692  	defer body.Close()
   693  	yamlBytes, err := ioutil.ReadAll(body)
   694  	if err != nil {
   695  		as.WriteJSON(w, http.StatusBadRequest, fmt.Sprintf("Error reading request body: %v", err))
   696  		return
   697  	}
   698  
   699  	project := &model.Project{}
   700  	validationErr := validator.ValidationError{}
   701  	if err = model.LoadProjectInto(yamlBytes, "", project); err != nil {
   702  		validationErr.Message = err.Error()
   703  		as.WriteJSON(w, http.StatusBadRequest, []validator.ValidationError{validationErr})
   704  		return
   705  	}
   706  	syntaxErrs, err := validator.CheckProjectSyntax(project)
   707  	if err != nil {
   708  		http.Error(w, err.Error(), http.StatusInternalServerError)
   709  		return
   710  	}
   711  	semanticErrs := validator.CheckProjectSemantics(project)
   712  	if len(syntaxErrs)+len(semanticErrs) != 0 {
   713  		as.WriteJSON(w, http.StatusBadRequest, append(syntaxErrs, semanticErrs...))
   714  		return
   715  	}
   716  	as.WriteJSON(w, http.StatusOK, []validator.ValidationError{})
   717  }
   718  
   719  // getGlobalLock attempts to acquire the global lock and takes in
   720  // client - a remote address of what is trying to get the global lock
   721  // taskId, and caller, which is the function that is called.
   722  func getGlobalLock(client, taskId, caller string) bool {
   723  	grip.Debugf("Attempting to acquire global lock for %s (remote addr: %s) with caller %s", taskId, client, caller)
   724  
   725  	lockAcquired, err := db.WaitTillAcquireGlobalLock(client, db.LockTimeout)
   726  	if err != nil {
   727  		grip.Errorf("Error acquiring global lock for %s (remote addr: %s) with caller %s: %+v", taskId, client, caller, err)
   728  		return false
   729  	}
   730  	if !lockAcquired {
   731  		grip.Errorf("Timed out attempting to acquire global lock for %s (remote addr: %s) with caller %s", taskId, client, caller)
   732  		return false
   733  	}
   734  
   735  	grip.Debugf("Acquired global lock for %s (remote addr: %s) with caller %s", taskId, client, caller)
   736  	return true
   737  }
   738  
   739  // helper function for releasing the global lock
   740  func releaseGlobalLock(client, taskId, caller string) {
   741  	grip.Debugf("Attempting to release global lock for %s (remote addr: %s) with caller %s", taskId, client, caller)
   742  	if err := db.ReleaseGlobalLock(client); err != nil {
   743  		grip.Errorf("Error releasing global lock for %s (remote addr: %s) with caller %s - this is really bad: %s", taskId, client, caller, err)
   744  	}
   745  	grip.Debugf("Released global lock for %s (remote addr: %s) with caller %s", taskId, client, caller)
   746  }
   747  
   748  // LoggedError logs the given error and writes an HTTP response with its details formatted
   749  // as JSON if the request headers indicate that it's acceptable (or plaintext otherwise).
   750  func (as *APIServer) LoggedError(w http.ResponseWriter, r *http.Request, code int, err error) {
   751  	grip.Errorln(r.Method, r.URL, err)
   752  	// if JSON is the preferred content type for the request, reply with a json message
   753  	if strings.HasPrefix(r.Header.Get("accept"), "application/json") {
   754  		as.WriteJSON(w, code, struct {
   755  			Error string `json:"error"`
   756  		}{err.Error()})
   757  	} else {
   758  		// Not a JSON request, so write plaintext.
   759  		http.Error(w, err.Error(), code)
   760  	}
   761  }
   762  
   763  // Returns information about available updates for client binaries.
   764  // Replies 404 if this data is not configured.
   765  func (as *APIServer) getUpdate(w http.ResponseWriter, r *http.Request) {
   766  	as.WriteJSON(w, http.StatusOK, as.clientConfig)
   767  }
   768  
   769  // GetSettings returns the global evergreen settings.
   770  func (as *APIServer) GetSettings() evergreen.Settings {
   771  	return as.Settings
   772  }
   773  
   774  // Handler returns the root handler for all APIServer endpoints.
   775  func (as *APIServer) Handler() (http.Handler, error) {
   776  	root := mux.NewRouter()
   777  
   778  	// attaches the /rest/v1 routes
   779  	AttachRESTHandler(root, as)
   780  	// attaches /rest/v2 routes
   781  	route.AttachHandler(root, as.Settings.SuperUsers, as.Settings.ApiUrl, evergreen.RestRoutePrefix)
   782  
   783  	r := root.PathPrefix("/api/2/").Subrouter()
   784  	r.HandleFunc("/", home)
   785  
   786  	apiRootOld := root.PathPrefix("/api/").Subrouter()
   787  
   788  	// Project lookup and validation routes
   789  	apiRootOld.HandleFunc("/ref/{identifier:[\\w_\\-\\@.]+}", as.fetchProjectRef)
   790  	apiRootOld.HandleFunc("/validate", as.validateProjectConfig).Methods("POST")
   791  	apiRootOld.HandleFunc("/projects", requireUser(as.listProjects, nil)).Methods("GET")
   792  	apiRootOld.HandleFunc("/tasks/{projectId}", requireUser(as.checkProject(as.listTasks), nil)).Methods("GET")
   793  	apiRootOld.HandleFunc("/variants/{projectId}", requireUser(as.checkProject(as.listVariants), nil)).Methods("GET")
   794  
   795  	// Task Queue routes
   796  	apiRootOld.HandleFunc("/task_queue", as.getTaskQueueSizes).Methods("GET")
   797  	apiRootOld.HandleFunc("/task_queue_limit", as.checkTaskQueueSize).Methods("GET")
   798  
   799  	// Client auto-update routes
   800  	apiRootOld.HandleFunc("/update", as.getUpdate).Methods("GET")
   801  
   802  	// User session routes
   803  	apiRootOld.HandleFunc("/token", as.getUserSession).Methods("POST")
   804  
   805  	// Patches
   806  	patchPath := apiRootOld.PathPrefix("/patches").Subrouter()
   807  	patchPath.HandleFunc("/", requireUser(as.submitPatch, nil)).Methods("PUT")
   808  	patchPath.HandleFunc("/mine", requireUser(as.listPatches, nil)).Methods("GET")
   809  	patchPath.HandleFunc("/{patchId:\\w+}", requireUser(as.summarizePatch, nil)).Methods("GET")
   810  	patchPath.HandleFunc("/{patchId:\\w+}", requireUser(as.existingPatchRequest, nil)).Methods("POST")
   811  	patchPath.HandleFunc("/{patchId:\\w+}/{projectId}/modules", requireUser(as.checkProject(as.listPatchModules), nil)).Methods("GET")
   812  	patchPath.HandleFunc("/{patchId:\\w+}/modules", requireUser(as.deletePatchModule, nil)).Methods("DELETE")
   813  	patchPath.HandleFunc("/{patchId:\\w+}/modules", requireUser(as.updatePatchModule, nil)).Methods("POST")
   814  
   815  	// Routes for operating on existing spawn hosts - get info, terminate, etc.
   816  	spawn := apiRootOld.PathPrefix("/spawn/").Subrouter()
   817  	spawn.HandleFunc("/{instance_id:[\\w_\\-\\@]+}/", requireUser(as.hostInfo, nil)).Methods("GET")
   818  	spawn.HandleFunc("/{instance_id:[\\w_\\-\\@]+}/", requireUser(as.modifyHost, nil)).Methods("POST")
   819  	spawn.HandleFunc("/ready/{instance_id:[\\w_\\-\\@]+}/{status}", requireUser(as.spawnHostReady, nil)).Methods("POST")
   820  
   821  	runtimes := apiRootOld.PathPrefix("/runtimes/").Subrouter()
   822  	runtimes.HandleFunc("/", as.listRuntimes).Methods("GET")
   823  	runtimes.HandleFunc("/timeout/{seconds:\\d*}", as.lateRuntimes).Methods("GET")
   824  
   825  	// Internal status
   826  	status := apiRootOld.PathPrefix("/status/").Subrouter()
   827  	status.HandleFunc("/consistent_task_assignment", as.consistentTaskAssignment).Methods("GET")
   828  	status.HandleFunc("/info", requireUser(as.serviceStatusWithAuth, as.serviceStatusSimple)).Methods("GET")
   829  	status.HandleFunc("/stuck_hosts", as.getStuckHosts).Methods("GET")
   830  
   831  	// Hosts callback
   832  	host := r.PathPrefix("/host/{tag:[\\w_\\-\\@]+}/").Subrouter()
   833  	host.HandleFunc("/ready/{status}", as.hostReady).Methods("POST")
   834  
   835  	// Spawnhost routes - creating new hosts, listing existing hosts, listing distros
   836  	spawns := apiRootOld.PathPrefix("/spawns/").Subrouter()
   837  	spawns.HandleFunc("/", requireUser(as.requestHost, nil)).Methods("PUT")
   838  	spawns.HandleFunc("/{user}/", requireUser(as.hostsInfoForUser, nil)).Methods("GET")
   839  	spawns.HandleFunc("/distros/list/", requireUser(as.listDistros, nil)).Methods("GET")
   840  
   841  	// Agent routes
   842  	agentRouter := r.PathPrefix("/agent").Subrouter()
   843  	agentRouter.HandleFunc("/next_task", as.checkHost(as.NextTask)).Methods("GET")
   844  
   845  	taskRouter := r.PathPrefix("/task/{taskId}").Subrouter()
   846  
   847  	taskRouter.HandleFunc("/end", as.checkTask(true, as.checkHost(as.EndTask))).Methods("POST")
   848  	taskRouter.HandleFunc("/start", as.checkTask(true, as.checkHost(as.StartTask))).Methods("POST")
   849  	taskRouter.HandleFunc("/new_end", as.checkTask(true, as.checkHost(as.EndTask))).Methods("POST")
   850  	taskRouter.HandleFunc("/new_start", as.checkTask(true, as.checkHost(as.StartTask))).Methods("POST")
   851  
   852  	taskRouter.HandleFunc("/log", as.checkTask(true, as.checkHost(as.AppendTaskLog))).Methods("POST")
   853  	taskRouter.HandleFunc("/heartbeat", as.checkTask(true, as.checkHost(as.Heartbeat))).Methods("POST")
   854  	taskRouter.HandleFunc("/results", as.checkTask(true, as.checkHost(as.AttachResults))).Methods("POST")
   855  	taskRouter.HandleFunc("/test_logs", as.checkTask(true, as.checkHost(as.AttachTestLog))).Methods("POST")
   856  	taskRouter.HandleFunc("/files", as.checkTask(false, as.checkHost(as.AttachFiles))).Methods("POST")
   857  	taskRouter.HandleFunc("/system_info", as.checkTask(true, as.checkHost(as.TaskSystemInfo))).Methods("POST")
   858  	taskRouter.HandleFunc("/process_info", as.checkTask(true, as.checkHost(as.TaskProcessInfo))).Methods("POST")
   859  	taskRouter.HandleFunc("/distro", as.checkTask(false, as.GetDistro)).Methods("GET")
   860  	taskRouter.HandleFunc("/", as.checkTask(true, as.FetchTask)).Methods("GET")
   861  	taskRouter.HandleFunc("/version", as.checkTask(false, as.GetVersion)).Methods("GET")
   862  	taskRouter.HandleFunc("/project_ref", as.checkTask(false, as.GetProjectRef)).Methods("GET")
   863  	taskRouter.HandleFunc("/fetch_vars", as.checkTask(true, as.FetchProjectVars)).Methods("GET")
   864  
   865  	// Install plugin routes
   866  	for _, pl := range as.plugins {
   867  		if pl == nil {
   868  			continue
   869  		}
   870  		pluginSettings := as.Settings.Plugins[pl.Name()]
   871  		err := pl.Configure(pluginSettings)
   872  		if err != nil {
   873  			return nil, errors.Wrapf(err, "Failed to configure plugin %s", pl.Name())
   874  		}
   875  		handler := pl.GetAPIHandler()
   876  		if handler == nil {
   877  			grip.Warningf("no API handlers to install for %s plugin", pl.Name())
   878  			continue
   879  		}
   880  		grip.Debugf("Installing API handlers for %s plugin", pl.Name())
   881  		util.MountHandler(taskRouter, fmt.Sprintf("/%s/", pl.Name()), as.checkTask(false, handler.ServeHTTP))
   882  	}
   883  
   884  	n := negroni.New()
   885  	n.Use(NewLogger())
   886  	n.Use(negroni.HandlerFunc(UserMiddleware(as.UserManager)))
   887  	n.UseHandler(root)
   888  	return n, nil
   889  }