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)
		})
	})
}
Пример #3
0
// 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

}
Пример #4
0
// 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
}
Пример #5
0
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)
}
Пример #6
0
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")
}
Пример #7
0
// 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
}
Пример #8
0
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

}
Пример #9
0
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)
}
Пример #10
0
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
}
Пример #11
0
// 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
}
Пример #12
0
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")
}
Пример #13
0
// 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
}
Пример #14
0
// 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
}
Пример #15
0
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)
}
Пример #16
0
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})
}
Пример #17
0
// 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
}
Пример #18
0
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
}
Пример #19
0
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})
}
Пример #20
0
// 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

}