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

     1  package fossa
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  
     7  	"github.com/apex/log"
     8  )
     9  
    10  // A License holds the FOSSA API response for the license API.
    11  type License struct {
    12  	ID             int64
    13  	LicenseID      string
    14  	RevisionID     string
    15  	LicenseGroupID int64
    16  	Ignored        bool
    17  	Title          string
    18  	URL            string
    19  	Copyright      string
    20  	Text           string
    21  	Attribution    string
    22  }
    23  
    24  // A Revision holds the FOSSA API response for the revision API.
    25  type Revision struct {
    26  	Locator  *Locator `json:"loc"`
    27  	Licenses []License
    28  	Project  *Project
    29  	Meta     []RevisionMeta
    30  	Issues   []Issue
    31  }
    32  
    33  // A RevisionMeta holds metadata about a FOSSA API revision.
    34  type RevisionMeta struct {
    35  	LastScan string `json:"last_scan"`
    36  }
    37  
    38  // A Project holds the FOSSA API response for the project API.
    39  type Project struct {
    40  	Title   string
    41  	URL     string
    42  	Public  bool
    43  	Authors []string
    44  }
    45  
    46  // RevisionsAPI is the API endpoint for revisions.
    47  const RevisionsAPI = "/api/revisions/%s"
    48  
    49  // RevisionsDependenciesAPI is the API endpoint to retrieve transitive dependencies of a revision.
    50  const RevisionsDependenciesAPI = "/api/revisions/%s/dependencies"
    51  
    52  // GetRevisionDependencies returns all transitive dependencies for a project revision.
    53  func GetRevisionDependencies(locator Locator, licenseText bool) ([]Revision, error) {
    54  	var revisions []Revision
    55  	licenseParams := url.Values{}
    56  	if licenseText {
    57  		licenseParams.Add("include_license_text", "true")
    58  		licenseParams.Add("generate_attribution", "true")
    59  	}
    60  
    61  	url := fmt.Sprintf(RevisionsDependenciesAPI, url.PathEscape(locator.OrgString())) + "?" + licenseParams.Encode()
    62  	_, err := GetJSON(url, &revisions)
    63  	return revisions, err
    64  }
    65  
    66  // GetRevision loads a single revision.
    67  func GetRevision(locator Locator) (Revision, error) {
    68  	var revision Revision
    69  	_, err := GetJSON(fmt.Sprintf(RevisionsAPI, url.PathEscape(locator.String())), &revision)
    70  	if err != nil {
    71  		return Revision{}, err
    72  	}
    73  
    74  	if revision.Locator == nil {
    75  		revision.Locator = &locator
    76  	}
    77  	if len(revision.Licenses) == 0 {
    78  		revision.Licenses = append(revision.Licenses, License{
    79  			LicenseID: "UNKNOWN",
    80  		})
    81  	}
    82  	if revision.Project == nil {
    83  		revision.Project = &Project{
    84  			Title: revision.Locator.Project,
    85  			URL:   "UNKNOWN",
    86  		}
    87  	}
    88  
    89  	return revision, err
    90  }
    91  
    92  // GetRevisions loads many revisions in batched requests.
    93  func GetRevisions(locators []Locator) (revs []Revision, err error) {
    94  	var locs []string
    95  	for _, loc := range locators {
    96  		locs = append(locs, loc.String())
    97  	}
    98  
    99  	// Split locators into chunks of 20 (this is an API limitation).
   100  	chunks := make([][]string, 0)
   101  	chunkSize := 20
   102  	for i := 0; i < len(locs); i += chunkSize {
   103  		end := i + chunkSize
   104  
   105  		if end > len(locs) {
   106  			end = len(locs)
   107  		}
   108  
   109  		chunks = append(chunks, locs[i:end])
   110  	}
   111  
   112  	// Make chunked API calls in parallel.
   113  	responses := make(chan []Revision, len(chunks))
   114  	for _, chunk := range chunks {
   115  		qs := url.Values{}
   116  		for _, q := range chunk {
   117  			qs.Add("locator", q)
   118  		}
   119  
   120  		go func(endpoint string) {
   121  			var revisions []Revision
   122  			_, err := GetJSON(endpoint, &revisions)
   123  			if err != nil {
   124  				log.Warnf("Failed to get some revisions: %s", err.Error())
   125  				responses <- []Revision{}
   126  			} else {
   127  				responses <- revisions
   128  			}
   129  		}(fmt.Sprintf(RevisionsAPI, "?"+qs.Encode()))
   130  	}
   131  
   132  	var revisions []Revision
   133  	for range chunks {
   134  		r := <-responses
   135  		revisions = append(revisions, r...)
   136  	}
   137  
   138  	return revisions, err
   139  }