github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/githubactions/package.go (about)

     1  package githubactions
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/anchore/packageurl-go"
     7  	"github.com/anchore/syft/internal/log"
     8  	"github.com/anchore/syft/syft/file"
     9  	"github.com/anchore/syft/syft/pkg"
    10  )
    11  
    12  func newPackageFromUsageStatement(use string, location file.Location) *pkg.Package {
    13  	name, version := parseStepUsageStatement(use)
    14  
    15  	if name == "" {
    16  		log.WithFields("file", location.RealPath, "statement", use).Trace("unable to parse github action usage statement")
    17  		return nil
    18  	}
    19  
    20  	if strings.Contains(name, ".github/workflows/") {
    21  		return newGithubActionWorkflowPackageUsage(name, version, location)
    22  	}
    23  
    24  	return newGithubActionPackageUsage(name, version, location)
    25  }
    26  
    27  func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package {
    28  	p := &pkg.Package{
    29  		Name:      name,
    30  		Version:   version,
    31  		Locations: file.NewLocationSet(workflowLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    32  		PURL:      packageURL(name, version),
    33  		Type:      pkg.GithubActionWorkflowPkg,
    34  	}
    35  
    36  	p.SetID()
    37  
    38  	return p
    39  }
    40  
    41  func newGithubActionPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package {
    42  	p := &pkg.Package{
    43  		Name:      name,
    44  		Version:   version,
    45  		Locations: file.NewLocationSet(workflowLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    46  		PURL:      packageURL(name, version),
    47  		Type:      pkg.GithubActionPkg,
    48  	}
    49  
    50  	p.SetID()
    51  
    52  	return p
    53  }
    54  
    55  func parseStepUsageStatement(use string) (string, string) {
    56  	// from octo-org/another-repo/.github/workflows/workflow.yml@v1 get octo-org/another-repo/.github/workflows/workflow.yml and v1
    57  	// from ./.github/workflows/workflow-2.yml interpret as only the name
    58  
    59  	// from actions/cache@v3 get actions/cache and v3
    60  
    61  	fields := strings.Split(use, "@")
    62  	switch len(fields) {
    63  	case 1:
    64  		return use, ""
    65  	case 2:
    66  		return fields[0], fields[1]
    67  	}
    68  	return "", ""
    69  }
    70  
    71  func packageURL(name, version string) string {
    72  	var qualifiers packageurl.Qualifiers
    73  	var subPath string
    74  	var namespace string
    75  
    76  	fields := strings.SplitN(name, "/", 3)
    77  	switch len(fields) {
    78  	case 1:
    79  		return ""
    80  	case 2:
    81  		namespace = fields[0]
    82  		name = fields[1]
    83  	case 3:
    84  		namespace = fields[0]
    85  		name = fields[1]
    86  		subPath = fields[2]
    87  	}
    88  	if namespace == "." {
    89  		// this is a local composite action, which is unclear how to represent in a PURL without more information
    90  		return ""
    91  	}
    92  
    93  	// there isn't a github actions PURL but there is a github PURL type for referencing github repos, which is the
    94  	// next best thing until there is a supported type.
    95  	return packageurl.NewPackageURL(
    96  		packageurl.TypeGithub,
    97  		namespace,
    98  		name,
    99  		version,
   100  		qualifiers,
   101  		subPath,
   102  	).ToString()
   103  }