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 }