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

     1  package fossa
     2  
     3  import (
     4  	"net/url"
     5  	"regexp"
     6  	"strings"
     7  
     8  	"github.com/apex/log"
     9  
    10  	"github.com/fossas/fossa-cli/config"
    11  	"github.com/fossas/fossa-cli/pkg"
    12  )
    13  
    14  // Locator serializes FOSSA API locators.
    15  type Locator struct {
    16  	Fetcher  string `json:"fetcher"`
    17  	Project  string `json:"package"`
    18  	Revision string `json:"revision"`
    19  }
    20  
    21  // String returns a locator converted to a string as a URL path for API access.
    22  func (l Locator) String() string {
    23  	if l.Fetcher == "git" {
    24  		return "git+" + NormalizeGitURL(l.Project) + "$" + l.Revision
    25  	}
    26  	if l.Fetcher == "archive" {
    27  		orgID, err := GetOrganizationID()
    28  		if err != nil {
    29  			log.Warnf("Could not get OrganizationID while constructing locator")
    30  		}
    31  		l.Project = orgID + "/" + l.Project
    32  	}
    33  	return l.Fetcher + "+" + l.Project + "$" + l.Revision
    34  }
    35  
    36  // OrgString returns a locator converted to a string as a URL path for API access.
    37  // The OrgID is included for custom fetchers.
    38  func (l Locator) OrgString() string {
    39  	if l.Fetcher == "git" {
    40  		return "git+" + NormalizeGitURL(l.Project) + "$" + l.Revision
    41  	}
    42  
    43  	if l.Fetcher == "archive" || l.Fetcher == "custom" {
    44  		orgID, err := GetOrganizationID()
    45  		if err != nil {
    46  			log.Warnf("Could not get OrganizationID while constructing locator")
    47  		}
    48  		l.Project = orgID + "/" + NormalizeGitURLTest(l.Project)
    49  	}
    50  
    51  	return l.Fetcher + "+" + l.Project + "$" + l.Revision
    52  }
    53  
    54  // URL calculates the FOSSA URL for a project's locator.
    55  func (l Locator) URL() string {
    56  	server, err := url.Parse(config.Endpoint())
    57  	if err != nil {
    58  		log.Fatalf("Invalid FOSSA endpoint: %s", err.Error())
    59  	}
    60  	branch := config.Branch()
    61  	if branch == "" {
    62  		branch = "master"
    63  	}
    64  	url, err := url.Parse(
    65  		"/projects/" +
    66  			url.PathEscape(l.Fetcher+"+"+l.Project) +
    67  			"/refs/branch/" +
    68  			url.PathEscape(branch) +
    69  			"/" +
    70  			url.PathEscape(l.Revision))
    71  	if err != nil {
    72  		log.Fatalf("Invalid FOSSA URL: %s", err.Error())
    73  	}
    74  	return server.ResolveReference(url).String()
    75  }
    76  
    77  // ReportURL provides a formatted URL.
    78  func (l Locator) ReportURL() string {
    79  	return `
    80  ============================================================
    81  
    82      View FOSSA Report:
    83      ` + l.URL() + `
    84  
    85  ============================================================
    86  `
    87  }
    88  
    89  // IsResolved returns true only if a locator is resolved.
    90  func (l Locator) IsResolved() bool {
    91  	return l.Revision != ""
    92  }
    93  
    94  // NormalizeGitURL normalizes all forms of git remote URLs to a single standard
    95  // form.
    96  func NormalizeGitURL(project string) string {
    97  	// Remove fetcher prefix (in case project is derived from splitting a locator on '$').
    98  	noFetcherPrefix := strings.TrimPrefix(project, "git+")
    99  
   100  	// Normalize Git URL format.
   101  	noGitExtension := strings.TrimSuffix(noFetcherPrefix, ".git")
   102  	handleGitHubSSH := strings.Replace(noGitExtension, "git@github.com:", "github.com/", 1)
   103  
   104  	// Remove protocols
   105  	noHTTPPrefix := strings.TrimPrefix(handleGitHubSSH, "http://")
   106  	noHTTPSPrefix := strings.TrimPrefix(noHTTPPrefix, "https://")
   107  
   108  	return noHTTPSPrefix
   109  }
   110  
   111  // NormalizeGitURL normalizes all forms of git remote URLs to a single standard
   112  // form. This works around the backend only normalizing strings starting with http.
   113  // HACK until the backend and cli are more in sync
   114  func NormalizeGitURLTest(project string) string {
   115  	// Remove fetcher prefix (in case project is derived from splitting a locator on '$').
   116  	noFetcherPrefix := strings.TrimPrefix(project, "git+")
   117  
   118  	// Normalize Git URL format only if not ssh
   119  	if strings.HasPrefix(noFetcherPrefix, "http") {
   120  		noFetcherPrefix = strings.TrimSuffix(noFetcherPrefix, ".git")
   121  	}
   122  
   123  	// Remove protocols
   124  	noHTTPPrefix := strings.TrimPrefix(noFetcherPrefix, "http://")
   125  	noHTTPSPrefix := strings.TrimPrefix(noHTTPPrefix, "https://")
   126  
   127  	return noHTTPSPrefix
   128  }
   129  
   130  // ReadLocator parses a string locator into a Locator.
   131  func ReadLocator(locator string) Locator {
   132  	locatorRegexp := regexp.MustCompile(`^(.*?)\+(.*?)\$(.*?)$`)
   133  	matches := locatorRegexp.FindStringSubmatch(locator)
   134  	return Locator{
   135  		Fetcher:  matches[1],
   136  		Project:  matches[2],
   137  		Revision: matches[3],
   138  	}
   139  }
   140  
   141  // LocatorOf returns the locator of a pkg.ID.
   142  func LocatorOf(id pkg.ID) Locator {
   143  	// TODO: maybe this should panic
   144  	if id.Type == pkg.Invalid {
   145  		log.Warnf("Unrecognized locator")
   146  		return Locator{}
   147  	}
   148  	// Normalize locator fetchers.
   149  	fetcher := id.Type.String()
   150  	switch id.Type {
   151  	case pkg.Composer:
   152  		fetcher = "comp"
   153  	case pkg.Gradle:
   154  		fetcher = "mvn"
   155  	case pkg.Ant:
   156  		fetcher = "mvn"
   157  	case pkg.Scala:
   158  		fetcher = "mvn"
   159  	case pkg.Haskell:
   160  		fetcher = "hackage"
   161  	}
   162  
   163  	return Locator{
   164  		Fetcher:  fetcher,
   165  		Project:  id.Name,
   166  		Revision: id.Revision,
   167  	}
   168  }