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  }