func TestFinalizePatch(t *testing.T) { testutil.ConfigureIntegrationTest(t, patchTestConfig, "TestFinalizePatch") Convey("With FinalizePatch on a project and commit event generated from GetPatchedProject path", t, func() { configPatch := resetPatchSetup(t, configFilePath) Convey("a patched config should drive version creation", func() { project, err := GetPatchedProject(configPatch, patchTestConfig) So(err, ShouldBeNil) yamlBytes, err := yaml.Marshal(project) So(err, ShouldBeNil) configPatch.PatchedConfig = string(yamlBytes) version, err := model.FinalizePatch(configPatch, patchTestConfig) So(err, ShouldBeNil) So(version, ShouldNotBeNil) // ensure the relevant builds/tasks were created builds, err := build.Find(build.All) So(err, ShouldBeNil) So(len(builds), ShouldEqual, 1) So(len(builds[0].Tasks), ShouldEqual, 2) tasks, err := task.Find(task.All) So(err, ShouldBeNil) So(len(tasks), ShouldEqual, 2) }) Convey("a patch that does not include the remote config should not "+ "drive version creation", func() { patchedConfigFile := "fakeInPatchSoNotPatched" configPatch := resetPatchSetup(t, patchedConfigFile) project, err := GetPatchedProject(configPatch, patchTestConfig) So(err, ShouldBeNil) yamlBytes, err := yaml.Marshal(project) So(err, ShouldBeNil) configPatch.PatchedConfig = string(yamlBytes) version, err := model.FinalizePatch(configPatch, patchTestConfig) So(err, ShouldBeNil) So(version, ShouldNotBeNil) So(err, ShouldBeNil) So(version, ShouldNotBeNil) // ensure the relevant builds/tasks were created builds, err := build.Find(build.All) So(err, ShouldBeNil) So(len(builds), ShouldEqual, 1) So(len(builds[0].Tasks), ShouldEqual, 1) tasks, err := task.Find(task.All) So(err, ShouldBeNil) So(len(tasks), ShouldEqual, 1) }) Reset(func() { db.Clear(distro.Collection) }) }) }
func (as *APIServer) existingPatchRequest(w http.ResponseWriter, r *http.Request) { p, err := getPatchFromRequest(r) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } if !getGlobalLock(r.RemoteAddr, p.Id.String()) { as.LoggedError(w, r, http.StatusInternalServerError, ErrLockTimeout) return } defer releaseGlobalLock(r.RemoteAddr, p.Id.String()) // dispatch to handlers based on specified action switch r.FormValue("action") { case "update": name := r.FormValue("desc") err := p.SetDescription(name) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } as.WriteJSON(w, http.StatusOK, "patch updated") case "finalize": if p.Activated == true { http.Error(w, "patch is already finalized", http.StatusBadRequest) return } patchedProject, err := validator.GetPatchedProject(p, &as.Settings) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, 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 } p.PatchedConfig = string(projectYamlBytes) _, err = model.FinalizePatch(p, &as.Settings) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } as.WriteJSON(w, http.StatusOK, "patch finalized") case "cancel": err = model.CancelPatch(p) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } as.WriteJSON(w, http.StatusOK, "patch deleted") default: http.Error(w, fmt.Sprintf("Unrecognized action: %v", r.FormValue("action")), http.StatusBadRequest) } }
func (uis *UIServer) schedulePatch(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) if projCtx.Patch == nil { http.Error(w, "patch not found", http.StatusNotFound) return } // grab patch again, as the diff was excluded var err error projCtx.Patch, err = patch.FindOne(patch.ById(projCtx.Patch.Id)) if err != nil { http.Error(w, fmt.Sprintf("error loading patch: %v", err), http.StatusInternalServerError) return } patchUpdateReq := struct { Variants []string `json:"variants"` Tasks []string `json:"tasks"` Description string `json:"description"` }{} err = util.ReadJSONInto(r.Body, &patchUpdateReq) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if projCtx.Patch.Version != "" { // This patch has already been finalized, just add the new builds and tasks if projCtx.Version == nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Couldn't find patch for id %v", projCtx.Patch.Version)) return } // First add new tasks to existing builds, if necessary if len(patchUpdateReq.Tasks) > 0 { err = model.AddNewTasksForPatch(projCtx.Patch, projCtx.Version, patchUpdateReq.Tasks) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error creating new tasks: `%v` for version `%v`", err, projCtx.Version.Id)) return } } if len(patchUpdateReq.Variants) > 0 { _, err := model.AddNewBuildsForPatch(projCtx.Patch, projCtx.Version, patchUpdateReq.Variants) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error creating new builds: `%v` for version `%v`", err, projCtx.Version.Id)) return } } PushFlash(uis.CookieStore, r, w, NewSuccessFlash("Builds and tasks successfully added to patch.")) uis.WriteJSON(w, http.StatusOK, struct { VersionId string `json:"version"` }{projCtx.Version.Id}) } else { err = projCtx.Patch.SetVariantsAndTasks(patchUpdateReq.Variants, patchUpdateReq.Tasks) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error setting patch variants and tasks: %v", err)) return } if err = projCtx.Patch.SetDescription(patchUpdateReq.Description); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error setting description: %v", err)) return } // Unmarshal the project config and set it in the project context project := model.Project{} if err := yaml.Unmarshal([]byte(projCtx.Patch.PatchedConfig), &project); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error unmarshaling project config: %v", err)) } projCtx.Project = &project ver, err := model.FinalizePatch(projCtx.Patch, &uis.Settings) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error finalizing patch: %v", err)) return } PushFlash(uis.CookieStore, r, w, NewSuccessFlash("Patch builds are scheduled.")) uis.WriteJSON(w, http.StatusOK, struct { VersionId string `json:"version"` }{ver.Id}) } }
func (as *APIServer) existingPatchRequest(w http.ResponseWriter, r *http.Request) { user := MustHaveUser(r) p, err := getPatchFromRequest(r) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } if !getGlobalLock(r.RemoteAddr, p.Id.String()) { as.LoggedError(w, r, http.StatusInternalServerError, ErrLockTimeout) return } defer releaseGlobalLock(r.RemoteAddr, p.Id.String()) var action, desc string if r.Header.Get("Content-Type") == "application/x-www-form-urlencoded" { action = r.FormValue("action") } else { data := struct { PatchId string `json:"patch_id"` Action string `json:"action"` Description string `json:"description"` }{} if err := util.ReadJSONInto(r.Body, &data); err != nil { as.LoggedError(w, r, http.StatusBadRequest, err) return } action, desc = data.Action, data.Description } // dispatch to handlers based on specified action switch action { case "update": err := p.SetDescription(desc) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } as.WriteJSON(w, http.StatusOK, "patch updated") case "finalize": if p.Activated == true { http.Error(w, "patch is already finalized", http.StatusBadRequest) return } patchedProject, err := validator.GetPatchedProject(p, &as.Settings) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, 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 } p.PatchedConfig = string(projectYamlBytes) _, err = model.FinalizePatch(p, &as.Settings) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } as.WriteJSON(w, http.StatusOK, "patch finalized") case "cancel": err = model.CancelPatch(p, user.Id) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } as.WriteJSON(w, http.StatusOK, "patch deleted") default: http.Error(w, fmt.Sprintf("Unrecognized action: %v", action), http.StatusBadRequest) } }
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 (uis *UIServer) schedulePatch(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) if projCtx.Patch == nil { http.Error(w, "patch not found", http.StatusNotFound) return } curUser := GetUser(r) if !uis.canEditPatch(curUser, projCtx.Patch) { http.Error(w, "Not authorized to schedule patch", http.StatusUnauthorized) return } // grab patch again, as the diff was excluded var err error projCtx.Patch, err = patch.FindOne(patch.ById(projCtx.Patch.Id)) if err != nil { http.Error(w, fmt.Sprintf("error loading patch: %v", err), http.StatusInternalServerError) return } // Unmarshal the project config and set it in the project context project := &model.Project{} if err := yaml.Unmarshal([]byte(projCtx.Patch.PatchedConfig), project); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error unmarshaling project config: %v", err)) } projCtx.Project = project patchUpdateReq := patchVariantsTasksRequest{} err = util.ReadJSONInto(r.Body, &patchUpdateReq) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } var pairs []model.TVPair if len(patchUpdateReq.VariantsTasks) > 0 { pairs = model.VariantTasksToTVPairs(patchUpdateReq.VariantsTasks) } else { for _, v := range patchUpdateReq.Variants { for _, t := range patchUpdateReq.Tasks { if project.FindTaskForVariant(t, v) != nil { pairs = append(pairs, model.TVPair{v, t}) } } } } pairs = model.IncludePatchDependencies(projCtx.Project, pairs) if err = model.ValidateTVPairs(projCtx.Project, pairs); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // update the description for both reconfigured and new patches if err = projCtx.Patch.SetDescription(patchUpdateReq.Description); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error setting description: %v", err)) return } // update the description for both reconfigured and new patches if err = projCtx.Patch.SetVariantsTasks(model.TVPairsToVariantTasks(pairs)); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error setting description: %v", err)) return } if projCtx.Patch.Version != "" { projCtx.Patch.Activated = true // This patch has already been finalized, just add the new builds and tasks if projCtx.Version == nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Couldn't find patch for id %v", projCtx.Patch.Version)) return } // First add new tasks to existing builds, if necessary err = model.AddNewTasksForPatch(projCtx.Patch, projCtx.Version, projCtx.Project, pairs) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error creating new tasks: `%v` for version `%v`", err, projCtx.Version.Id)) return } err := model.AddNewBuildsForPatch(projCtx.Patch, projCtx.Version, projCtx.Project, pairs) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error creating new builds: `%v` for version `%v`", err, projCtx.Version.Id)) return } PushFlash(uis.CookieStore, r, w, NewSuccessFlash("Builds and tasks successfully added to patch.")) uis.WriteJSON(w, http.StatusOK, struct { VersionId string `json:"version"` }{projCtx.Version.Id}) } else { projCtx.Patch.Activated = true err = projCtx.Patch.SetVariantsTasks(model.TVPairsToVariantTasks(pairs)) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error setting patch variants and tasks: %v", err)) return } ver, err := model.FinalizePatch(projCtx.Patch, &uis.Settings) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error finalizing patch: %v", err)) return } PushFlash(uis.CookieStore, r, w, NewSuccessFlash("Patch builds are scheduled.")) uis.WriteJSON(w, http.StatusOK, struct { VersionId string `json:"version"` }{ver.Id}) } }
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) }
// submitPatch creates the Patch document, adds the patched project config to it, // and saves the patches to GridFS to be retrieved func (as *APIServer) submitPatch(w http.ResponseWriter, r *http.Request) { dbUser := MustHaveUser(r) var apiRequest PatchAPIRequest 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{ ProjectId: 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"), } 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.")) } finalize = data.Finalize apiRequest = PatchAPIRequest{ ProjectId: data.Project, ModuleName: r.FormValue("module"), Githash: data.Githash, PatchContent: data.Patch, BuildVariants: strings.Split(data.Variants, ","), Tasks: data.Tasks, Description: data.Description, } } project, patchDoc, err := apiRequest.CreatePatch( finalize, as.Settings.Credentials["github"], dbUser, &as.Settings) if err != nil { as.LoggedError(w, r, http.StatusBadRequest, fmt.Errorf("Invalid patch: %v", err)) return } //expand tasks and build variants and include dependencies if len(patchDoc.BuildVariants) == 1 && patchDoc.BuildVariants[0] == "all" { patchDoc.BuildVariants = []string{} for _, buildVariant := range project.BuildVariants { if buildVariant.Disabled { continue } patchDoc.BuildVariants = append(patchDoc.BuildVariants, buildVariant.Name) } } if len(patchDoc.Tasks) == 1 && patchDoc.Tasks[0] == "all" { patchDoc.Tasks = []string{} for _, t := range project.Tasks { if t.Patchable != nil && !(*t.Patchable) { continue } patchDoc.Tasks = append(patchDoc.Tasks, t.Name) } } var pairs []model.TVPair for _, v := range patchDoc.BuildVariants { for _, t := range patchDoc.Tasks { if project.FindTaskForVariant(t, v) != nil { pairs = append(pairs, model.TVPair{v, t}) } } } // update variant and tasks to include dependencies pairs = model.IncludePatchDependencies(project, pairs) patchDoc.SyncVariantsTasks(model.TVPairsToVariantTasks(pairs)) 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 (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 } // 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 } patchDoc.PatchedConfig = string(projectYamlBytes) 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 = model.FinalizePatch(patchDoc, &as.Settings); err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } } as.WriteJSON(w, http.StatusCreated, PatchAPIResponse{Patch: patchDoc}) }
func (uis *UIServer) schedulePatch(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) if projCtx.Patch == nil { http.Error(w, "patch not found", http.StatusNotFound) return } // grab patch again, as the diff was excluded var err error projCtx.Patch, err = patch.FindOne(patch.ById(projCtx.Patch.Id)) if err != nil { http.Error(w, fmt.Sprintf("error loading patch: %v", err), http.StatusInternalServerError) return } // Unmarshal the project config and set it in the project context project := &model.Project{} if err := yaml.Unmarshal([]byte(projCtx.Patch.PatchedConfig), project); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error unmarshaling project config: %v", err)) } projCtx.Project = project patchUpdateReq := struct { Variants []string `json:"variants"` Tasks []string `json:"tasks"` Description string `json:"description"` }{} err = util.ReadJSONInto(r.Body, &patchUpdateReq) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Add all dependencies to patchUpdateReq.Tasks and add their variants to patchUpdateReq.Variants // Construct a set of variants to include in patchUpdateReq.Variants updateReqVariants := make(map[string]bool) for _, variant := range patchUpdateReq.Variants { updateReqVariants[variant] = true } // Construct a set of tasks to include in patchUpdateReq.Tasks // Add all dependencies, and add their variants to updateReqVariants updateReqTasks := make(map[string]bool) for _, v := range patchUpdateReq.Variants { for _, t := range projCtx.Project.FindTasksForVariant(v) { for _, task := range patchUpdateReq.Tasks { if t == task { deps, variants, err := getDeps(task, v, projCtx.Project) if err != nil { if err == TaskNotPatchableError { continue } else { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error getting dependencies for task: %v", err)) return } } updateReqTasks[task] = true for _, dep := range deps { updateReqTasks[dep] = true } for _, variant := range variants { updateReqVariants[variant] = true } } } } } // Reset patchUpdateReq.Tasks and patchUpdateReq.Variants patchUpdateReq.Tasks = make([]string, 0, len(updateReqTasks)) for task := range updateReqTasks { patchUpdateReq.Tasks = append(patchUpdateReq.Tasks, task) } patchUpdateReq.Variants = make([]string, 0, len(updateReqVariants)) for variant := range updateReqVariants { patchUpdateReq.Variants = append(patchUpdateReq.Variants, variant) } if projCtx.Patch.Version != "" { // This patch has already been finalized, just add the new builds and tasks if projCtx.Version == nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Couldn't find patch for id %v", projCtx.Patch.Version)) return } // First add new tasks to existing builds, if necessary if len(patchUpdateReq.Tasks) > 0 { err = model.AddNewTasksForPatch(projCtx.Patch, projCtx.Version, projCtx.Project, patchUpdateReq.Tasks) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error creating new tasks: `%v` for version `%v`", err, projCtx.Version.Id)) return } } if len(patchUpdateReq.Variants) > 0 { _, err := model.AddNewBuildsForPatch(projCtx.Patch, projCtx.Version, projCtx.Project, patchUpdateReq.Variants) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error creating new builds: `%v` for version `%v`", err, projCtx.Version.Id)) return } } PushFlash(uis.CookieStore, r, w, NewSuccessFlash("Builds and tasks successfully added to patch.")) uis.WriteJSON(w, http.StatusOK, struct { VersionId string `json:"version"` }{projCtx.Version.Id}) } else { err = projCtx.Patch.SetVariantsAndTasks(patchUpdateReq.Variants, patchUpdateReq.Tasks) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error setting patch variants and tasks: %v", err)) return } if err = projCtx.Patch.SetDescription(patchUpdateReq.Description); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error setting description: %v", err)) return } ver, err := model.FinalizePatch(projCtx.Patch, &uis.Settings) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error finalizing patch: %v", err)) return } PushFlash(uis.CookieStore, r, w, NewSuccessFlash("Patch builds are scheduled.")) uis.WriteJSON(w, http.StatusOK, struct { VersionId string `json:"version"` }{ver.Id}) } }