github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/service/patch.go (about) 1 package service 2 3 import ( 4 "fmt" 5 "net/http" 6 "strconv" 7 8 "github.com/evergreen-ci/evergreen/model" 9 "github.com/evergreen-ci/evergreen/model/patch" 10 "github.com/evergreen-ci/evergreen/model/user" 11 "github.com/evergreen-ci/evergreen/util" 12 "github.com/mongodb/grip" 13 "github.com/pkg/errors" 14 "gopkg.in/yaml.v2" 15 ) 16 17 type patchVariantsTasksRequest struct { 18 VariantsTasks []patch.VariantTasks `json:"variants_tasks,omitempty"` // new format 19 Variants []string `json:"variants"` // old format 20 Tasks []string `json:"tasks"` // old format 21 Description string `json:"description"` 22 } 23 24 func (uis *UIServer) patchPage(w http.ResponseWriter, r *http.Request) { 25 projCtx := MustHaveProjectContext(r) 26 if projCtx.Patch == nil { 27 http.Error(w, "not found", http.StatusNotFound) 28 return 29 } 30 31 currentUser := MustHaveUser(r) 32 33 var versionAsUI *uiVersion 34 if projCtx.Version != nil { // Patch is already finalized 35 versionAsUI = &uiVersion{ 36 Version: *projCtx.Version, 37 RepoOwner: projCtx.ProjectRef.Owner, 38 Repo: projCtx.ProjectRef.Repo, 39 } 40 } 41 42 // get the new patch document with the patched configuration 43 var err error 44 projCtx.Patch, err = patch.FindOne(patch.ById(projCtx.Patch.Id)) 45 if err != nil { 46 http.Error(w, fmt.Sprintf("error loading patch: %v", err), http.StatusInternalServerError) 47 return 48 } 49 50 // Unmarshal the patch's project config so that it is always up to date with the configuration file in the project 51 project := &model.Project{} 52 if err := yaml.Unmarshal([]byte(projCtx.Patch.PatchedConfig), project); err != nil { 53 uis.LoggedError(w, r, http.StatusInternalServerError, errors.Wrap(err, "Error unmarshaling project config")) 54 } 55 projCtx.Project = project 56 57 // retrieve tasks and variant mappings' names 58 variantMappings := make(map[string]model.BuildVariant) 59 for _, variant := range projCtx.Project.BuildVariants { 60 variantMappings[variant.Name] = variant 61 } 62 63 tasksList := []interface{}{} 64 for _, task := range projCtx.Project.Tasks { 65 // add a task name to the list if it's patchable 66 if !(task.Patchable != nil && !*task.Patchable) { 67 tasksList = append(tasksList, struct{ Name string }{task.Name}) 68 } 69 } 70 71 uis.WriteHTML(w, http.StatusOK, struct { 72 ProjectData projectContext 73 User *user.DBUser 74 Version *uiVersion 75 Variants map[string]model.BuildVariant 76 Tasks []interface{} 77 CanEdit bool 78 }{projCtx, currentUser, versionAsUI, variantMappings, tasksList, uis.canEditPatch(currentUser, projCtx.Patch)}, "base", 79 "patch_version.html", "base_angular.html", "menu.html") 80 } 81 82 func (uis *UIServer) schedulePatch(w http.ResponseWriter, r *http.Request) { 83 projCtx := MustHaveProjectContext(r) 84 if projCtx.Patch == nil { 85 http.Error(w, "patch not found", http.StatusNotFound) 86 return 87 } 88 curUser := GetUser(r) 89 if !uis.canEditPatch(curUser, projCtx.Patch) { 90 http.Error(w, "Not authorized to schedule patch", http.StatusUnauthorized) 91 return 92 } 93 // grab patch again, as the diff was excluded 94 var err error 95 projCtx.Patch, err = patch.FindOne(patch.ById(projCtx.Patch.Id)) 96 if err != nil { 97 http.Error(w, fmt.Sprintf("error loading patch: %v", err), http.StatusInternalServerError) 98 return 99 } 100 101 // Unmarshal the project config and set it in the project context 102 project := &model.Project{} 103 if err = yaml.Unmarshal([]byte(projCtx.Patch.PatchedConfig), project); err != nil { 104 uis.LoggedError(w, r, http.StatusInternalServerError, errors.Errorf("Error unmarshaling project config: %v", err)) 105 } 106 projCtx.Project = project 107 108 patchUpdateReq := patchVariantsTasksRequest{} 109 110 if err = util.ReadJSONInto(util.NewRequestReader(r), &patchUpdateReq); err != nil { 111 http.Error(w, err.Error(), http.StatusBadRequest) 112 return 113 } 114 115 var pairs []model.TVPair 116 if len(patchUpdateReq.VariantsTasks) > 0 { 117 pairs = model.VariantTasksToTVPairs(patchUpdateReq.VariantsTasks) 118 } else { 119 for _, v := range patchUpdateReq.Variants { 120 for _, t := range patchUpdateReq.Tasks { 121 if project.FindTaskForVariant(t, v) != nil { 122 pairs = append(pairs, model.TVPair{v, t}) 123 } 124 } 125 } 126 } 127 128 pairs = model.IncludePatchDependencies(projCtx.Project, pairs) 129 130 if err = model.ValidateTVPairs(projCtx.Project, pairs); err != nil { 131 http.Error(w, err.Error(), http.StatusBadRequest) 132 return 133 } 134 135 // update the description for both reconfigured and new patches 136 if err = projCtx.Patch.SetDescription(patchUpdateReq.Description); err != nil { 137 uis.LoggedError(w, r, http.StatusInternalServerError, 138 errors.Wrap(err, "Error setting description")) 139 return 140 } 141 142 // update the description for both reconfigured and new patches 143 if err = projCtx.Patch.SetVariantsTasks(model.TVPairsToVariantTasks(pairs)); err != nil { 144 uis.LoggedError(w, r, http.StatusInternalServerError, 145 errors.Wrap(err, "Error setting description")) 146 return 147 } 148 149 if projCtx.Patch.Version != "" { 150 projCtx.Patch.Activated = true 151 // This patch has already been finalized, just add the new builds and tasks 152 if projCtx.Version == nil { 153 uis.LoggedError(w, r, http.StatusInternalServerError, 154 errors.Errorf("Couldn't find patch for id %v", projCtx.Patch.Version)) 155 return 156 } 157 158 // First add new tasks to existing builds, if necessary 159 err = model.AddNewTasksForPatch(projCtx.Patch, projCtx.Version, projCtx.Project, pairs) 160 if err != nil { 161 uis.LoggedError(w, r, http.StatusInternalServerError, 162 errors.Wrapf(err, "Error creating new tasks for version `%v`", projCtx.Version.Id)) 163 return 164 } 165 166 err := model.AddNewBuildsForPatch(projCtx.Patch, projCtx.Version, projCtx.Project, pairs) 167 if err != nil { 168 uis.LoggedError(w, r, http.StatusInternalServerError, 169 errors.Wrapf(err, "Error creating new builds for version `%v`", err, projCtx.Version.Id)) 170 return 171 } 172 173 PushFlash(uis.CookieStore, r, w, NewSuccessFlash("Builds and tasks successfully added to patch.")) 174 uis.WriteJSON(w, http.StatusOK, struct { 175 VersionId string `json:"version"` 176 }{projCtx.Version.Id}) 177 } else { 178 projCtx.Patch.Activated = true 179 err = projCtx.Patch.SetVariantsTasks(model.TVPairsToVariantTasks(pairs)) 180 if err != nil { 181 uis.LoggedError(w, r, http.StatusInternalServerError, 182 errors.Wrap(err, "Error setting patch variants and tasks")) 183 return 184 } 185 186 ver, err := model.FinalizePatch(projCtx.Patch, &uis.Settings) 187 if err != nil { 188 uis.LoggedError(w, r, http.StatusInternalServerError, 189 errors.Wrap(err, "Error finalizing patch")) 190 return 191 } 192 PushFlash(uis.CookieStore, r, w, NewSuccessFlash("Patch builds are scheduled.")) 193 uis.WriteJSON(w, http.StatusOK, struct { 194 VersionId string `json:"version"` 195 }{ver.Id}) 196 } 197 } 198 199 func (uis *UIServer) diffPage(w http.ResponseWriter, r *http.Request) { 200 projCtx := MustHaveProjectContext(r) 201 if projCtx.Patch == nil { 202 http.Error(w, "patch not found", http.StatusNotFound) 203 return 204 } 205 // We have to reload the patch outside of the project context, 206 // since the raw diff is excluded by default. This redundancy is 207 // worth the time savings this behavior offers other pages. 208 fullPatch, err := patch.FindOne(patch.ById(projCtx.Patch.Id)) 209 if err != nil { 210 http.Error(w, fmt.Sprintf("error loading patch: %v", err.Error), 211 http.StatusInternalServerError) 212 return 213 } 214 if err = fullPatch.FetchPatchFiles(); err != nil { 215 http.Error(w, fmt.Sprintf("finding patch files: %v", err.Error), 216 http.StatusInternalServerError) 217 return 218 } 219 uis.WriteHTML(w, http.StatusOK, fullPatch, "base", "diff.html") 220 } 221 222 func (uis *UIServer) fileDiffPage(w http.ResponseWriter, r *http.Request) { 223 projCtx := MustHaveProjectContext(r) 224 if projCtx.Patch == nil { 225 http.Error(w, "patch not found", http.StatusNotFound) 226 return 227 } 228 fullPatch, err := patch.FindOne(patch.ById(projCtx.Patch.Id)) 229 if err != nil { 230 http.Error(w, fmt.Sprintf("error loading patch: %v", err.Error), 231 http.StatusInternalServerError) 232 return 233 } 234 if err = fullPatch.FetchPatchFiles(); err != nil { 235 http.Error(w, fmt.Sprintf("error finding patch: %v", err.Error), 236 http.StatusInternalServerError) 237 } 238 uis.WriteHTML(w, http.StatusOK, struct { 239 Data patch.Patch 240 FileName string 241 PatchNumber string 242 }{*fullPatch, r.FormValue("file_name"), r.FormValue("patch_number")}, 243 "base", "file_diff.html") 244 } 245 246 func (uis *UIServer) rawDiffPage(w http.ResponseWriter, r *http.Request) { 247 projCtx := MustHaveProjectContext(r) 248 if projCtx.Patch == nil { 249 http.Error(w, "patch not found", http.StatusNotFound) 250 return 251 } 252 fullPatch, err := patch.FindOne(patch.ById(projCtx.Patch.Id)) 253 if err != nil { 254 http.Error(w, fmt.Sprintf("error loading patch: %v", err.Error), 255 http.StatusInternalServerError) 256 return 257 } 258 if err = fullPatch.FetchPatchFiles(); err != nil { 259 http.Error(w, fmt.Sprintf("error fetching patch files: %v", err.Error), 260 http.StatusInternalServerError) 261 return 262 } 263 patchNum, err := strconv.Atoi(r.FormValue("patch_number")) 264 if err != nil { 265 http.Error(w, fmt.Sprintf("error getting patch number: %v", err.Error), 266 http.StatusInternalServerError) 267 return 268 } 269 if patchNum < 0 || patchNum >= len(fullPatch.Patches) { 270 http.Error(w, "patch number out of range", http.StatusInternalServerError) 271 return 272 } 273 diff := fullPatch.Patches[patchNum].PatchSet.Patch 274 w.Header().Set("Content-Type", "text/plain") 275 w.WriteHeader(http.StatusOK) 276 _, err = w.Write([]byte(diff)) 277 grip.Warning(err) 278 }