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 }