github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/plugin/builtin/attach/attach_plugin.go (about) 1 package attach 2 3 import ( 4 "fmt" 5 "net/http" 6 "time" 7 8 "github.com/evergreen-ci/evergreen/model" 9 "github.com/evergreen-ci/evergreen/model/artifact" 10 "github.com/evergreen-ci/evergreen/model/task" 11 "github.com/evergreen-ci/evergreen/model/user" 12 "github.com/evergreen-ci/evergreen/plugin" 13 "github.com/evergreen-ci/evergreen/util" 14 "github.com/mongodb/grip" 15 "github.com/mongodb/grip/slogger" 16 "github.com/pkg/errors" 17 ) 18 19 func init() { 20 plugin.Publish(&AttachPlugin{}) 21 } 22 23 const ( 24 AttachPluginName = "attach" 25 AttachResultsCmd = "results" 26 AttachXunitResultsCmd = "xunit_results" 27 28 AttachResultsAPIEndpoint = "results" 29 AttachLogsAPIEndpoint = "test_logs" 30 31 AttachResultsPostRetries = 5 32 AttachResultsRetrySleepSec = 10 * time.Second 33 ) 34 35 // AttachPlugin has commands for uploading task results and links to files, 36 // for display and easy access in the UI. 37 type AttachPlugin struct{} 38 39 // Name returns the name of this plugin - it serves to satisfy 40 // the 'Plugin' interface 41 func (self *AttachPlugin) Name() string { 42 return AttachPluginName 43 } 44 45 func (self *AttachPlugin) GetAPIHandler() http.Handler { 46 r := http.NewServeMux() 47 r.HandleFunc(fmt.Sprintf("/%v", AttachResultsAPIEndpoint), AttachResultsHandler) 48 r.HandleFunc("/", http.NotFound) // 404 any request not routable to these endpoints 49 return r 50 } 51 52 func (self *AttachPlugin) GetUIHandler() http.Handler { 53 return nil 54 } 55 56 func (self *AttachPlugin) Configure(map[string]interface{}) error { 57 return nil 58 } 59 60 // stripHiddenFiles is a helper for only showing users the files they are allowed to see. 61 func stripHiddenFiles(files []artifact.File, pluginUser *user.DBUser) []artifact.File { 62 publicFiles := []artifact.File{} 63 for _, file := range files { 64 switch { 65 case file.Visibility == artifact.None: 66 continue 67 case file.Visibility == artifact.Private && pluginUser == nil: 68 continue 69 default: 70 publicFiles = append(publicFiles, file) 71 } 72 } 73 return publicFiles 74 } 75 76 // GetPanelConfig returns a plugin.PanelConfig struct representing panels 77 // that will be added to the Task and Build pages. 78 func (self *AttachPlugin) GetPanelConfig() (*plugin.PanelConfig, error) { 79 return &plugin.PanelConfig{ 80 Panels: []plugin.UIPanel{ 81 { 82 Page: plugin.TaskPage, 83 Position: plugin.PageLeft, 84 PanelHTML: "<div ng-include=\"'/plugin/attach/static/partials/task_files_panel.html'\" " + 85 "ng-init='files=plugins.attach' ng-show='plugins.attach.length'></div>", 86 DataFunc: func(context plugin.UIContext) (interface{}, error) { 87 if context.Task == nil { 88 return nil, nil 89 } 90 artifactEntry, err := artifact.FindOne(artifact.ByTaskId(context.Task.Id)) 91 if err != nil { 92 return nil, errors.Wrap(err, "error finding artifact files for task") 93 } 94 if artifactEntry == nil { 95 return nil, nil 96 } 97 return stripHiddenFiles(artifactEntry.Files, context.User), nil 98 }, 99 }, 100 { 101 Page: plugin.BuildPage, 102 Position: plugin.PageLeft, 103 PanelHTML: "<div ng-include=\"'/plugin/attach/static/partials/build_files_panel.html'\" " + 104 "ng-init='filesByTask=plugins.attach' ng-show='plugins.attach.length'></div>", 105 DataFunc: func(context plugin.UIContext) (interface{}, error) { 106 if context.Build == nil { 107 return nil, nil 108 } 109 taskArtifactFiles, err := artifact.FindAll(artifact.ByBuildId(context.Build.Id)) 110 if err != nil { 111 return nil, errors.Wrap(err, "error finding artifact files for build") 112 } 113 for i := range taskArtifactFiles { 114 // remove hidden files if the user isn't logged in 115 taskArtifactFiles[i].Files = stripHiddenFiles(taskArtifactFiles[i].Files, context.User) 116 } 117 return taskArtifactFiles, nil 118 }, 119 }, 120 }, 121 }, nil 122 } 123 124 // NewCommand returns the AttachPlugin - this is to satisfy the 125 // 'Plugin' interface 126 func (self *AttachPlugin) NewCommand(cmdName string) (plugin.Command, 127 error) { 128 switch cmdName { 129 case AttachResultsCmd: 130 return &AttachResultsCommand{}, nil 131 case AttachXunitResultsCmd: 132 return &AttachXUnitResultsCommand{}, nil 133 default: 134 return nil, errors.Errorf("No such %v command: %v", AttachPluginName, cmdName) 135 } 136 } 137 138 // SendJSONLogs is responsible for sending the specified logs 139 // to the API Server. If successful, it returns a log ID that can be used 140 // to refer to the log object in test results. 141 func SendJSONLogs(pluginLogger plugin.Logger, pluginCom plugin.PluginCommunicator, logs *model.TestLog) (string, error) { 142 pluginLogger.LogExecution(slogger.INFO, "Attaching test logs for %v", logs.Name) 143 logId, err := pluginCom.TaskPostTestLog(logs) 144 if err != nil { 145 return "", err 146 } 147 pluginLogger.LogTask(slogger.INFO, "Attach test logs succeeded") 148 return logId, nil 149 } 150 151 //XXX remove this once the transition is complete... 152 //AttachResultsHandler is an API hook for receiving and updating test results 153 func AttachResultsHandler(w http.ResponseWriter, r *http.Request) { 154 t := plugin.GetTask(r) 155 if t == nil { 156 message := "Cannot find task for attach results request" 157 grip.Error(message) 158 http.Error(w, message, http.StatusBadRequest) 159 return 160 } 161 results := &task.TestResults{} 162 err := util.ReadJSONInto(util.NewRequestReader(r), results) 163 if err != nil { 164 message := fmt.Sprintf("error reading test results: %v", err) 165 grip.Error(message) 166 http.Error(w, message, http.StatusBadRequest) 167 return 168 } 169 // set test result of task 170 if err := t.SetResults(results.Results); err != nil { 171 message := fmt.Sprintf("Error calling set results on task %v: %v", t.Id, err) 172 grip.Error(message) 173 http.Error(w, message, http.StatusInternalServerError) 174 return 175 } 176 plugin.WriteJSON(w, http.StatusOK, "Test results successfully attached") 177 }