// GetProjectConfig loads the communicator's task's project from the API server. func (h *HTTPCommunicator) GetProjectRef() (*model.ProjectRef, error) { projectRef := &model.ProjectRef{} retriableGet := util.RetriableFunc( func() error { resp, err := h.tryGet("project_ref") if resp != nil { defer resp.Body.Close() } if resp != nil && resp.StatusCode == http.StatusConflict { // Something very wrong, fail now with no retry. return fmt.Errorf("conflict - wrong secret!") } if err != nil { // Some generic error trying to connect - try again return util.RetriableError{err} } if resp == nil { return util.RetriableError{fmt.Errorf("empty response")} } else { err = util.ReadJSONInto(resp.Body, projectRef) if err != nil { return util.RetriableError{err} } return nil } }, ) retryFail, err := util.Retry(retriableGet, h.MaxAttempts, h.RetrySleep) if retryFail { return nil, fmt.Errorf("getting project ref failed after %v tries: %v", h.MaxAttempts, err) } return projectRef, nil }
// Log sends a batch of log messages for the task's logs to the API server. func (h *HTTPCommunicator) Log(messages []model.LogMessage) error { outgoingData := model.TaskLog{ TaskId: h.TaskId, Timestamp: time.Now(), MessageCount: len(messages), Messages: messages, } retriableLog := util.RetriableFunc( func() error { resp, err := h.TryPostJSON("log", outgoingData) if resp != nil { defer resp.Body.Close() } if err != nil { return util.RetriableError{err} } if resp.StatusCode == http.StatusInternalServerError { return util.RetriableError{fmt.Errorf("http status %v response body %v", resp.StatusCode, resp.Body)} } return nil }, ) retryFail, err := util.Retry(retriableLog, h.MaxAttempts, h.RetrySleep) if retryFail { return fmt.Errorf("logging failed after %vtries: %v", h.MaxAttempts, err) } return err }
// PostTaskFiles is used by the PluginCommunicator interface for attaching task files. func (t *TaskJSONCommunicator) PostTaskFiles(task_files []*artifact.File) error { retriableSendFile := util.RetriableFunc( func() error { resp, err := t.TryPostJSON("files", task_files) if resp != nil { defer resp.Body.Close() } if err != nil { err := fmt.Errorf("error posting results: %v", err) return util.RetriableError{err} } body, readAllErr := ioutil.ReadAll(resp.Body) bodyErr := fmt.Errorf("error posting results (%v): %v", resp.StatusCode, string(body)) if readAllErr != nil { return bodyErr } switch resp.StatusCode { case http.StatusOK: return nil case http.StatusBadRequest: return bodyErr default: return util.RetriableError{bodyErr} } }, ) retryFail, err := util.RetryArithmeticBackoff(retriableSendFile, 10, 1) if retryFail { return fmt.Errorf("attaching task files failed after %v tries: %v", 10, err) } return nil }
// TaskPostResults posts a set of test results for the communicator's task. func (t *TaskJSONCommunicator) TaskPostResults(results *model.TestResults) error { retriableSendFile := util.RetriableFunc( func() error { resp, err := t.tryPostJSON("results", results) if resp != nil { defer resp.Body.Close() } if err != nil { err := fmt.Errorf("error posting results: %v", err) return util.RetriableError{err} } body, _ := ioutil.ReadAll(resp.Body) bodyErr := fmt.Errorf("error posting results (%v): %v", resp.StatusCode, string(body)) switch resp.StatusCode { case http.StatusOK: return nil case http.StatusBadRequest: return bodyErr default: return util.RetriableError{bodyErr} } }, ) retryFail, err := util.RetryArithmeticBackoff(retriableSendFile, 10, 1) if retryFail { return fmt.Errorf("attaching test results failed after %v tries: %v", 10, err) } return nil }
func (h *HTTPCommunicator) postJSON(path string, data interface{}) ( resp *http.Response, retryFail bool, err error) { retriablePost := util.RetriableFunc( func() error { resp, err = h.tryPostJSON(path, data) if err == nil && resp.StatusCode == http.StatusOK { return nil } if resp != nil && resp.StatusCode == http.StatusConflict { h.Logger.Logf(slogger.ERROR, "received 409 conflict error") return HTTPConflictError } if err != nil { h.Logger.Logf(slogger.ERROR, "HTTP Post failed on '%v': %v", path, err) return util.RetriableError{err} } else { h.Logger.Logf(slogger.ERROR, "bad response '%v' posting to "+ "'%v'", resp.StatusCode, path) return util.RetriableError{fmt.Errorf("unexpected status "+ "code: %v", resp.StatusCode)} } }, ) retryFail, err = util.Retry(retriablePost, h.MaxAttempts, h.RetrySleep) return resp, retryFail, err }
// Wrapper around the Put() function to retry it func (s3pc *S3PutCommand) PutWithRetry(log plugin.Logger, com plugin.PluginCommunicator) error { retriablePut := util.RetriableFunc( func() error { err := s3pc.Put() if err != nil { if err == errSkippedFile { return err } log.LogExecution(slogger.ERROR, "Error putting to s3 bucket: %v", err) return util.RetriableError{err} } return nil }, ) retryFail, err := util.RetryArithmeticBackoff(retriablePut, maxS3PutAttempts, s3PutSleep) if err == errSkippedFile { log.LogExecution(slogger.INFO, "S3 put skipped optional missing file.") return nil } if retryFail { log.LogExecution(slogger.ERROR, "S3 put failed with error: %v", err) return err } return s3pc.AttachTaskFiles(log, com) }
// tryGithubPost posts the data to the Github api endpoint with the url given func tryGithubPost(url string, oauthToken string, data interface{}) (resp *http.Response, err error) { evergreen.Logger.Logf(slogger.ERROR, "Attempting GitHub API POST at ‘%v’", url) retriableGet := util.RetriableFunc( func() (retryError error) { resp, err = githubRequest("POST", url, oauthToken, data) if err != nil { evergreen.Logger.Logf(slogger.ERROR, "failed trying to call github POST on %v: %v", url, err) return util.RetriableError{err} } if resp.StatusCode == http.StatusUnauthorized { err = fmt.Errorf("Calling github POST on %v failed: got 'unauthorized' response", url) evergreen.Logger.Logf(slogger.ERROR, err.Error()) return err } if resp.StatusCode != http.StatusOK { err = fmt.Errorf("Calling github POST on %v got a bad response code: %v", url, resp.StatusCode) } // read the results rateMessage, loglevel := getGithubRateLimit(resp.Header) evergreen.Logger.Logf(loglevel, "Github API response: %v. %v", resp.Status, rateMessage) return nil }, ) retryFail, err := util.Retry(retriableGet, NumGithubRetries, GithubSleepTimeSecs*time.Second) if err != nil { // couldn't post it if retryFail { evergreen.Logger.Logf(slogger.ERROR, "Github POST on %v used up all retries.") } return nil, err } return }
func tryGithubGet(oauthToken, url string) (resp *http.Response, err error) { evergreen.Logger.Logf(slogger.INFO, "Attempting GitHub API call at ‘%v’", url) retriableGet := util.RetriableFunc( func() error { resp, err = githubRequest("GET", url, oauthToken, nil) if err != nil { evergreen.Logger.Logf(slogger.ERROR, "failed trying to call github GET on %v: %v", url, err) return util.RetriableError{err} } if resp.StatusCode == http.StatusUnauthorized { err = fmt.Errorf("Calling github GET on %v failed: got 'unauthorized' response", url) evergreen.Logger.Logf(slogger.ERROR, err.Error()) return err } if resp.StatusCode != http.StatusOK { err = fmt.Errorf("Calling github GET on %v got a bad response code: %v", url, resp.StatusCode) } // read the results rateMessage, _ := getGithubRateLimit(resp.Header) evergreen.Logger.Logf(slogger.DEBUG, "Github API repsonse: %v. %v", resp.Status, rateMessage) return nil }, ) retryFail, err := util.Retry(retriableGet, NumGithubRetries, GithubSleepTimeSecs*time.Second) if err != nil { // couldn't get it if retryFail { evergreen.Logger.Logf(slogger.ERROR, "Github GET on %v used up all retries.", err) } return nil, err } return }
func (jsc *JSONSendCommand) Execute(log plugin.Logger, com plugin.PluginCommunicator, conf *model.TaskConfig, stop chan bool) error { if jsc.File == "" { return fmt.Errorf("'file' param must not be blank") } if jsc.DataName == "" { return fmt.Errorf("'name' param must not be blank") } errChan := make(chan error) go func() { // attempt to open the file fileLoc := filepath.Join(conf.WorkDir, jsc.File) jsonFile, err := os.Open(fileLoc) if err != nil { errChan <- fmt.Errorf("Couldn't open json file: '%v'", err) return } jsonData := map[string]interface{}{} err = util.ReadJSONInto(jsonFile, &jsonData) if err != nil { errChan <- fmt.Errorf("File contained invalid json: %v", err) return } retriablePost := util.RetriableFunc( func() error { log.LogTask(slogger.INFO, "Posting JSON") resp, err := com.TaskPostJSON(fmt.Sprintf("data/%v", jsc.DataName), jsonData) if resp != nil { defer resp.Body.Close() } if err != nil { return util.RetriableError{err} } if resp.StatusCode != http.StatusOK { return util.RetriableError{fmt.Errorf("unexpected status code %v", resp.StatusCode)} } return nil }, ) _, err = util.Retry(retriablePost, 10, 3*time.Second) errChan <- err }() select { case err := <-errChan: if err != nil { log.LogTask(slogger.ERROR, "Sending json data failed: %v", err) } return err case <-stop: log.LogExecution(slogger.INFO, "Received abort signal, stopping.") return nil } }
func (jgc *JSONHistoryCommand) Execute(log plugin.Logger, com plugin.PluginCommunicator, conf *model.TaskConfig, stop chan bool) error { err := plugin.ExpandValues(jgc, conf.Expansions) if err != nil { return err } if jgc.File == "" { return fmt.Errorf("'file' param must not be blank") } if jgc.DataName == "" { return fmt.Errorf("'name' param must not be blank") } if jgc.TaskName == "" { return fmt.Errorf("'task' param must not be blank") } if jgc.File != "" && !filepath.IsAbs(jgc.File) { jgc.File = filepath.Join(conf.WorkDir, jgc.File) } endpoint := fmt.Sprintf("history/%s/%s", jgc.TaskName, jgc.DataName) if jgc.Tags { endpoint = fmt.Sprintf("tags/%s/%s", jgc.TaskName, jgc.DataName) } retriableGet := util.RetriableFunc( func() error { resp, err := com.TaskGetJSON(endpoint) if resp != nil { defer resp.Body.Close() } if err != nil { //Some generic error trying to connect - try again log.LogExecution(slogger.WARN, "Error connecting to API server: %v", err) return util.RetriableError{err} } if resp.StatusCode == http.StatusOK { jsonBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return err } return ioutil.WriteFile(jgc.File, jsonBytes, 0755) } if resp.StatusCode != http.StatusOK { if resp.StatusCode == http.StatusNotFound { return fmt.Errorf("No JSON data found") } return util.RetriableError{fmt.Errorf("unexpected status code %v", resp.StatusCode)} } return nil }, ) _, err = util.Retry(retriableGet, 10, 3*time.Second) return err }
// FetchExpansionVars loads expansions for a communicator's task from the API server. func (h *HTTPCommunicator) FetchExpansionVars() (*apimodels.ExpansionVars, error) { resultVars := &apimodels.ExpansionVars{} retriableGet := util.RetriableFunc( func() error { resp, err := h.tryGet("fetch_vars") if resp != nil { defer resp.Body.Close() } if err != nil { // Some generic error trying to connect - try again h.Logger.Logf(slogger.ERROR, "failed trying to call fetch GET: %v", err) return util.RetriableError{err} } if resp.StatusCode == http.StatusUnauthorized { err = fmt.Errorf("fetching expansions failed: got 'unauthorized' response.") h.Logger.Logf(slogger.ERROR, err.Error()) return err } if resp.StatusCode != http.StatusOK { err = fmt.Errorf("failed trying fetch GET, got bad response code: %v", resp.StatusCode) h.Logger.Logf(slogger.ERROR, err.Error()) return util.RetriableError{err} } if resp == nil { err = fmt.Errorf("empty response fetching expansions") h.Logger.Logf(slogger.ERROR, err.Error()) return util.RetriableError{err} } // got here safely, so all is good - read the results err = util.ReadJSONInto(resp.Body, resultVars) if err != nil { err = fmt.Errorf("failed to read vars from response: %v", err) h.Logger.Logf(slogger.ERROR, err.Error()) return err } return nil }, ) retryFail, err := util.Retry(retriableGet, 10, 1*time.Second) if err != nil { // stop trying to make fetch happen, it's not going to happen if retryFail { h.Logger.Logf(slogger.ERROR, "Fetching vars used up all retries.") } return nil, err } return resultVars, err }
// GetProjectConfig loads the communicator's task's project from the API server. func (h *HTTPCommunicator) GetProjectConfig() (*model.Project, error) { projectConfig := &model.Project{} retriableGet := util.RetriableFunc( func() error { resp, err := h.tryGet("version") if resp != nil { defer resp.Body.Close() } if resp != nil && resp.StatusCode == http.StatusConflict { // Something very wrong, fail now with no retry. return fmt.Errorf("conflict - wrong secret!") } if err != nil { // Some generic error trying to connect - try again return util.RetriableError{err} } if resp == nil { return util.RetriableError{fmt.Errorf("empty response")} } else { v := &version.Version{} err = util.ReadJSONInto(resp.Body, v) if err != nil { h.Logger.Errorf(slogger.ERROR, "unable to read project version response: %v\n", err) return util.RetriableError{fmt.Errorf("unable to read "+ "project version response: %v\n", err)} } err = model.LoadProjectInto([]byte(v.Config), v.Project, projectConfig) if err != nil { h.Logger.Errorf(slogger.ERROR, "unable to unmarshal project config: %v\n", err) return util.RetriableError{fmt.Errorf("unable to "+ "unmarshall project config: %v\n", err)} } return nil } }, ) retryFail, err := util.Retry(retriableGet, h.MaxAttempts, h.RetrySleep) if retryFail { return nil, fmt.Errorf("getting project configuration failed after %v "+ "tries: %v", h.MaxAttempts, err) } return projectConfig, nil }
// GetVersion loads the communicator's task's version from the API server. func (h *HTTPCommunicator) GetVersion() (*version.Version, error) { v := &version.Version{} retriableGet := util.RetriableFunc( func() error { resp, err := h.TryGet("version") if resp != nil { defer resp.Body.Close() } if resp != nil { if resp.StatusCode == http.StatusConflict { // Something very wrong, fail now with no retry. return fmt.Errorf("conflict - wrong secret!") } if resp.StatusCode != http.StatusOK { msg, _ := ioutil.ReadAll(resp.Body) // ignore ReadAll error return util.RetriableError{ fmt.Errorf("bad status code %v: %v", resp.StatusCode, string(msg)), } } } if err != nil { // Some generic error trying to connect - try again return util.RetriableError{err} } if resp == nil { return util.RetriableError{fmt.Errorf("empty response")} } else { err = util.ReadJSONInto(resp.Body, v) if err != nil { h.Logger.Errorf(slogger.ERROR, "unable to read project version response: %v\n", err) return fmt.Errorf("unable to read project version response: %v\n", err) } return nil } }, ) retryFail, err := util.Retry(retriableGet, h.MaxAttempts, h.RetrySleep) if retryFail { return nil, fmt.Errorf("getting project configuration failed after %v "+ "tries: %v", h.MaxAttempts, err) } return v, nil }
// getPatchContents() dereferences any patch files that are stored externally, fetching them from // the API server, and setting them into the patch object. func (gapc GitApplyPatchCommand) getPatchContents(conf *model.TaskConfig, com plugin.PluginCommunicator, log plugin.Logger, p *patch.Patch) error { for i, patchPart := range p.Patches { // If the patch isn't stored externally, no need to do anything. if patchPart.PatchSet.PatchFileId == "" { continue } // otherwise, fetch the contents and load it into the patch object log.LogExecution(slogger.INFO, "Fetching patch contents for %v", patchPart.PatchSet.PatchFileId) var result []byte retriableGet := util.RetriableFunc( func() error { resp, err := com.TaskGetJSON(fmt.Sprintf("%s/%s", GitPatchFilePath, patchPart.PatchSet.PatchFileId)) if resp != nil { defer resp.Body.Close() } if err != nil { //Some generic error trying to connect - try again log.LogExecution(slogger.WARN, "Error connecting to API server: %v", err) return util.RetriableError{err} } if resp != nil && resp.StatusCode != http.StatusOK { log.LogExecution(slogger.WARN, "Unexpected status code %v, retrying", resp.StatusCode) resp.Body.Close() return util.RetriableError{fmt.Errorf("Unexpected status code %v", resp.StatusCode)} } result, err = ioutil.ReadAll(resp.Body) if err != nil { return err } return nil }) _, err := util.RetryArithmeticBackoff(retriableGet, 5, 5*time.Second) if err != nil { return err } p.Patches[i].PatchSet.Patch = string(result) } return nil }
// TaskPostTestLog posts a test log for a communicator's task. func (t *TaskJSONCommunicator) TaskPostTestLog(log *model.TestLog) (string, error) { var logId string retriableSendFile := util.RetriableFunc( func() error { resp, err := t.TryPostJSON("test_logs", log) if err != nil { err := fmt.Errorf("error posting logs: %v", err) if resp != nil { resp.Body.Close() } return util.RetriableError{err} } if resp.StatusCode == http.StatusOK { logReply := struct { Id string `json:"_id"` }{} err = util.ReadJSONInto(resp.Body, &logReply) if err != nil { return err } logId = logReply.Id return nil } bodyErr, err := ioutil.ReadAll(resp.Body) if err != nil { return util.RetriableError{err} } if resp.StatusCode == http.StatusBadRequest { return fmt.Errorf("bad request posting logs: %v", string(bodyErr)) } return util.RetriableError{fmt.Errorf("failed posting logs: %v", string(bodyErr))} }, ) retryFail, err := util.RetryArithmeticBackoff(retriableSendFile, 10, 1) if retryFail { return "", fmt.Errorf("attaching test logs failed after %v tries: %v", 10, err) } return logId, nil }
// Wrapper around the Put() function to retry it func (self *S3PutCommand) PutWithRetry(pluginLogger plugin.Logger, pluginComm plugin.PluginCommunicator) error { retriablePut := util.RetriableFunc( func() error { err := self.Put() if err != nil { pluginLogger.LogExecution(slogger.ERROR, "Error putting to s3"+ " bucket: %v", err) return util.RetriableError{err} } return nil }, ) retryFail, err := util.RetryArithmeticBackoff(retriablePut, maxS3PutAttempts, s3PutSleep) if retryFail { pluginLogger.LogExecution(slogger.ERROR, "S3 put failed with error: %v", err) return err } return self.AttachTaskFiles(pluginLogger, pluginComm) }
// GetDistro returns the distro for the communicator's task. func (h *HTTPCommunicator) GetDistro() (*distro.Distro, error) { d := &distro.Distro{} retriableGet := util.RetriableFunc( func() error { resp, err := h.tryGet("distro") if resp != nil { defer resp.Body.Close() } if resp != nil && resp.StatusCode == http.StatusConflict { // Something very wrong, fail now with no retry. return fmt.Errorf("conflict - wrong secret!") } if err != nil { // Some generic error trying to connect - try again return util.RetriableError{err} } if resp == nil { return util.RetriableError{fmt.Errorf("empty response")} } else { err = util.ReadJSONInto(resp.Body, d) if err != nil { h.Logger.Errorf(slogger.ERROR, "unable to read distro response: %v\n", err) return util.RetriableError{err} } return nil } }, ) retryFail, err := util.Retry(retriableGet, h.MaxAttempts, h.RetrySleep) if retryFail { return nil, fmt.Errorf("getting distro failed after %v tries: %v", h.MaxAttempts, err) } return d, nil }
// Load performs a GET on /manifest/load func (mfc *ManifestLoadCommand) Load(log plugin.Logger, pluginCom plugin.PluginCommunicator, conf *model.TaskConfig) error { var loadedManifest *manifest.Manifest var err error retriableGet := util.RetriableFunc( func() error { resp, err := pluginCom.TaskGetJSON(ManifestLoadAPIEndpoint) if resp != nil { defer resp.Body.Close() } if err != nil { //Some generic error trying to connect - try again log.LogExecution(slogger.WARN, "Error connecting to API server: %v", err) return util.RetriableError{err} } if resp != nil && resp.StatusCode != http.StatusOK { log.LogExecution(slogger.WARN, "Unexpected status code %v, retrying", resp.StatusCode) return util.RetriableError{fmt.Errorf("Unexpected status code %v", resp.StatusCode)} } err = util.ReadJSONInto(resp.Body, &loadedManifest) if err != nil { return err } return nil }) _, err = util.RetryArithmeticBackoff(retriableGet, 5, 5*time.Second) if err != nil { return err } if loadedManifest == nil { return fmt.Errorf("Manifest is empty") } mfc.updateExpansions(loadedManifest, conf) return nil }
// Wrapper around the Get() function to retry it func (self *S3GetCommand) GetWithRetry(pluginLogger plugin.Logger) error { retriableGet := util.RetriableFunc( func() error { pluginLogger.LogTask(slogger.INFO, "Fetching %v from"+ " s3 bucket %v", self.RemoteFile, self.Bucket) err := self.Get() if err != nil { pluginLogger.LogExecution(slogger.ERROR, "Error getting from"+ " s3 bucket: %v", err) return util.RetriableError{err} } return nil }, ) retryFail, err := util.RetryArithmeticBackoff(retriableGet, MaxS3GetAttempts, S3GetSleep) if retryFail { pluginLogger.LogExecution(slogger.ERROR, "S3 get failed with error: %v", err) return err } return nil }
// GetTask returns the communicator's task. func (h *HTTPCommunicator) GetTask() (*task.Task, error) { task := &task.Task{} retriableGet := util.RetriableFunc( func() error { resp, err := h.tryGet("") if resp != nil { defer resp.Body.Close() } if resp != nil && resp.StatusCode == http.StatusConflict { // Something very wrong, fail now with no retry. return fmt.Errorf("conflict - wrong secret!") } if err != nil { // Some generic error trying to connect - try again return util.RetriableError{err} } if resp == nil { return util.RetriableError{fmt.Errorf("empty response")} } else { err = util.ReadJSONInto(resp.Body, task) if err != nil { fmt.Printf("error3, retrying: %v\n", err) return util.RetriableError{err} } return nil } }, ) retryFail, err := util.Retry(retriableGet, h.MaxAttempts, h.RetrySleep) if retryFail { return nil, fmt.Errorf("getting task failed after %v tries: %v", h.MaxAttempts, err) } return task, nil }
// SendJSONResults is responsible for sending the // specified file to the API Server func (self *AttachTaskFilesCommand) SendTaskFiles(taskConfig *model.TaskConfig, pluginLogger plugin.Logger, pluginCom plugin.PluginCommunicator) error { // log each file attachment for name, link := range self.Files { pluginLogger.LogTask(slogger.INFO, "Attaching file: %v -> %v", name, link) } retriableSendFile := util.RetriableFunc( func() error { resp, err := pluginCom.TaskPostJSON( AttachTaskFilesAPIEndpoint, self.Files.Array(), ) if resp != nil { defer resp.Body.Close() } if resp != nil && resp.StatusCode == http.StatusConflict { body, _ := ioutil.ReadAll(resp.Body) msg := fmt.Sprintf( "secret conflict while posting task files: %v", string(body)) pluginLogger.LogTask(slogger.ERROR, msg) return fmt.Errorf(msg) } if resp != nil && resp.StatusCode == http.StatusBadRequest { body, _ := ioutil.ReadAll(resp.Body) msg := fmt.Sprintf( "error posting task files (%v): %v", resp.StatusCode, string(body)) pluginLogger.LogTask(slogger.ERROR, msg) return fmt.Errorf(msg) } if resp != nil && resp.StatusCode != http.StatusOK { body, _ := ioutil.ReadAll(resp.Body) msg := fmt.Sprintf("error posting task files (%v): %v", resp.StatusCode, string(body)) pluginLogger.LogExecution(slogger.WARN, msg) return util.RetriableError{err} } if err != nil { msg := fmt.Sprintf("error posting files: %v", err) pluginLogger.LogExecution(slogger.WARN, msg) return util.RetriableError{fmt.Errorf(msg)} } return nil }, ) retryFail, err := util.Retry(retriableSendFile, AttachResultsPostRetries, AttachResultsRetrySleepSec) if retryFail { return fmt.Errorf("Attach files failed after %v tries: %v", AttachResultsPostRetries, err) } if err != nil { return fmt.Errorf("Attach files failed: %v", err) } pluginLogger.LogExecution(slogger.INFO, "API attach files call succeeded") return nil }
// GetPatch tries to get the patch data from the server in json format, // and unmarhals it into a patch struct. The GET request is attempted // multiple times upon failure. func (gapc GitApplyPatchCommand) GetPatch(conf *model.TaskConfig, pluginCom plugin.PluginCommunicator, pluginLogger plugin.Logger) (*patch.Patch, error) { patch := &patch.Patch{} retriableGet := util.RetriableFunc( func() error { resp, err := pluginCom.TaskGetJSON(GitPatchPath) if resp != nil { defer resp.Body.Close() } if err != nil { //Some generic error trying to connect - try again pluginLogger.LogExecution(slogger.WARN, "Error connecting to API server: %v", err) return util.RetriableError{err} } if resp != nil && resp.StatusCode == http.StatusNotFound { //nothing broke, but no patch was found for task Id - no retry body, err := ioutil.ReadAll(resp.Body) if err != nil { pluginLogger.LogExecution(slogger.ERROR, "Error reading response body") } msg := fmt.Sprintf("no patch found for task: %v", string(body)) pluginLogger.LogExecution(slogger.WARN, msg) return fmt.Errorf(msg) } if resp != nil && resp.StatusCode == http.StatusInternalServerError { //something went wrong in api server body, err := ioutil.ReadAll(resp.Body) if err != nil { pluginLogger.LogExecution(slogger.ERROR, "Error reading response body") } msg := fmt.Sprintf("error fetching patch from server: %v", string(body)) pluginLogger.LogExecution(slogger.WARN, msg) return util.RetriableError{ fmt.Errorf(msg), } } if resp != nil && resp.StatusCode == http.StatusConflict { //wrong secret body, err := ioutil.ReadAll(resp.Body) if err != nil { pluginLogger.LogExecution(slogger.ERROR, "Error reading response body") } msg := fmt.Sprintf("secret conflict: %v", string(body)) pluginLogger.LogExecution(slogger.ERROR, msg) return fmt.Errorf(msg) } if resp == nil { pluginLogger.LogExecution(slogger.WARN, "Empty response from API server") return util.RetriableError{fmt.Errorf("empty response")} } else { err = util.ReadJSONInto(resp.Body, patch) if err != nil { pluginLogger.LogExecution(slogger.ERROR, "Error reading json into patch struct: %v", err) return util.RetriableError{err} } return nil } }, ) retryFail, err := util.RetryArithmeticBackoff(retriableGet, 5, 5*time.Second) if retryFail { return nil, fmt.Errorf("getting patch failed after %v tries: %v", 10, err) } if err != nil { return nil, fmt.Errorf("getting patch failed: %v", err) } return patch, nil }