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  }