func TestCheckProjectSemantics(t *testing.T) { Convey("When validating a project's semantics", t, func() { Convey("if the project passes all of the validation funcs, no errors"+ " should be returned", func() { distros := []distro.Distro{ {Id: "test-distro-one"}, {Id: "test-distro-two"}, } for _, d := range distros { So(d.Insert(), ShouldBeNil) } projectRef := &model.ProjectRef{ Identifier: "project_test", LocalConfig: "test: testing", } project, err := model.FindProject("", projectRef) So(err, ShouldBeNil) So(CheckProjectSemantics(project), ShouldResemble, []ValidationError{}) }) Reset(func() { db.Clear(distro.Collection) }) }) }
func TestCheckProjectSyntax(t *testing.T) { Convey("When validating a project's syntax", t, func() { Convey("if the project passes all of the validation funcs, no errors"+ " should be returned", func() { distros := []distro.Distro{ {Id: "test-distro-one"}, {Id: "test-distro-two"}, } err := testutil.CreateTestLocalConfig(projectValidatorConf, "project_test", "") So(err, ShouldBeNil) projectRef, err := model.FindOneProjectRef("project_test") So(err, ShouldBeNil) for _, d := range distros { So(d.Insert(), ShouldBeNil) } project, err := model.FindProject("", projectRef) So(err, ShouldBeNil) So(CheckProjectSyntax(project), ShouldResemble, []ValidationError{}) }) Reset(func() { db.Clear(distro.Collection) }) }) }
// reachedFailureLimit returns true if task for the previous failure transition alert // happened too long ago, as determined by some magic math. func reachedFailureLimit(taskId string) (bool, error) { t, err := task.FindOne(task.ById(taskId)) if err != nil { return false, err } if t == nil { return false, fmt.Errorf("task %v not found", taskId) } pr, err := model.FindOneProjectRef(t.Project) if err != nil { return false, err } if pr == nil { return false, fmt.Errorf("project ref %v not found", t.Project) } p, err := model.FindProject(t.Revision, pr) if err != nil { return false, err } if p == nil { return false, fmt.Errorf("project %v not found for revision %v", t.Project, t.Revision) } v := p.FindBuildVariant(t.BuildVariant) if v == nil { return false, fmt.Errorf("build variant %v does not exist in project", t.BuildVariant) } batchTime := pr.GetBatchTime(v) reached := time.Since(t.FinishTime) > (time.Duration(batchTime) * time.Minute * failureLimitMultiplier) return reached, nil }
// LoadProjectContext builds a projectContext from vars in the request's URL. // This is done by reading in specific variables and inferring other required // context variables when necessary (e.g. loading a project based on the task). func (uis *UIServer) LoadProjectContext(rw http.ResponseWriter, r *http.Request) (projectContext, error) { dbUser := GetUser(r) vars := mux.Vars(r) taskId := vars["task_id"] buildId := vars["build_id"] versionId := vars["version_id"] patchId := vars["patch_id"] projectId := uis.getRequestProjectId(r) pc := projectContext{AuthRedirect: uis.UserManager.IsRedirect()} isSuperUser := (dbUser != nil) && auth.IsSuperUser(uis.Settings, dbUser) err := pc.populateProjectRefs(dbUser != nil, isSuperUser, dbUser) if err != nil { return pc, err } // If we still don't have a default projectId, just use the first project in the list // if there is one. if len(projectId) == 0 && len(pc.AllProjects) > 0 { projectId = pc.AllProjects[0].Identifier } // Build a model.Context using the data available. ctx, err := model.LoadContext(taskId, buildId, versionId, patchId, projectId) pc.Context = ctx if err != nil { return pc, err } // set the cookie for the next request if a project was found if ctx.ProjectRef != nil { ctx.Project, err = model.FindProject("", ctx.ProjectRef) if err != nil { return pc, err } // A project was found, update the project cookie for subsequent request. http.SetCookie(rw, &http.Cookie{ Name: ProjectCookieName, Value: ctx.ProjectRef.Identifier, Path: "/", Expires: time.Now().Add(7 * 24 * time.Hour), }) } if len(uis.GetAppPlugins()) > 0 { pluginNames := []string{} for _, p := range uis.GetAppPlugins() { pluginNames = append(pluginNames, p.Name()) } pc.PluginNames = pluginNames } return pc, nil }
func (as *APIServer) listVariants(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["projectId"] projectRef, err := model.FindOneProjectRef(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } project, err := model.FindProject("", projectRef) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } as.WriteJSON(w, http.StatusOK, project.BuildVariants) }
func (uis *UIServer) taskTimingPage(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) if projCtx.Project == nil { http.Error(w, "not found", http.StatusNotFound) } type UIBuildVariant struct { Name string TaskNames []string } type UIProject struct { Name string BuildVariants []UIBuildVariant } var allProjects []UIProject for _, p := range projCtx.AllProjects { proj, err := model.FindProject("", &p) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } uip := UIProject{p.Identifier, []UIBuildVariant{}} for _, bv := range projCtx.Project.BuildVariants { newBv := UIBuildVariant{bv.Name, []string{}} for _, task := range proj.Tasks { newBv.TaskNames = append(newBv.TaskNames, task.Name) } uip.BuildVariants = append(uip.BuildVariants, newBv) } allProjects = append(allProjects, uip) } newProject := UIProject{projCtx.Project.Identifier, []UIBuildVariant{}} data := struct { ProjectData projectContext User *user.DBUser Project UIProject AllProjects []UIProject }{projCtx, GetUser(r), newProject, allProjects} uis.WriteHTML(w, http.StatusOK, data, "base", "task_timing.html", "base_angular.html", "menu.html") }
// Construct a map of project names to build variants for that project func findProjectBuildVariants(configName string) (map[string][]string, error) { projectNameToBuildVariants := make(map[string][]string) allProjects, err := model.FindAllTrackedProjectRefs() if err != nil { return nil, err } for _, projectRef := range allProjects { if !projectRef.Enabled { continue } var buildVariants []string var proj *model.Project var err error if projectRef.LocalConfig != "" { proj, err = model.FindProject("", &projectRef) if err != nil { return nil, fmt.Errorf("unable to find project file: %v", err) } } else { lastGood, err := version.FindOne(version.ByLastKnownGoodConfig(projectRef.Identifier)) if err != nil { return nil, fmt.Errorf("unable to find last valid config: %v", err) } if lastGood == nil { // brand new project + no valid config yet, just return an empty map return projectNameToBuildVariants, nil } proj = &model.Project{} err = model.LoadProjectInto([]byte(lastGood.Config), projectRef.Identifier, proj) if err != nil { return nil, fmt.Errorf("error loading project '%v' from version: %v", projectRef.Identifier, err) } } for _, buildVariant := range proj.BuildVariants { buildVariants = append(buildVariants, buildVariant.Name) } projectNameToBuildVariants[projectRef.Identifier] = buildVariants } return projectNameToBuildVariants, nil }
func (restapi restAPI) getTaskHistory(w http.ResponseWriter, r *http.Request) { taskName := mux.Vars(r)["task_name"] projectName := r.FormValue("project") projectRef, err := model.FindOneProjectRef(projectName) if err != nil || projectRef == nil { msg := fmt.Sprintf("Error finding project '%v'", projectName) statusCode := http.StatusNotFound if err != nil { evergreen.Logger.Logf(slogger.ERROR, "%v: %v", msg, err) statusCode = http.StatusInternalServerError } restapi.WriteJSON(w, statusCode, responseError{Message: msg}) return } project, err := model.FindProject("", projectRef) if err != nil { msg := fmt.Sprintf("Error finding project '%v'", projectName) evergreen.Logger.Logf(slogger.ERROR, "%v: %v", msg, err) restapi.WriteJSON(w, http.StatusInternalServerError, responseError{Message: msg}) return } buildVariants := project.GetVariantsWithTask(taskName) iter := model.NewTaskHistoryIterator(taskName, buildVariants, project.Identifier) chunk, err := iter.GetChunk(nil, MaxNumRevisions, NoRevisions, false) if err != nil { msg := fmt.Sprintf("Error finding history for task '%v'", taskName) evergreen.Logger.Logf(slogger.ERROR, "%v: %v", msg, err) restapi.WriteJSON(w, http.StatusInternalServerError, responseError{Message: msg}) return } restapi.WriteJSON(w, http.StatusOK, chunk) return }
func (as *APIServer) listTasks(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["projectId"] projectRef, err := model.FindOneProjectRef(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } project, err := model.FindProject("", projectRef) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // zero out the depends on and commands fields because they are // unnecessary and may not get marshaled properly for i := range project.Tasks { project.Tasks[i].DependsOn = []model.TaskDependency{} project.Tasks[i].Commands = []model.PluginCommandConf{} } as.WriteJSON(w, http.StatusOK, project.Tasks) }
func (as *APIServer) updatePatchModule(w http.ResponseWriter, r *http.Request) { p, err := getPatchFromRequest(r) if err != nil { as.WriteJSON(w, http.StatusBadRequest, err.Error()) return } moduleName := r.FormValue("module") patchContent := r.FormValue("patch") githash := r.FormValue("githash") projectRef, err := model.FindOneProjectRef(p.Project) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error getting project ref with id %v: %v", p.Project, err)) return } project, err := model.FindProject("", projectRef) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error getting patch: %v", err)) return } if project == nil { as.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("can't find project: %v", p.Project)) return } module, err := project.GetModuleByName(moduleName) if err != nil || module == nil { as.LoggedError(w, r, http.StatusBadRequest, fmt.Errorf("No such module")) return } gitOutput, err := thirdparty.GitApplyNumstat(patchContent) if err != nil { as.WriteJSON(w, http.StatusBadRequest, fmt.Errorf("Invalid patch: %v", err)) return } if gitOutput == nil { as.WriteJSON(w, http.StatusBadRequest, fmt.Errorf("Empty diff")) return } summaries, err := thirdparty.ParseGitSummary(gitOutput) if err != nil { as.WriteJSON(w, http.StatusBadRequest, fmt.Errorf("Can't validate patch: %v", err)) return } repoOwner, repo := module.GetRepoOwnerAndName() commitInfo, err := thirdparty.GetCommitEvent(as.Settings.Credentials[projectRef.RepoKind], repoOwner, repo, githash) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } if commitInfo == nil { as.WriteJSON(w, http.StatusBadRequest, fmt.Errorf("commit hash doesn't seem to exist")) return } modulePatch := patch.ModulePatch{ ModuleName: moduleName, Githash: githash, PatchSet: patch.PatchSet{ Patch: patchContent, Summary: summaries, // thirdparty.GetPatchSummary(apiRequest.PatchContent), }, } if err = p.UpdateModulePatch(modulePatch); err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } as.WriteJSON(w, http.StatusOK, "Patch module updated") return }
// ManifestLoadHandler attempts to get the manifest, if it exists it updates the expansions and returns // If it does not exist it performs GitHub API calls for each of the project's modules and gets // the head revision of the branch and inserts it into the manifest collection. // If there is a duplicate key error, then do a find on the manifest again. func (mp *ManifestPlugin) ManifestLoadHandler(w http.ResponseWriter, r *http.Request) { task := plugin.GetTask(r) projectRef, err := model.FindOneProjectRef(task.Project) if err != nil { http.Error(w, fmt.Sprintf("projectRef not found for project %v: %v", task.Project, err), http.StatusNotFound) return } project, err := model.FindProject("", projectRef) if err != nil { http.Error(w, fmt.Sprintf("project not found for ProjectRef %v: %v", projectRef.Identifier, err), http.StatusNotFound) return } if project == nil { http.Error(w, fmt.Sprintf("empty project not found for ProjectRef %v: %v", projectRef.Identifier, err), http.StatusNotFound) return } // try to get the manifest currentManifest, err := manifest.FindOne(manifest.ById(task.Version)) if err != nil { http.Error(w, fmt.Sprintf("error retrieving manifest with version id %v: %v", task.Version, err), http.StatusNotFound) return } if currentManifest != nil { plugin.WriteJSON(w, http.StatusOK, currentManifest) return } if task.Version == "" { http.Error(w, fmt.Sprintf("versionId is empty"), http.StatusNotFound) return } // attempt to insert a manifest after making GitHub API calls newManifest := &manifest.Manifest{ Id: task.Version, Revision: task.Revision, ProjectName: task.Project, Branch: project.Branch, } // populate modules modules := make(map[string]*manifest.Module) for _, module := range project.Modules { owner, repo := module.GetRepoOwnerAndName() gitBranch, err := thirdparty.GetBranchEvent(mp.OAuthCredentials, owner, repo, module.Branch) if err != nil { http.Error(w, fmt.Sprintf("error retrieving getting git branch for module %v: %v", module.Name, err), http.StatusNotFound) return } modules[module.Name] = &manifest.Module{ Branch: module.Branch, Revision: gitBranch.Commit.SHA, Repo: repo, Owner: owner, URL: gitBranch.Commit.Url, } } newManifest.Modules = modules duplicate, err := newManifest.TryInsert() if err != nil { http.Error(w, fmt.Sprintf("error inserting manifest for %v: %v", newManifest.ProjectName, err), http.StatusNotFound) return } // if it is a duplicate, load the manifest again` if duplicate { // try to get the manifest m, err := manifest.FindOne(manifest.ById(task.Version)) if err != nil { http.Error(w, fmt.Sprintf("error getting latest manifest for %v: %v", newManifest.ProjectName, err), http.StatusNotFound) return } if m != nil { plugin.WriteJSON(w, http.StatusOK, m) return } } // no duplicate key error, use the manifest just created. plugin.WriteJSON(w, http.StatusOK, newManifest) return }
func (uis *UIServer) variantHistory(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) variant := mux.Vars(r)["variant"] beforeCommitId := r.FormValue("before") isJson := (r.FormValue("format") == "json") var beforeCommit *version.Version var err error beforeCommit = nil if beforeCommitId != "" { beforeCommit, err = version.FindOne(version.ById(beforeCommitId)) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } if beforeCommit == nil { evergreen.Logger.Logf(slogger.WARN, "'before' was specified but query returned nil") } } project, err := model.FindProject("", projCtx.ProjectRef) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } bv := project.FindBuildVariant(variant) if bv == nil { http.Error(w, "variant not found", http.StatusNotFound) return } iter := model.NewBuildVariantHistoryIterator(variant, bv.Name, projCtx.Project.Identifier) tasks, versions, err := iter.GetItems(beforeCommit, 50) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } var suites []string for _, task := range bv.Tasks { suites = append(suites, task.Name) } sort.Strings(suites) data := struct { Variant string Tasks []bson.M TaskNames []string Versions []version.Version Project string }{variant, tasks, suites, versions, projCtx.Project.Identifier} if isJson { uis.WriteJSON(w, http.StatusOK, data) return } uis.WriteHTML(w, http.StatusOK, struct { ProjectData projectContext User *user.DBUser Flashes []interface{} Data interface{} }{projCtx, GetUser(r), []interface{}{}, data}, "base", "build_variant_history.html", "base_angular.html", "menu.html") }
// LoadProjectContext builds a projectContext from vars in the request's URL. // This is done by reading in specific variables and inferring other required // context variables when necessary (e.g. loading a project based on the task). func (uis *UIServer) LoadProjectContext(rw http.ResponseWriter, r *http.Request) (projectContext, error) { user := GetUser(r) vars := mux.Vars(r) proj := &projectContext{} taskId := vars["task_id"] buildId := vars["build_id"] versionId := vars["version_id"] patchId := vars["patch_id"] err := proj.populateProjectRefs(user != nil) if err != nil { return *proj, err } projectId, err := proj.populateTaskBuildVersion(taskId, buildId, versionId) if err != nil { return *proj, err } err = proj.populatePatch(patchId) if err != nil { return *proj, err } if proj.Patch != nil && len(projectId) == 0 { projectId = proj.Patch.Project } // Try to infer project ID - if we don't already have it from the task/build/version, try to // get it from the URL if len(projectId) == 0 { projectId = vars["project_id"] } usingDefault := false // Still don't have a project ID to use, check if the user's cookie contains one if len(projectId) == 0 { cookie, err := r.Cookie(ProjectCookieName) if err == nil { projectId = cookie.Value } usingDefault = true } // Still no project ID found anywhere, just use the default one according to config. if len(projectId) == 0 { projectId = uis.Settings.Ui.DefaultProject usingDefault = true } // Try to load project for the ID we found, and set cookie with it for subsequent requests if len(projectId) > 0 { // Also lookup the ProjectRef itself and add it to context. proj.ProjectRef, err = model.FindOneProjectRef(projectId) if err != nil { return *proj, err } // If we used the default project or a cookie to choose the project, // but that project doesn't exist, choose the first one in the list. if usingDefault && proj.ProjectRef == nil { if len(proj.AllProjects) > 0 { proj.ProjectRef = &proj.AllProjects[0] } } if proj.ProjectRef != nil { proj.Project, err = model.FindProject("", proj.ProjectRef) if err != nil { return *proj, err } if proj.Project != nil { // A project was found, update the project cookie for subsequent request. http.SetCookie(rw, &http.Cookie{ Name: ProjectCookieName, Value: projectId, Path: "", Expires: time.Now().Add(7 * 24 * time.Hour), }) } } } proj.AuthRedirect = uis.UserManager.IsRedirect() return *proj, nil }
// GetProjectConfig fetches the project configuration for a given repository // returning a remote config if the project references a remote repository // configuration file - via the Identifier. Otherwise it defaults to the local // project file func (repoTracker *RepoTracker) GetProjectConfig(revision string) ( project *model.Project, err error) { projectRef := repoTracker.ProjectRef if projectRef.LocalConfig != "" { // return the Local config from the project Ref. return model.FindProject("", projectRef) } project, err = repoTracker.GetRemoteConfig(revision) if err != nil { // Only create a stub version on API request errors that pertain // to actually fetching a config. Those errors currently include: // thirdparty.APIRequestError, thirdparty.FileNotFoundError and // thirdparty.YAMLFormatError _, apiReqErr := err.(thirdparty.APIRequestError) _, ymlFmtErr := err.(thirdparty.YAMLFormatError) _, noFileErr := err.(thirdparty.FileNotFoundError) if apiReqErr || noFileErr || ymlFmtErr { // If there's an error getting the remote config, e.g. because it // does not exist, we treat this the same as when the remote config // is invalid - but add a different error message message := fmt.Sprintf("error fetching project “%v” configuration "+ "data at revision “%v” (remote path=“%v”): %v", projectRef.Identifier, revision, projectRef.RemotePath, err) evergreen.Logger.Logf(slogger.ERROR, message) return nil, projectConfigError{[]string{message}} } // If we get here then we have an infrastructural error - e.g. // a thirdparty.APIUnmarshalError (indicating perhaps an API has // changed), a thirdparty.ResponseReadError(problem reading an // API response) or a thirdparty.APIResponseError (nil API // response) - or encountered a problem in fetching a local // configuration file. At any rate, this is bad enough that we // want to send a notification instead of just creating a stub // version. var lastRevision string repository, fErr := model.FindRepository(projectRef.Identifier) if fErr != nil || repository == nil { evergreen.Logger.Logf(slogger.ERROR, "error finding "+ "repository '%v': %v", projectRef.Identifier, fErr) } else { lastRevision = repository.LastRevision } repoTracker.sendFailureNotification(lastRevision, err) return nil, err } // check if project config is valid errs := validator.CheckProjectSyntax(project) if len(errs) != 0 { // We have syntax errors in the project. // Format them, as we need to store + display them to the user var message string var projectParseErrors []string for _, configError := range errs { message += fmt.Sprintf("\n\t=> %v", configError) projectParseErrors = append(projectParseErrors, configError.Error()) } evergreen.Logger.Logf(slogger.ERROR, "error validating project '%v' "+ "configuration at revision '%v': %v", projectRef.Identifier, revision, message) return nil, projectConfigError{projectParseErrors} } return project, nil }
func (as *APIServer) EndTask(w http.ResponseWriter, r *http.Request) { finishTime := time.Now() taskEndResponse := &apimodels.TaskEndResponse{} task := MustHaveTask(r) details := &apimodels.TaskEndDetail{} if err := util.ReadJSONInto(r.Body, details); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Check that finishing status is a valid constant if details.Status != evergreen.TaskSucceeded && details.Status != evergreen.TaskFailed && details.Status != evergreen.TaskUndispatched { msg := fmt.Errorf("Invalid end status '%v' for task %v", details.Status, task.Id) as.LoggedError(w, r, http.StatusBadRequest, msg) return } projectRef, err := model.FindOneProjectRef(task.Project) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) } if projectRef == nil { as.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("empty projectRef for task")) return } project, err := model.FindProject("", projectRef) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } if !getGlobalLock(r.RemoteAddr, task.Id) { as.LoggedError(w, r, http.StatusInternalServerError, ErrLockTimeout) return } defer releaseGlobalLock(r.RemoteAddr, task.Id) // mark task as finished err = task.MarkEnd(APIServerLockTitle, finishTime, details, project, projectRef.DeactivatePrevious) if err != nil { message := fmt.Errorf("Error calling mark finish on task %v : %v", task.Id, err) as.LoggedError(w, r, http.StatusInternalServerError, message) return } if task.Requester != evergreen.PatchVersionRequester { alerts.RunTaskFailureTriggers(task) } else { //TODO(EVG-223) process patch-specific triggers } // if task was aborted, reset to inactive if details.Status == evergreen.TaskUndispatched { if err = model.SetTaskActivated(task.Id, "", false); err != nil { message := fmt.Sprintf("Error deactivating task after abort: %v", err) evergreen.Logger.Logf(slogger.ERROR, message) taskEndResponse.Message = message as.WriteJSON(w, http.StatusInternalServerError, taskEndResponse) return } as.taskFinished(w, task, finishTime) return } // update the bookkeeping entry for the task err = bookkeeping.UpdateExpectedDuration(task, task.TimeTaken) if err != nil { evergreen.Logger.Logf(slogger.ERROR, "Error updating expected duration: %v", err) } // log the task as finished evergreen.Logger.Logf(slogger.INFO, "Successfully marked task %v as finished", task.Id) // construct and return the appropriate response for the agent as.taskFinished(w, task, finishTime) }
func (as *APIServer) submitPatch(w http.ResponseWriter, r *http.Request) { user := MustHaveUser(r) var apiRequest PatchAPIRequest var projId, description string var finalize bool if r.Header.Get("Content-Type") == "application/x-www-form-urlencoded" { patchContent := r.FormValue("patch") if patchContent == "" { as.LoggedError(w, r, http.StatusBadRequest, fmt.Errorf("Error: Patch must not be empty")) return } apiRequest = PatchAPIRequest{ ProjectFileName: r.FormValue("project"), ModuleName: r.FormValue("module"), Githash: r.FormValue("githash"), PatchContent: r.FormValue("patch"), BuildVariants: strings.Split(r.FormValue("buildvariants"), ","), } projId = r.FormValue("project") description = r.FormValue("desc") finalize = strings.ToLower(r.FormValue("finalize")) == "true" } else { data := struct { Description string `json:"desc"` Project string `json:"project"` Patch string `json:"patch"` Githash string `json:"githash"` Variants string `json:"buildvariants"` Tasks []string `json:"tasks"` Finalize bool `json:"finalize"` }{} if err := util.ReadJSONInto(r.Body, &data); err != nil { as.LoggedError(w, r, http.StatusBadRequest, err) return } if len(data.Patch) > patch.SizeLimit { as.LoggedError(w, r, http.StatusBadRequest, fmt.Errorf("Patch is too large.")) } if len(data.Patch) == 0 { as.LoggedError(w, r, http.StatusBadRequest, fmt.Errorf("Error: Patch must not be empty")) return } description = data.Description projId = data.Project finalize = data.Finalize apiRequest = PatchAPIRequest{ ProjectFileName: data.Project, ModuleName: r.FormValue("module"), Githash: data.Githash, PatchContent: data.Patch, BuildVariants: strings.Split(data.Variants, ","), Tasks: data.Tasks, } } projectRef, err := model.FindOneProjectRef(projId) if err != nil { message := fmt.Errorf("Error locating project ref '%v': %v", projId, err) as.LoggedError(w, r, http.StatusInternalServerError, message) return } project, err := model.FindProject("", projectRef) if err != nil { message := fmt.Errorf("Error locating project '%v' from '%v': %v", projId, as.Settings.ConfigDir, err) as.LoggedError(w, r, http.StatusInternalServerError, message) return } if project == nil { as.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("project %v not found", projId)) return } patchMetadata, err := apiRequest.Validate(as.Settings.Credentials["github"]) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Invalid patch: %v", err)) return } if patchMetadata == nil { as.LoggedError(w, r, http.StatusBadRequest, fmt.Errorf("patch metadata is empty")) return } if apiRequest.ModuleName != "" { as.WriteJSON(w, http.StatusBadRequest, "module not allowed when creating new patches (must be added in a subsequent request)") return } patchProjectRef, err := model.FindOneProjectRef(patchMetadata.Project.Identifier) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Invalid projectRef: %v", err)) return } if patchProjectRef == nil { as.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("Empty patch project Ref")) return } commitInfo, err := thirdparty.GetCommitEvent(as.Settings.Credentials["github"], patchProjectRef.Owner, patchProjectRef.Repo, apiRequest.Githash) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } if commitInfo == nil { as.WriteJSON(w, http.StatusBadRequest, "That commit doesn't seem to exist.") return } createTime := time.Now() // create a new object ID to use as reference for the patch data patchFileId := bson.NewObjectId().Hex() patchDoc := &patch.Patch{ Id: bson.NewObjectId(), Description: description, Author: user.Id, Project: apiRequest.ProjectFileName, Githash: apiRequest.Githash, CreateTime: createTime, Status: evergreen.PatchCreated, BuildVariants: apiRequest.BuildVariants, Tasks: apiRequest.Tasks, Patches: []patch.ModulePatch{ patch.ModulePatch{ ModuleName: "", Githash: apiRequest.Githash, PatchSet: patch.PatchSet{ PatchFileId: patchFileId, Summary: patchMetadata.Summaries, }, }, }, } // write the patch content into a GridFS file under a new ObjectId. err = db.WriteGridFile(patch.GridFSPrefix, patchFileId, strings.NewReader(apiRequest.PatchContent)) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("failed to write patch file to db: %v", err)) return } // Get and validate patched config and add it to the patch document patchedProject, err := validator.GetPatchedProject(patchDoc, &as.Settings) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("invalid patched config: %v", err)) return } projectYamlBytes, err := yaml.Marshal(patchedProject) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("error marshalling patched config: %v", err)) return } // set the patch number based on patch author patchDoc.PatchNumber, err = user.IncPatchNumber() if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("error computing patch num %v", err)) return } patchDoc.PatchedConfig = string(projectYamlBytes) patchDoc.ClearPatchData() //expand tasks and build variants and include dependencies buildVariants := patchDoc.BuildVariants if len(patchDoc.BuildVariants) == 1 && patchDoc.BuildVariants[0] == "all" { buildVariants = make([]string, 0) for _, buildVariant := range patchedProject.BuildVariants { if buildVariant.Disabled { continue } buildVariants = append(buildVariants, buildVariant.Name) } } tasks := patchDoc.Tasks if len(patchDoc.Tasks) == 1 && patchDoc.Tasks[0] == "all" { tasks = make([]string, 0) for _, t := range patchedProject.Tasks { tasks = append(tasks, t.Name) } } // update variant and tasks to include dependencies patchDoc.BuildVariants, patchDoc.Tasks = model.IncludePatchDependencies( project, buildVariants, tasks) if err = patchDoc.Insert(); err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("error inserting patch: %v", err)) return } if finalize { if _, err = model.FinalizePatch(patchDoc, &as.Settings); err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } } as.WriteJSON(w, http.StatusCreated, PatchAPIResponse{Patch: patchDoc}) }
// Validate checks an API request to see if it is safe and sane. // Returns the relevant patch metadata and any errors that occur. func (pr *PatchAPIRequest) Validate(oauthToken string) (*PatchMetadata, error) { var repoOwner, repo string var module *model.Module projectRef, err := model.FindOneProjectRef(pr.ProjectFileName) if err != nil { return nil, fmt.Errorf("Could not find project ref %v : %v", pr.ProjectFileName, err) } repoOwner = projectRef.Owner repo = projectRef.Repo // validate the project file project, err := model.FindProject("", projectRef) if err != nil { return nil, fmt.Errorf("Could not find project file %v: %v", pr.ProjectFileName, err) } if project == nil { return nil, fmt.Errorf("No such project file named %v", pr.ProjectFileName) } if pr.ModuleName != "" { // is there a module? validate it. module, err = project.GetModuleByName(pr.ModuleName) if err != nil { return nil, fmt.Errorf("could not find module %v: %v", pr.ModuleName, err) } if module == nil { return nil, fmt.Errorf("no module named %v", pr.ModuleName) } repoOwner, repo = module.GetRepoOwnerAndName() } if len(pr.Githash) != 40 { return nil, fmt.Errorf("invalid githash") } gitCommit, err := thirdparty.GetCommitEvent(oauthToken, repoOwner, repo, pr.Githash) if err != nil { return nil, fmt.Errorf("could not find base revision %v for project %v: %v", pr.Githash, projectRef.Identifier, err) } if gitCommit == nil { return nil, fmt.Errorf("commit hash %v doesn't seem to exist", pr.Githash) } gitOutput, err := thirdparty.GitApplyNumstat(pr.PatchContent) if err != nil { return nil, fmt.Errorf("couldn't validate patch: %v", err) } if gitOutput == nil { return nil, fmt.Errorf("couldn't validate patch: git apply --numstat returned empty") } summaries, err := thirdparty.ParseGitSummary(gitOutput) if err != nil { return nil, fmt.Errorf("couldn't validate patch: %v", err) } if len(pr.BuildVariants) == 0 || pr.BuildVariants[0] == "" { return nil, fmt.Errorf("no buildvariants specified") } // verify that this build variant exists for _, buildVariant := range pr.BuildVariants { if buildVariant == "all" { continue } bv := project.FindBuildVariant(buildVariant) if bv == nil { return nil, fmt.Errorf("No such buildvariant: %v", buildVariant) } } return &PatchMetadata{pr.Githash, project, module, pr.BuildVariants, summaries}, nil }
func (as *APIServer) updatePatchModule(w http.ResponseWriter, r *http.Request) { p, err := getPatchFromRequest(r) if err != nil { as.WriteJSON(w, http.StatusBadRequest, err.Error()) return } var moduleName, patchContent, githash string if r.Header.Get("Content-Type") == "application/x-www-form-urlencoded" { moduleName, patchContent, githash = r.FormValue("module"), r.FormValue("patch"), r.FormValue("githash") } else { data := struct { Module string `json:"module"` Patch string `json:"patch"` Githash string `json:"githash"` }{} if err := util.ReadJSONInto(r.Body, &data); err != nil { as.LoggedError(w, r, http.StatusBadRequest, err) return } moduleName, patchContent, githash = data.Module, data.Patch, data.Githash } if len(patchContent) == 0 { as.LoggedError(w, r, http.StatusBadRequest, fmt.Errorf("Error: Patch must not be empty")) return } projectRef, err := model.FindOneProjectRef(p.Project) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error getting project ref with id %v: %v", p.Project, err)) return } project, err := model.FindProject("", projectRef) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error getting patch: %v", err)) return } if project == nil { as.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("can't find project: %v", p.Project)) return } module, err := project.GetModuleByName(moduleName) if err != nil || module == nil { as.LoggedError(w, r, http.StatusBadRequest, fmt.Errorf("No such module", moduleName)) return } gitOutput, err := thirdparty.GitApplyNumstat(patchContent) if err != nil { as.WriteJSON(w, http.StatusBadRequest, fmt.Errorf("Invalid patch: %v", err)) return } if gitOutput == nil { as.WriteJSON(w, http.StatusBadRequest, fmt.Errorf("Empty diff")) return } summaries, err := thirdparty.ParseGitSummary(gitOutput) if err != nil { as.WriteJSON(w, http.StatusBadRequest, fmt.Errorf("Can't validate patch: %v", err)) return } repoOwner, repo := module.GetRepoOwnerAndName() commitInfo, err := thirdparty.GetCommitEvent(as.Settings.Credentials["github"], repoOwner, repo, githash) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } if commitInfo == nil { as.WriteJSON(w, http.StatusBadRequest, fmt.Errorf("commit hash doesn't seem to exist")) return } // write the patch content into a GridFS file under a new ObjectId. patchFileId := bson.NewObjectId().Hex() err = db.WriteGridFile(patch.GridFSPrefix, patchFileId, strings.NewReader(patchContent)) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("failed to write patch file to db: %v", err)) return } modulePatch := patch.ModulePatch{ ModuleName: moduleName, Githash: githash, PatchSet: patch.PatchSet{ PatchFileId: patchFileId, Summary: summaries, }, } if err = p.UpdateModulePatch(modulePatch); err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } as.WriteJSON(w, http.StatusOK, "Patch module updated") return }
func (as *APIServer) submitPatch(w http.ResponseWriter, r *http.Request) { user := MustHaveUser(r) apiRequest := PatchAPIRequest{ ProjectFileName: r.FormValue("project"), ModuleName: r.FormValue("module"), Githash: r.FormValue("githash"), PatchContent: r.FormValue("patch"), BuildVariants: strings.Split(r.FormValue("buildvariants"), ","), } description := r.FormValue("desc") projId := r.FormValue("project") projectRef, err := model.FindOneProjectRef(projId) if err != nil { message := fmt.Errorf("Error locating project ref '%v': %v", projId, err) as.LoggedError(w, r, http.StatusInternalServerError, message) return } project, err := model.FindProject("", projectRef) if err != nil { message := fmt.Errorf("Error locating project '%v' from '%v': %v", projId, as.Settings.ConfigDir, err) as.LoggedError(w, r, http.StatusInternalServerError, message) return } if project == nil { as.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("project %v not found", projId)) return } patchMetadata, err := apiRequest.Validate(as.Settings.Credentials[projectRef.RepoKind]) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Invalid patch: %v", err)) return } if patchMetadata == nil { as.LoggedError(w, r, http.StatusBadRequest, fmt.Errorf("patch metadata is empty")) return } if apiRequest.ModuleName != "" { as.WriteJSON(w, http.StatusBadRequest, "module not allowed when creating new patches (must be added in a subsequent request)") return } patchProjectRef, err := model.FindOneProjectRef(patchMetadata.Project.Identifier) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Invalid projectRef: %v", err)) return } if patchProjectRef == nil { as.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("Empty patch project Ref")) return } commitInfo, err := thirdparty.GetCommitEvent(as.Settings.Credentials[projectRef.RepoKind], patchProjectRef.Owner, patchProjectRef.Repo, apiRequest.Githash) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } if commitInfo == nil { as.WriteJSON(w, http.StatusBadRequest, "That commit doesn't seem to exist.") return } createTime := time.Now() patchDoc := &patch.Patch{ Id: bson.NewObjectId(), Description: description, Author: user.Id, Project: apiRequest.ProjectFileName, Githash: apiRequest.Githash, CreateTime: createTime, Status: evergreen.PatchCreated, BuildVariants: apiRequest.BuildVariants, Tasks: nil, Patches: []patch.ModulePatch{ patch.ModulePatch{ ModuleName: "", Githash: apiRequest.Githash, PatchSet: patch.PatchSet{ Patch: apiRequest.PatchContent, Summary: patchMetadata.Summaries, }, }, }, } // set the patch number based on patch author patchDoc.PatchNumber, err = user.IncPatchNumber() if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("error computing patch num %v", err)) return } if err = patchDoc.Insert(); err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("error inserting patch: %v", err)) return } if strings.ToLower(r.FormValue("finalize")) == "true" { if _, err = validator.ValidateAndFinalize(patchDoc, &as.Settings); err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } } as.WriteJSON(w, http.StatusCreated, PatchAPIResponse{Patch: patchDoc}) }
// run all monitoring functions func RunAllMonitoring(settings *evergreen.Settings) error { // load in all of the distros distros, err := distro.Find(db.Q{}) if err != nil { return fmt.Errorf("error finding distros: %v", err) } // fetch the project refs, which we will use to get all of the projects projectRefs, err := model.FindAllProjectRefs() if err != nil { return fmt.Errorf("error loading in project refs: %v", err) } // turn the project refs into a map of the project id -> project projects := map[string]model.Project{} for _, ref := range projectRefs { // only monitor projects that are enabled if !ref.Enabled { continue } project, err := model.FindProject("", &ref) // continue on error to stop the whole monitoring process from // being held up if err != nil { evergreen.Logger.Logf(slogger.ERROR, "error finding project %v: %v", ref.Identifier, err) continue } if project == nil { evergreen.Logger.Logf(slogger.ERROR, "no project entry found for"+ " ref %v", ref.Identifier) continue } projects[project.Identifier] = *project } // initialize the task monitor taskMonitor := &TaskMonitor{ flaggingFuncs: defaultTaskFlaggingFuncs, } // clean up any necessary tasks errs := taskMonitor.CleanupTasks(projects) for _, err := range errs { evergreen.Logger.Logf(slogger.ERROR, "Error cleaning up tasks: %v", err) } // initialize the host monitor hostMonitor := &HostMonitor{ flaggingFuncs: defaultHostFlaggingFuncs, monitoringFuncs: defaultHostMonitoringFuncs, } // clean up any necessary hosts errs = hostMonitor.CleanupHosts(distros, settings) for _, err := range errs { evergreen.Logger.Logf(slogger.ERROR, "Error cleaning up hosts: %v", err) } // run monitoring checks errs = hostMonitor.RunMonitoringChecks(settings) for _, err := range errs { evergreen.Logger.Logf(slogger.ERROR, "Error running host monitoring checks: %v", err) } // initialize the notifier notifier := &Notifier{ notificationBuilders: defaultNotificationBuilders, } // send notifications errs = notifier.Notify(settings) for _, err := range errs { evergreen.Logger.Logf(slogger.ERROR, "Error sending notifications: %v", err) } // Do alerts for spawnhosts - collect all hosts expiring in the next 12 hours. // The trigger logic will filter out any hosts that aren't in a notification window, or have // already have alerts sent. now := time.Now() thresholdTime := now.Add(12 * time.Hour) expiringSoonHosts, err := host.Find(host.ByExpiringBetween(now, thresholdTime)) if err != nil { return err } for _, h := range expiringSoonHosts { err := alerts.RunSpawnWarningTriggers(&h) if err != nil { evergreen.Logger.Logf(slogger.ERROR, "Error queueing alert: %v", err) } } return nil }