// GetRemoteConfig fetches the contents of a remote github repository's // configuration data as at a given revision func (gRepoPoller *GithubRepositoryPoller) GetChangedFiles(commitRevision string) ([]string, error) { // get the entire commit, then pull the files from it projectRef := gRepoPoller.ProjectRef commit, err := thirdparty.GetCommitEvent( gRepoPoller.OauthToken, projectRef.Owner, projectRef.Repo, commitRevision, ) if err != nil { return nil, fmt.Errorf("error loading commit '%v': %v", commitRevision, err) } files := []string{} for _, f := range commit.Files { files = append(files, f.FileName) } return files, nil }
// Finalizes a patch: // Patches a remote project's configuration file if needed. // Creates a version for this patch and links it. // Creates builds based on the version. func FinalizePatch(p *patch.Patch, settings *evergreen.Settings) (*version.Version, error) { // unmarshal the project YAML for storage project := &Project{} err := yaml.Unmarshal([]byte(p.PatchedConfig), project) if err != nil { return nil, fmt.Errorf( "Error marshalling patched project config from repository revision “%v”: %v", p.Githash, err) } projectRef, err := FindOneProjectRef(p.Project) if err != nil { return nil, err } gitCommit, err := thirdparty.GetCommitEvent( settings.Credentials["github"], projectRef.Owner, projectRef.Repo, p.Githash, ) if err != nil { return nil, fmt.Errorf("Couldn't fetch commit information: %v", err) } if gitCommit == nil { return nil, fmt.Errorf("Couldn't fetch commit information: git commit doesn't exist?") } patchVersion := &version.Version{ Id: p.Id.Hex(), CreateTime: time.Now(), Identifier: p.Project, Revision: p.Githash, Author: p.Author, Message: p.Description, BuildIds: []string{}, BuildVariants: []version.BuildStatus{}, Config: string(p.PatchedConfig), Status: evergreen.PatchCreated, Requester: evergreen.PatchVersionRequester, Branch: project.Branch, } var pairs []TVPair if len(p.VariantsTasks) > 0 { pairs = VariantTasksToTVPairs(p.VariantsTasks) } else { // handle case where the patch is being finalized but only has the old schema tasks/variants // instead of the new one. for _, v := range p.BuildVariants { for _, t := range p.Tasks { if project.FindTaskForVariant(t, v) != nil { pairs = append(pairs, TVPair{v, t}) } } } p.VariantsTasks = TVPairsToVariantTasks(pairs) } tt := NewPatchTaskIdTable(project, patchVersion, pairs) variantsProcessed := map[string]bool{} for _, vt := range p.VariantsTasks { if _, ok := variantsProcessed[vt.Variant]; ok { continue } buildId, err := CreateBuildFromVersion(project, patchVersion, tt, vt.Variant, true, vt.Tasks) if err != nil { return nil, err } patchVersion.BuildIds = append(patchVersion.BuildIds, buildId) patchVersion.BuildVariants = append(patchVersion.BuildVariants, version.BuildStatus{ BuildVariant: vt.Variant, Activated: true, BuildId: buildId, }, ) } if err = patchVersion.Insert(); err != nil { return nil, err } if err = p.SetActivated(patchVersion.Id); err != nil { return nil, err } return patchVersion, nil }
// Finalizes a patch: // Patches a remote project's configuration file if needed. // Creates a version for this patch and links it. // Creates builds based on the version. func FinalizePatch(p *patch.Patch, settings *evergreen.Settings) ( patchVersion *version.Version, err error) { // unmarshal the project YAML for storage project := &Project{} err = yaml.Unmarshal([]byte(p.PatchedConfig), project) if err != nil { return nil, fmt.Errorf( "Error marshalling patched project config from repository revision “%v”: %v", p.Githash, err) } projectRef, err := FindOneProjectRef(p.Project) if err != nil { return } gitCommit, err := thirdparty.GetCommitEvent( settings.Credentials["github"], projectRef.Owner, projectRef.Repo, p.Githash, ) if err != nil { return nil, fmt.Errorf("Couldn't fetch commit information: %v", err) } if gitCommit == nil { return nil, fmt.Errorf("Couldn't fetch commit information: git commit" + " doesn't exist?") } patchVersion = &version.Version{ Id: fmt.Sprintf("%v_%v", p.Id.Hex(), 0), CreateTime: time.Now(), Identifier: p.Project, Revision: p.Githash, Author: gitCommit.Commit.Committer.Name, AuthorEmail: gitCommit.Commit.Committer.Email, Message: gitCommit.Commit.Message, BuildIds: []string{}, BuildVariants: []version.BuildStatus{}, Config: string(p.PatchedConfig), Status: evergreen.PatchCreated, Requester: evergreen.PatchVersionRequester, } //expand tasks and build variants buildVariants := p.BuildVariants if len(p.BuildVariants) == 1 && p.BuildVariants[0] == "all" { buildVariants = make([]string, 0) for _, buildVariant := range project.BuildVariants { if buildVariant.Disabled { continue } buildVariants = append(buildVariants, buildVariant.Name) } } tasks := p.Tasks if len(p.Tasks) == 1 && p.Tasks[0] == "all" { tasks = make([]string, 0) for _, t := range project.Tasks { tasks = append(tasks, t.Name) } } tt := BuildTaskIdTable(project, patchVersion) for _, buildvariant := range buildVariants { buildId, err := CreateBuildFromVersion(project, patchVersion, tt, buildvariant, true, tasks) if err != nil { return nil, err } patchVersion.BuildIds = append(patchVersion.BuildIds, buildId) patchVersion.BuildVariants = append(patchVersion.BuildVariants, version.BuildStatus{ BuildVariant: buildvariant, Activated: true, BuildId: buildId, }, ) } if err = patchVersion.Insert(); err != nil { return nil, err } if err = p.SetActivated(patchVersion.Id); err != nil { return nil, err } return patchVersion, nil }
// 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 } 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 }
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}) }
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) 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}) }
func ValidateAndFinalize(p *patch.Patch, settings *evergreen.Settings) (*version.Version, error) { if p.Version != "" { return nil, fmt.Errorf("Patch %v already finalized", p.Version) } projectRef, err := model.FindOneProjectRef(p.Project) if err != nil { return nil, err } gitCommit, err := thirdparty.GetCommitEvent( settings.Credentials["github"], projectRef.Owner, projectRef.Repo, p.Githash, ) if err != nil { return nil, fmt.Errorf("Couldn't fetch commit information: %v", err) } if gitCommit == nil { return nil, fmt.Errorf("Couldn't fetch commit information: git commit" + " doesn't exist?") } // get the remote file at the requested revision projectFileURL := thirdparty.GetGithubFileURL( projectRef.Owner, projectRef.Repo, projectRef.RemotePath, p.Githash, ) githubFile, err := thirdparty.GetGithubFile( settings.Credentials["github"], projectFileURL, ) if err != nil { return nil, fmt.Errorf("Could not get github file at %v: %v", projectFileURL, err) } projectFileBytes, err := base64.StdEncoding.DecodeString(githubFile.Content) if err != nil { return nil, fmt.Errorf("Could not decode github file at %v: %v", projectFileURL, err) } project := &model.Project{} if err = model.LoadProjectInto(projectFileBytes, projectRef.Identifier, project); err != nil { return nil, err } // apply remote configuration patch if needed if p.ConfigChanged(projectRef.RemotePath) { project, err = model.MakePatchedConfig(p, projectRef.RemotePath, string(projectFileBytes)) if err != nil { return nil, fmt.Errorf("Could not patch remote configuration file: %v", err) } // overwrite project fields with the project ref to disallow tracking a // different project or doing other crazy things via config patches if err = angier.TransferByFieldNames(projectRef, project); err != nil { return nil, fmt.Errorf("Could not merge project Ref ref into project: %v", err) } errs := CheckProjectSyntax(project) if len(errs) != 0 { var message string for _, err := range errs { message += fmt.Sprintf("\n\t=> %v", err) } return nil, fmt.Errorf(message) } } else { // overwrite project fields with the project ref to disallow tracking a // different project or doing other crazy things via config patches if err = angier.TransferByFieldNames(projectRef, project); err != nil { return nil, fmt.Errorf("Could not merge project Ref ref into project: %v", err) } } return model.FinalizePatch(p, gitCommit, settings, project) }
// CreatePatch checks an API request to see if it is safe and sane. // Returns the relevant patch metadata, the patch document, and any errors that occur. func (pr *PatchAPIRequest) CreatePatch(finalize bool, oauthToken string, dbUser *user.DBUser, settings *evergreen.Settings) (*model.Project, *patch.Patch, error) { var repoOwner, repo string var module *model.Module projectRef, err := model.FindOneProjectRef(pr.ProjectId) if err != nil { return nil, nil, fmt.Errorf("Could not find project ref %v : %v", pr.ProjectId, err) } repoOwner = projectRef.Owner repo = projectRef.Repo if len(pr.Githash) != 40 { return nil, nil, fmt.Errorf("invalid githash") } gitCommit, err := thirdparty.GetCommitEvent(oauthToken, repoOwner, repo, pr.Githash) if err != nil { return nil, nil, fmt.Errorf("could not find base revision %v for project %v: %v", pr.Githash, projectRef.Identifier, err) } if gitCommit == nil { return nil, nil, fmt.Errorf("commit hash %v doesn't seem to exist", pr.Githash) } summaries, err := getSummaries(pr.PatchContent) if err != nil { return nil, nil, err } isEmpty := (pr.PatchContent == "") if finalize && (len(pr.BuildVariants) == 0 || pr.BuildVariants[0] == "") { return nil, nil, fmt.Errorf("no buildvariants specified") } 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: pr.Description, Author: dbUser.Id, Project: pr.ProjectId, Githash: pr.Githash, CreateTime: createTime, Status: evergreen.PatchCreated, BuildVariants: pr.BuildVariants, Tasks: pr.Tasks, IsEmpty: isEmpty, Patches: []patch.ModulePatch{ { ModuleName: "", Githash: pr.Githash, PatchSet: patch.PatchSet{ Patch: pr.PatchContent, PatchFileId: patchFileId, Summary: summaries, }, }, }, } // Get and validate patched config and add it to the patch document project, err := validator.GetPatchedProject(patchDoc, settings) if err != nil { return nil, nil, fmt.Errorf("invalid patched config: %v", err) } if pr.ModuleName != "" { // is there a module? validate it. module, err = project.GetModuleByName(pr.ModuleName) if err != nil { return nil, nil, fmt.Errorf("could not find module %v: %v", pr.ModuleName, err) } if module == nil { return nil, nil, fmt.Errorf("no module named %v", pr.ModuleName) } } // verify that all variants exists for _, buildVariant := range pr.BuildVariants { if buildVariant == "all" || buildVariant == "" { continue } bv := project.FindBuildVariant(buildVariant) if bv == nil { return nil, nil, fmt.Errorf("No such buildvariant: %v", buildVariant) } } // write the patch content into a GridFS file under a new ObjectId after validating. err = db.WriteGridFile(patch.GridFSPrefix, patchFileId, strings.NewReader(pr.PatchContent)) if err != nil { return nil, nil, fmt.Errorf("failed to write patch file to db: %v", err) } // add the project config projectYamlBytes, err := yaml.Marshal(project) if err != nil { return nil, nil, fmt.Errorf("error marshalling patched config: %v", err) } // set the patch number based on patch author patchDoc.PatchNumber, err = dbUser.IncPatchNumber() if err != nil { return nil, nil, fmt.Errorf("error computing patch num %v", err) } patchDoc.PatchedConfig = string(projectYamlBytes) patchDoc.ClearPatchData() return project, patchDoc, nil }