github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/plugin/builtin/taskdata/json.go (about)

     1  package taskdata
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/evergreen-ci/evergreen/db"
    12  	"github.com/evergreen-ci/evergreen/model"
    13  	"github.com/evergreen-ci/evergreen/model/task"
    14  	"github.com/evergreen-ci/evergreen/plugin"
    15  	"github.com/evergreen-ci/evergreen/util"
    16  	"github.com/gorilla/mux"
    17  	"github.com/mitchellh/mapstructure"
    18  	"github.com/mongodb/grip/slogger"
    19  	"github.com/pkg/errors"
    20  	"gopkg.in/mgo.v2/bson"
    21  )
    22  
    23  func init() {
    24  	plugin.Publish(&TaskJSONPlugin{})
    25  }
    26  
    27  const (
    28  	TaskJSONPluginName = "json"
    29  	TaskJSONSend       = "send"
    30  	TaskJSONGet        = "get"
    31  	TaskJSONGetHistory = "get_history"
    32  	TaskJSONHistory    = "history"
    33  )
    34  
    35  // TaskJSONPlugin handles thet
    36  type TaskJSONPlugin struct{}
    37  
    38  // Name implements Plugin Interface.
    39  func (jsp *TaskJSONPlugin) Name() string {
    40  	return TaskJSONPluginName
    41  }
    42  
    43  // GetRoutes returns an API route for serving patch data.
    44  func (jsp *TaskJSONPlugin) GetAPIHandler() http.Handler {
    45  	r := mux.NewRouter()
    46  	r.HandleFunc("/tags/{task_name}/{name}", apiGetTagsForTask)
    47  	r.HandleFunc("/history/{task_name}/{name}", apiGetTaskHistory)
    48  
    49  	r.HandleFunc("/data/{name}", apiInsertTask)
    50  	r.HandleFunc("/data/{task_name}/{name}", apiGetTaskByName)
    51  	r.HandleFunc("/data/{task_name}/{name}/{variant}", apiGetTaskForVariant)
    52  	return r
    53  }
    54  
    55  func (hwp *TaskJSONPlugin) GetUIHandler() http.Handler {
    56  	r := mux.NewRouter()
    57  
    58  	// version routes
    59  	r.HandleFunc("/version", getVersion)
    60  	r.HandleFunc("/version/{version_id}/{name}/", uiGetTasksForVersion)
    61  	r.HandleFunc("/version/latest/{project_id}/{name}", uiGetTasksForLatestVersion)
    62  
    63  	// task routes
    64  	r.HandleFunc("/task/{task_id}/{name}/", uiGetTaskById)
    65  	r.HandleFunc("/task/{task_id}/{name}/tags", uiGetTags)
    66  	r.HandleFunc("/task/{task_id}/{name}/tag", uiHandleTaskTag).Methods("POST", "DELETE")
    67  
    68  	r.HandleFunc("/tag/{project_id}/{tag}/{variant}/{task_name}/{name}", uiGetTaskJSONByTag)
    69  	r.HandleFunc("/commit/{project_id}/{revision}/{variant}/{task_name}/{name}", uiGetCommit)
    70  	r.HandleFunc("/history/{task_id}/{name}", uiGetTaskHistory)
    71  	return r
    72  }
    73  
    74  func fixPatchInHistory(taskId string, base *task.Task, history []TaskJSON) ([]TaskJSON, error) {
    75  	var jsonForTask *TaskJSON
    76  	err := db.FindOneQ(collection, db.Query(bson.M{"task_id": taskId}), &jsonForTask)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	if base != nil {
    81  		jsonForTask.RevisionOrderNumber = base.RevisionOrderNumber
    82  	}
    83  	if jsonForTask == nil {
    84  		return history, nil
    85  	}
    86  
    87  	found := false
    88  	for i, item := range history {
    89  		if item.Revision == base.Revision {
    90  			history[i] = *jsonForTask
    91  			found = true
    92  		}
    93  	}
    94  	// if found is false, it means we don't have json on the base commit, so it was
    95  	// not replaced and we must add it explicitly
    96  	if !found {
    97  		history = append(history, *jsonForTask)
    98  	}
    99  	return history, nil
   100  }
   101  
   102  func (jsp *TaskJSONPlugin) Configure(map[string]interface{}) error {
   103  	return nil
   104  }
   105  
   106  // GetPanelConfig is required to fulfill the Plugin interface. This plugin
   107  // does not have any UI hooks.
   108  func (jsp *TaskJSONPlugin) GetPanelConfig() (*plugin.PanelConfig, error) {
   109  	return &plugin.PanelConfig{}, nil
   110  }
   111  
   112  // NewCommand returns requested commands by name. Fulfills the Plugin interface.
   113  func (jsp *TaskJSONPlugin) NewCommand(cmdName string) (plugin.Command, error) {
   114  	if cmdName == TaskJSONSend {
   115  		return &TaskJSONSendCommand{}, nil
   116  	} else if cmdName == TaskJSONGet {
   117  		return &TaskJSONGetCommand{}, nil
   118  	} else if cmdName == TaskJSONGetHistory {
   119  		return &TaskJSONHistoryCommand{}, nil
   120  	}
   121  	return nil, &plugin.ErrUnknownCommand{cmdName}
   122  }
   123  
   124  type TaskJSONSendCommand struct {
   125  	File     string `mapstructure:"file" plugin:"expand"`
   126  	DataName string `mapstructure:"name" plugin:"expand"`
   127  }
   128  
   129  func (tjsc *TaskJSONSendCommand) Name() string {
   130  	return "send"
   131  }
   132  
   133  func (tjsc *TaskJSONSendCommand) Plugin() string {
   134  	return "json"
   135  }
   136  
   137  func (tjsc *TaskJSONSendCommand) ParseParams(params map[string]interface{}) error {
   138  	if err := mapstructure.Decode(params, tjsc); err != nil {
   139  		return errors.Wrapf(err, "error decoding '%v' params", tjsc.Name())
   140  	}
   141  	return nil
   142  }
   143  
   144  func (tjsc *TaskJSONSendCommand) Execute(log plugin.Logger, com plugin.PluginCommunicator, conf *model.TaskConfig, stop chan bool) error {
   145  	if tjsc.File == "" {
   146  		return errors.New("'file' param must not be blank")
   147  	}
   148  	if tjsc.DataName == "" {
   149  		return errors.New("'name' param must not be blank")
   150  	}
   151  
   152  	errChan := make(chan error)
   153  	go func() {
   154  		// attempt to open the file
   155  		fileLoc := filepath.Join(conf.WorkDir, tjsc.File)
   156  		jsonFile, err := os.Open(fileLoc)
   157  		if err != nil {
   158  			errChan <- errors.Wrap(err, "Couldn't open json file")
   159  			return
   160  		}
   161  
   162  		jsonData := map[string]interface{}{}
   163  		err = util.ReadJSONInto(jsonFile, &jsonData)
   164  		if err != nil {
   165  			errChan <- errors.Wrap(err, "File contained invalid json")
   166  			return
   167  		}
   168  
   169  		retriablePost := util.RetriableFunc(
   170  			func() error {
   171  				log.LogTask(slogger.INFO, "Posting JSON")
   172  				resp, err := com.TaskPostJSON(fmt.Sprintf("data/%v", tjsc.DataName), jsonData)
   173  				if resp != nil {
   174  					defer resp.Body.Close()
   175  				}
   176  				err = errors.WithStack(err)
   177  				if err != nil {
   178  					return util.RetriableError{err}
   179  				}
   180  				if resp.StatusCode != http.StatusOK {
   181  					return util.RetriableError{errors.Errorf("unexpected status code %v", resp.StatusCode)}
   182  				}
   183  				return nil
   184  			},
   185  		)
   186  
   187  		_, err = util.Retry(retriablePost, 10, 3*time.Second)
   188  		errChan <- errors.WithStack(err)
   189  	}()
   190  
   191  	select {
   192  	case err := <-errChan:
   193  		if err != nil {
   194  			log.LogTask(slogger.ERROR, "Sending json data failed: %v", err)
   195  		}
   196  		return errors.WithStack(err)
   197  	case <-stop:
   198  		log.LogExecution(slogger.INFO, "Received abort signal, stopping.")
   199  		return nil
   200  	}
   201  }
   202  
   203  type TaskJSONGetCommand struct {
   204  	File     string `mapstructure:"file" plugin:"expand"`
   205  	DataName string `mapstructure:"name" plugin:"expand"`
   206  	TaskName string `mapstructure:"task" plugin:"expand"`
   207  	Variant  string `mapstructure:"variant" plugin:"expand"`
   208  }
   209  
   210  type TaskJSONHistoryCommand struct {
   211  	Tags     bool   `mapstructure:"tags"`
   212  	File     string `mapstructure:"file" plugin:"expand"`
   213  	DataName string `mapstructure:"name" plugin:"expand"`
   214  	TaskName string `mapstructure:"task" plugin:"expand"`
   215  }
   216  
   217  func (jgc *TaskJSONGetCommand) Name() string {
   218  	return TaskJSONGet
   219  }
   220  
   221  func (jgc *TaskJSONHistoryCommand) Name() string {
   222  	return TaskJSONHistory
   223  }
   224  
   225  func (jgc *TaskJSONGetCommand) Plugin() string {
   226  	return TaskJSONPluginName
   227  }
   228  
   229  func (jgc *TaskJSONHistoryCommand) Plugin() string {
   230  	return TaskJSONPluginName
   231  }
   232  
   233  func (jgc *TaskJSONGetCommand) ParseParams(params map[string]interface{}) error {
   234  	if err := mapstructure.Decode(params, jgc); err != nil {
   235  		return errors.Wrapf(err, "error decoding '%v' params", jgc.Name())
   236  	}
   237  	if jgc.File == "" {
   238  		return errors.New("JSON 'get' command must not have blank 'file' parameter")
   239  	}
   240  	return nil
   241  }
   242  
   243  func (jgc *TaskJSONHistoryCommand) ParseParams(params map[string]interface{}) error {
   244  	if err := mapstructure.Decode(params, jgc); err != nil {
   245  		return errors.Wrapf(err, "error decoding '%v' params", jgc.Name())
   246  	}
   247  	if jgc.File == "" {
   248  		return errors.New("JSON 'history' command must not have blank 'file' parameter")
   249  	}
   250  	return nil
   251  }
   252  
   253  func (jgc *TaskJSONGetCommand) Execute(log plugin.Logger, com plugin.PluginCommunicator, conf *model.TaskConfig, stop chan bool) error {
   254  	err := errors.WithStack(plugin.ExpandValues(jgc, conf.Expansions))
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	if jgc.File == "" {
   260  		return errors.New("'file' param must not be blank")
   261  	}
   262  	if jgc.DataName == "" {
   263  		return errors.New("'name' param must not be blank")
   264  	}
   265  	if jgc.TaskName == "" {
   266  		return errors.New("'task' param must not be blank")
   267  	}
   268  
   269  	if jgc.File != "" && !filepath.IsAbs(jgc.File) {
   270  		jgc.File = filepath.Join(conf.WorkDir, jgc.File)
   271  	}
   272  
   273  	retriableGet := util.RetriableFunc(
   274  		func() error {
   275  			dataUrl := fmt.Sprintf("data/%s/%s", jgc.TaskName, jgc.DataName)
   276  			if jgc.Variant != "" {
   277  				dataUrl = fmt.Sprintf("data/%s/%s/%s", jgc.TaskName, jgc.DataName, jgc.Variant)
   278  			}
   279  			resp, err := com.TaskGetJSON(dataUrl)
   280  			if resp != nil {
   281  				defer resp.Body.Close()
   282  			}
   283  			if err != nil {
   284  				//Some generic error trying to connect - try again
   285  				log.LogExecution(slogger.WARN, "Error connecting to API server: %v", err)
   286  				return util.RetriableError{err}
   287  			}
   288  
   289  			if resp.StatusCode == http.StatusOK {
   290  				jsonBytes, err := ioutil.ReadAll(resp.Body)
   291  				if err != nil {
   292  					return err
   293  				}
   294  				return ioutil.WriteFile(jgc.File, jsonBytes, 0755)
   295  			}
   296  			if resp.StatusCode != http.StatusOK {
   297  				if resp.StatusCode == http.StatusNotFound {
   298  					return errors.New("No JSON data found")
   299  				}
   300  				return util.RetriableError{errors.Errorf("unexpected status code %v", resp.StatusCode)}
   301  			}
   302  			return nil
   303  		},
   304  	)
   305  	_, err = util.Retry(retriableGet, 10, 3*time.Second)
   306  	return errors.WithStack(err)
   307  }
   308  
   309  func (jgc *TaskJSONHistoryCommand) Execute(log plugin.Logger, com plugin.PluginCommunicator, conf *model.TaskConfig, stop chan bool) error {
   310  	err := errors.WithStack(plugin.ExpandValues(jgc, conf.Expansions))
   311  	if err != nil {
   312  		return err
   313  	}
   314  
   315  	if jgc.File == "" {
   316  		return errors.New("'file' param must not be blank")
   317  	}
   318  	if jgc.DataName == "" {
   319  		return errors.New("'name' param must not be blank")
   320  	}
   321  	if jgc.TaskName == "" {
   322  		return errors.New("'task' param must not be blank")
   323  	}
   324  
   325  	if jgc.File != "" && !filepath.IsAbs(jgc.File) {
   326  		jgc.File = filepath.Join(conf.WorkDir, jgc.File)
   327  	}
   328  
   329  	endpoint := fmt.Sprintf("history/%s/%s", jgc.TaskName, jgc.DataName)
   330  	if jgc.Tags {
   331  		endpoint = fmt.Sprintf("tags/%s/%s", jgc.TaskName, jgc.DataName)
   332  	}
   333  
   334  	retriableGet := util.RetriableFunc(
   335  		func() error {
   336  			resp, err := com.TaskGetJSON(endpoint)
   337  			if resp != nil {
   338  				defer resp.Body.Close()
   339  			}
   340  			if err != nil {
   341  				//Some generic error trying to connect - try again
   342  				log.LogExecution(slogger.WARN, "Error connecting to API server: %v", err)
   343  				return util.RetriableError{err}
   344  			}
   345  
   346  			if resp.StatusCode == http.StatusOK {
   347  				jsonBytes, err := ioutil.ReadAll(resp.Body)
   348  				if err != nil {
   349  					return err
   350  				}
   351  				return ioutil.WriteFile(jgc.File, jsonBytes, 0755)
   352  			}
   353  			if resp.StatusCode != http.StatusOK {
   354  				if resp.StatusCode == http.StatusNotFound {
   355  					return errors.New("No JSON data found")
   356  				}
   357  				return util.RetriableError{errors.Errorf("unexpected status code %v", resp.StatusCode)}
   358  			}
   359  			return nil
   360  		},
   361  	)
   362  	_, err = util.Retry(retriableGet, 10, 3*time.Second)
   363  	return errors.WithStack(err)
   364  }