github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/api/fossa/upload.go (about)

     1  package fossa
     2  
     3  import (
     4  	"encoding/json"
     5  	"net/url"
     6  
     7  	"github.com/pkg/errors"
     8  
     9  	"github.com/apex/log"
    10  	"github.com/fossas/fossa-cli/cmd/fossa/version"
    11  )
    12  
    13  // Errors related to preconditions.
    14  var (
    15  	ErrNoProject   = errors.New("no project provided for upload")
    16  	ErrNoRevision  = errors.New("no revision provided for upload")
    17  	ErrNoBuildData = errors.New("no build data to upload")
    18  )
    19  
    20  // Errors resulting from a bad API response.
    21  var (
    22  	ErrForbidden            = errors.New("authentication failed (is the API key correct?)")
    23  	ErrRevisionDoesNotExist = errors.New("revision does not exist (are the project and revision correct and published in FOSSA?)")
    24  )
    25  
    26  // UploadOptions are optional keys that provide extra metadata for an upload.
    27  type UploadOptions struct {
    28  	Branch         string
    29  	ProjectURL     string
    30  	JIRAProjectKey string
    31  	Link           string
    32  	Team           string
    33  }
    34  
    35  // Upload uploads a project's analysis.
    36  func Upload(title string, locator Locator, options UploadOptions, data []SourceUnit) (Locator, error) {
    37  	// Check preconditions.
    38  	if locator.Fetcher == "git" && locator.Revision == "" {
    39  		return Locator{}, errors.New("Could not infer revision name from `git` remote named `origin`. To submit a custom project, set Fetcher to `custom` in `.fossa.yml`")
    40  	}
    41  	if locator.Project == "" {
    42  		return Locator{}, errors.New("Could not infer project name from either `.fossa.yml` or `git` remote named `origin`")
    43  	}
    44  	if len(data) == 0 {
    45  		return Locator{}, errors.New("No data to upload")
    46  	}
    47  
    48  	payload, err := json.Marshal(data)
    49  	if err != nil {
    50  		return Locator{}, errors.Wrap(err, "could not marshal upload data")
    51  	}
    52  	log.WithFields(log.Fields{
    53  		"modules": data,
    54  		"payload": string(payload),
    55  	}).Debug("uploading build")
    56  
    57  	q := url.Values{}
    58  	q.Add("locator", locator.String())
    59  	q.Add("v", version.ShortString())
    60  	if locator.Fetcher == "custom" {
    61  		q.Add("managedBuild", "true")
    62  		q.Add("title", title)
    63  	}
    64  
    65  	if options.Branch != "" {
    66  		q.Add("branch", options.Branch)
    67  	}
    68  	if options.ProjectURL != "" {
    69  		q.Add("projectURL", options.ProjectURL)
    70  	}
    71  	if options.JIRAProjectKey != "" {
    72  		q.Add("jiraProjectKey", options.JIRAProjectKey)
    73  	}
    74  	if options.Link != "" {
    75  		q.Add("link", options.Link)
    76  	}
    77  	if options.Team != "" {
    78  		q.Add("team", options.Team)
    79  	}
    80  
    81  	endpoint, err := url.Parse("/api/builds/custom?" + q.Encode())
    82  	if err != nil {
    83  		return Locator{}, errors.New("Failed to generate upload URL")
    84  	}
    85  	log.WithField("endpoint", endpoint.String()).Debug("uploading build")
    86  
    87  	res, statusCode, err := Post(endpoint.String(), payload)
    88  	log.WithField("response", res).Debug("build upload completed")
    89  
    90  	if statusCode == 428 {
    91  		// TODO: handle "Managed Project" workflow
    92  		return Locator{}, errors.New("invalid project or revision; make sure this version is published and FOSSA has access to your repo (to submit a custom project, set Fetcher to `custom` in `.fossa.yml`)")
    93  	} else if statusCode == 403 {
    94  		return Locator{}, errors.New("you do not have permission to upload builds for this project")
    95  	} else if err != nil {
    96  		return Locator{}, errors.Wrap(err, "could not upload")
    97  	}
    98  	log.Debug("build upload succeeded")
    99  
   100  	var unmarshalled struct {
   101  		Locator string
   102  		Error   string
   103  	}
   104  	err = json.Unmarshal([]byte(res), &unmarshalled)
   105  	if err != nil || unmarshalled.Error != "" {
   106  		return Locator{}, errors.Errorf("Error response from API: %s", res)
   107  	}
   108  
   109  	if unmarshalled.Locator == "" {
   110  		return Locator{}, errors.Errorf("bad response: %s", res)
   111  	}
   112  
   113  	return ReadLocator(unmarshalled.Locator), nil
   114  }