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