mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
Parse GitHub actions comments (#3776)
* add version comment parsing support to github actions Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update json schema with github actions metadata Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add originator processing for github actions type Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
f851085668
commit
12f36420dd
@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.0.24"
|
||||
JSONSchemaVersion = "16.0.25"
|
||||
)
|
||||
|
||||
2914
schema/json/schema-16.0.25.json
Normal file
2914
schema/json/schema-16.0.25.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.24/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.25/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@ -995,6 +995,20 @@
|
||||
"size"
|
||||
]
|
||||
},
|
||||
"GithubActionsUseStatement": {
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"comment": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"value"
|
||||
]
|
||||
},
|
||||
"GoModuleBuildinfoEntry": {
|
||||
"properties": {
|
||||
"goBuildSettings": {
|
||||
@ -1804,6 +1818,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/ErlangRebarLockEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/GithubActionsUseStatement"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/GoModuleBuildinfoEntry"
|
||||
},
|
||||
|
||||
@ -34,7 +34,7 @@ const (
|
||||
//
|
||||
// Available options are: <omit>, NOASSERTION, Person: <person>, Organization: <org>
|
||||
// return values are: <type>, <value>
|
||||
func Originator(p pkg.Package) (typ string, author string) { //nolint: funlen
|
||||
func Originator(p pkg.Package) (typ string, author string) { //nolint: gocyclo,funlen
|
||||
if !hasMetadata(p) {
|
||||
return typ, author
|
||||
}
|
||||
@ -57,6 +57,15 @@ func Originator(p pkg.Package) (typ string, author string) { //nolint: funlen
|
||||
case pkg.DpkgArchiveEntry:
|
||||
author = metadata.Maintainer
|
||||
|
||||
case pkg.GitHubActionsUseStatement:
|
||||
typ = orgType
|
||||
org := strings.Split(metadata.Value, "/")[0]
|
||||
if org == "actions" {
|
||||
// this is a GitHub action, so the org is GitHub
|
||||
org = "GitHub"
|
||||
}
|
||||
author = org
|
||||
|
||||
case pkg.JavaArchive:
|
||||
if metadata.Manifest != nil {
|
||||
author = metadata.Manifest.Main.MustGet("Specification-Vendor")
|
||||
|
||||
@ -45,6 +45,7 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
pkg.SwiplPackEntry{},
|
||||
pkg.OpamPackage{},
|
||||
pkg.YarnLockEntry{},
|
||||
pkg.TerraformLockProviderEntry{},
|
||||
)
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -384,20 +385,24 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
supplier: "Person: me (me@auth.com)",
|
||||
},
|
||||
{
|
||||
name: "from ocaml opam",
|
||||
name: "from github actions workflow/action",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.OpamPackage{},
|
||||
Metadata: pkg.GitHubActionsUseStatement{
|
||||
Value: "actions/checkout@v4",
|
||||
},
|
||||
originator: "",
|
||||
supplier: "",
|
||||
},
|
||||
originator: "Organization: GitHub",
|
||||
supplier: "Organization: GitHub",
|
||||
},
|
||||
{
|
||||
name: "from terraform lock",
|
||||
name: "from github actions workflow/action",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.TerraformLockProviderEntry{},
|
||||
Metadata: pkg.GitHubActionsUseStatement{
|
||||
Value: "google/something@v6",
|
||||
},
|
||||
originator: "",
|
||||
supplier: "",
|
||||
},
|
||||
originator: "Organization: google",
|
||||
supplier: "Organization: google",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
||||
@ -25,6 +25,7 @@ func AllTypes() []any {
|
||||
pkg.ELFBinaryPackageNoteJSONPayload{},
|
||||
pkg.ElixirMixLockEntry{},
|
||||
pkg.ErlangRebarLockEntry{},
|
||||
pkg.GitHubActionsUseStatement{},
|
||||
pkg.GolangBinaryBuildinfoEntry{},
|
||||
pkg.GolangModuleEntry{},
|
||||
pkg.HackageStackYamlEntry{},
|
||||
|
||||
@ -78,6 +78,7 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.DpkgDBEntry{}, "dpkg-db-entry", "DpkgMetadata"),
|
||||
jsonNames(pkg.ELFBinaryPackageNoteJSONPayload{}, "elf-binary-package-note-json-payload"),
|
||||
jsonNames(pkg.RubyGemspec{}, "ruby-gemspec", "GemMetadata"),
|
||||
jsonNames(pkg.GitHubActionsUseStatement{}, "github-actions-use-statement"),
|
||||
jsonNames(pkg.GolangBinaryBuildinfoEntry{}, "go-module-buildinfo-entry", "GolangBinMetadata", "GolangMetadata"),
|
||||
jsonNames(pkg.GolangModuleEntry{}, "go-module-entry", "GolangModMetadata"),
|
||||
jsonNames(pkg.HackageStackYamlLockEntry{}, "haskell-hackage-stack-lock-entry", "HackageMetadataType"),
|
||||
|
||||
@ -2,6 +2,7 @@ package githubactions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
@ -10,8 +11,8 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func newPackageFromUsageStatement(use string, location file.Location) (*pkg.Package, error) {
|
||||
name, version := parseStepUsageStatement(use)
|
||||
func newPackageFromUsageStatement(use, comment string, location file.Location) (*pkg.Package, error) {
|
||||
name, version := parseStepUsageStatement(use, comment)
|
||||
|
||||
if name == "" {
|
||||
log.WithFields("file", location.RealPath, "statement", use).Trace("unable to parse github action usage statement")
|
||||
@ -19,19 +20,20 @@ func newPackageFromUsageStatement(use string, location file.Location) (*pkg.Pack
|
||||
}
|
||||
|
||||
if strings.Contains(name, ".github/workflows/") {
|
||||
return newGithubActionWorkflowPackageUsage(name, version, location), nil
|
||||
return newGithubActionWorkflowPackageUsage(name, version, location, pkg.GitHubActionsUseStatement{Value: use, Comment: comment}), nil
|
||||
}
|
||||
|
||||
return newGithubActionPackageUsage(name, version, location), nil
|
||||
return newGithubActionPackageUsage(name, version, location, pkg.GitHubActionsUseStatement{Value: use, Comment: comment}), nil
|
||||
}
|
||||
|
||||
func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package {
|
||||
func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location, m pkg.GitHubActionsUseStatement) *pkg.Package {
|
||||
p := &pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: file.NewLocationSet(workflowLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(name, version),
|
||||
Type: pkg.GithubActionWorkflowPkg,
|
||||
Metadata: m,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
@ -39,13 +41,14 @@ func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation
|
||||
return p
|
||||
}
|
||||
|
||||
func newGithubActionPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package {
|
||||
func newGithubActionPackageUsage(name, version string, workflowLocation file.Location, m pkg.GitHubActionsUseStatement) *pkg.Package {
|
||||
p := &pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: file.NewLocationSet(workflowLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(name, version),
|
||||
Type: pkg.GithubActionPkg,
|
||||
Metadata: m,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
@ -53,20 +56,32 @@ func newGithubActionPackageUsage(name, version string, workflowLocation file.Loc
|
||||
return p
|
||||
}
|
||||
|
||||
func parseStepUsageStatement(use string) (string, string) {
|
||||
// from octo-org/another-repo/.github/workflows/workflow.yml@v1 get octo-org/another-repo/.github/workflows/workflow.yml and v1
|
||||
// from ./.github/workflows/workflow-2.yml interpret as only the name
|
||||
|
||||
// from actions/cache@v3 get actions/cache and v3
|
||||
func parseStepUsageStatement(use, comment string) (string, string) {
|
||||
// from "octo-org/another-repo/.github/workflows/workflow.yml@v1" get octo-org/another-repo/.github/workflows/workflow.yml and v1
|
||||
// from "./.github/workflows/workflow-2.yml" interpret as only the name
|
||||
// from "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2" get actions/checkout and v4.2.2
|
||||
// from "actions/cache@v3" get actions/cache and v3
|
||||
|
||||
fields := strings.Split(use, "@")
|
||||
switch len(fields) {
|
||||
case 1:
|
||||
return use, ""
|
||||
case 2:
|
||||
return fields[0], fields[1]
|
||||
name := use
|
||||
version := ""
|
||||
|
||||
if len(fields) == 2 {
|
||||
name = fields[0]
|
||||
version = fields[1]
|
||||
}
|
||||
return "", ""
|
||||
|
||||
// if version looks like a commit hash and we have a comment, try to extract version from comment
|
||||
if version != "" && regexp.MustCompile(`^[0-9a-f]{7,}$`).MatchString(version) && comment != "" {
|
||||
versionRegex := regexp.MustCompile(`v?\d+\.\d+\.\d+`)
|
||||
matches := versionRegex.FindStringSubmatch(comment)
|
||||
|
||||
if len(matches) >= 1 {
|
||||
return name, matches[0]
|
||||
}
|
||||
}
|
||||
|
||||
return name, version
|
||||
}
|
||||
|
||||
func packageURL(name, version string) string {
|
||||
|
||||
@ -43,7 +43,7 @@ func parseCompositeActionForActionUsage(_ context.Context, _ file.Resolver, _ *g
|
||||
continue
|
||||
}
|
||||
|
||||
p, err := newPackageFromUsageStatement(step.Uses, reader.Location)
|
||||
p, err := newPackageFromUsageStatement(step.Uses, step.UsesComment, reader.Location)
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, reader, err)
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ func Test_parseCompositeActionForActionUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "pkg:github/actions/setup-go@v4",
|
||||
Metadata: pkg.GitHubActionsUseStatement{Value: "actions/setup-go@v4"},
|
||||
},
|
||||
{
|
||||
Name: "actions/cache",
|
||||
@ -27,6 +28,7 @@ func Test_parseCompositeActionForActionUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "pkg:github/actions/cache@v3",
|
||||
Metadata: pkg.GitHubActionsUseStatement{Value: "actions/cache@v3"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
@ -25,12 +26,14 @@ type workflowDef struct {
|
||||
|
||||
type workflowJobDef struct {
|
||||
Uses string `yaml:"uses"`
|
||||
UsesComment string `yaml:"-"`
|
||||
Steps []stepDef `yaml:"steps"`
|
||||
}
|
||||
|
||||
type stepDef struct {
|
||||
Name string `yaml:"name"`
|
||||
Uses string `yaml:"uses"`
|
||||
UsesComment string `yaml:"-"`
|
||||
With struct {
|
||||
Path string `yaml:"path"`
|
||||
Key string `yaml:"key"`
|
||||
@ -43,17 +46,26 @@ func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generi
|
||||
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs)
|
||||
}
|
||||
|
||||
var wf workflowDef
|
||||
if errs = yaml.Unmarshal(contents, &wf); errs != nil {
|
||||
// parse the yaml file into a generic node to preserve comments
|
||||
var node yaml.Node
|
||||
if errs = yaml.Unmarshal(contents, &node); errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs)
|
||||
}
|
||||
|
||||
// unmarshal the node into a workflowDef struct
|
||||
var wf workflowDef
|
||||
if errs = node.Decode(&wf); errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to decode workflow: %w", errs)
|
||||
}
|
||||
|
||||
attachUsageComments(&node, &wf)
|
||||
|
||||
// we use a collection to help with deduplication before raising to higher level processing
|
||||
pkgs := pkg.NewCollection()
|
||||
|
||||
for _, job := range wf.Jobs {
|
||||
if job.Uses != "" {
|
||||
p, err := newPackageFromUsageStatement(job.Uses, reader.Location)
|
||||
p, err := newPackageFromUsageStatement(job.Uses, job.UsesComment, reader.Location)
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, reader, err)
|
||||
}
|
||||
@ -72,11 +84,20 @@ func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.
|
||||
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs)
|
||||
}
|
||||
|
||||
var wf workflowDef
|
||||
if errs = yaml.Unmarshal(contents, &wf); errs != nil {
|
||||
// parse the yaml file into a generic node to preserve comments
|
||||
var node yaml.Node
|
||||
if errs = yaml.Unmarshal(contents, &node); errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs)
|
||||
}
|
||||
|
||||
// unmarshal the node into a workflowDef struct
|
||||
var wf workflowDef
|
||||
if errs = node.Decode(&wf); errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to decode workflow: %w", errs)
|
||||
}
|
||||
|
||||
attachUsageComments(&node, &wf)
|
||||
|
||||
// we use a collection to help with deduplication before raising to higher level processing
|
||||
pkgs := pkg.NewCollection()
|
||||
|
||||
@ -85,7 +106,7 @@ func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.
|
||||
if step.Uses == "" {
|
||||
continue
|
||||
}
|
||||
p, err := newPackageFromUsageStatement(step.Uses, reader.Location)
|
||||
p, err := newPackageFromUsageStatement(step.Uses, step.UsesComment, reader.Location)
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, reader, err)
|
||||
}
|
||||
@ -97,3 +118,101 @@ func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.
|
||||
|
||||
return pkgs.Sorted(), nil, errs
|
||||
}
|
||||
|
||||
// attachUsageComments traverses the yaml node tree and attaches usage comments to the workflowDef job strcuts and step structs.
|
||||
// This is a best-effort approach to attach comments to the correct job or step.
|
||||
func attachUsageComments(node *yaml.Node, wf *workflowDef) {
|
||||
// for a document node, process its content (usually a single mapping node)
|
||||
if node.Kind == yaml.DocumentNode && len(node.Content) > 0 {
|
||||
processNode(node.Content[0], wf, nil, nil, nil)
|
||||
} else {
|
||||
processNode(node, wf, nil, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func processNode(node *yaml.Node, wf *workflowDef, currentJob *string, currentStep *int, inJobsSection *bool) {
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
key := node.Content[i]
|
||||
value := node.Content[i+1]
|
||||
|
||||
// track if we're in the jobs section...
|
||||
if key.Value == "jobs" && inJobsSection == nil {
|
||||
inJobs := true
|
||||
inJobsSection = &inJobs
|
||||
processNode(value, wf, nil, nil, inJobsSection)
|
||||
continue
|
||||
}
|
||||
|
||||
// if we're in jobs section, and this is a job key...
|
||||
if inJobsSection != nil && *inJobsSection && currentJob == nil {
|
||||
job := key.Value
|
||||
currentJob = &job
|
||||
processNode(value, wf, currentJob, nil, inJobsSection)
|
||||
currentJob = nil
|
||||
continue
|
||||
}
|
||||
|
||||
// if this is a "uses" key...
|
||||
if key.Value == "uses" {
|
||||
processUsesNode(value, wf, currentJob, currentStep)
|
||||
}
|
||||
|
||||
// if this is a "steps" key inside a job...
|
||||
if key.Value == "steps" && currentJob != nil {
|
||||
for j, stepNode := range value.Content {
|
||||
stepIndex := j
|
||||
processNode(stepNode, wf, currentJob, &stepIndex, inJobsSection)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
processNode(key, wf, currentJob, currentStep, inJobsSection)
|
||||
processNode(value, wf, currentJob, currentStep, inJobsSection)
|
||||
}
|
||||
|
||||
case yaml.SequenceNode:
|
||||
for i, item := range node.Content {
|
||||
idx := i
|
||||
processNode(item, wf, currentJob, &idx, inJobsSection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processUsesNode(node *yaml.Node, wf *workflowDef, currentJob *string, currentStep *int) {
|
||||
if node.Kind != yaml.ScalarNode {
|
||||
return
|
||||
}
|
||||
|
||||
comment := node.LineComment
|
||||
if comment == "" {
|
||||
comment = node.HeadComment
|
||||
}
|
||||
if comment == "" {
|
||||
comment = node.FootComment
|
||||
}
|
||||
|
||||
if comment != "" {
|
||||
versionRegex := regexp.MustCompile(`v?\d+(\.\d+)*`)
|
||||
versionMatch := versionRegex.FindString(comment)
|
||||
|
||||
if versionMatch != "" {
|
||||
if currentJob != nil && currentStep == nil {
|
||||
// this is a job level "uses"
|
||||
if job, ok := wf.Jobs[*currentJob]; ok {
|
||||
job.UsesComment = versionMatch
|
||||
wf.Jobs[*currentJob] = job
|
||||
}
|
||||
} else if currentJob != nil && currentStep != nil {
|
||||
// this is a step level "uses"
|
||||
if job, ok := wf.Jobs[*currentJob]; ok {
|
||||
if *currentStep < len(job.Steps) {
|
||||
job.Steps[*currentStep].UsesComment = versionMatch
|
||||
wf.Jobs[*currentJob] = job
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "", // don't have enough context without parsing the git origin, which still may not be accurate
|
||||
Metadata: pkg.GitHubActionsUseStatement{Value: "./.github/actions/bootstrap"},
|
||||
},
|
||||
{
|
||||
Name: "actions/cache",
|
||||
@ -27,6 +28,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "pkg:github/actions/cache@v3",
|
||||
Metadata: pkg.GitHubActionsUseStatement{Value: "actions/cache@v3"},
|
||||
},
|
||||
{
|
||||
Name: "actions/cache/restore",
|
||||
@ -34,6 +36,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "pkg:github/actions/cache@v3#restore",
|
||||
Metadata: pkg.GitHubActionsUseStatement{Value: "actions/cache/restore@v3"},
|
||||
},
|
||||
{
|
||||
Name: "actions/cache/save",
|
||||
@ -41,6 +44,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "pkg:github/actions/cache@v3#save",
|
||||
Metadata: pkg.GitHubActionsUseStatement{Value: "actions/cache/save@v3"},
|
||||
},
|
||||
{
|
||||
Name: "actions/checkout",
|
||||
@ -48,6 +52,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "pkg:github/actions/checkout@v4",
|
||||
Metadata: pkg.GitHubActionsUseStatement{Value: "actions/checkout@v4"},
|
||||
},
|
||||
}
|
||||
|
||||
@ -66,6 +71,9 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionWorkflowPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "pkg:github/octo-org/this-repo@172239021f7ba04fe7327647b213799853a9eb89#.github/workflows/workflow-1.yml",
|
||||
Metadata: pkg.GitHubActionsUseStatement{
|
||||
Value: "octo-org/this-repo/.github/workflows/workflow-1.yml@172239021f7ba04fe7327647b213799853a9eb89",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "./.github/workflows/workflow-2.yml",
|
||||
@ -73,6 +81,7 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionWorkflowPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "", // don't have enough context without parsing the git origin, which still may not be accurate
|
||||
Metadata: pkg.GitHubActionsUseStatement{Value: "./.github/workflows/workflow-2.yml"},
|
||||
},
|
||||
{
|
||||
Name: "octo-org/another-repo/.github/workflows/workflow.yml",
|
||||
@ -80,6 +89,7 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) {
|
||||
Type: pkg.GithubActionWorkflowPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "pkg:github/octo-org/another-repo@v1#.github/workflows/workflow.yml",
|
||||
Metadata: pkg.GitHubActionsUseStatement{Value: "octo-org/another-repo/.github/workflows/workflow.yml@v1"},
|
||||
},
|
||||
}
|
||||
|
||||
@ -87,6 +97,38 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) {
|
||||
pkgtest.TestFileParser(t, fixture, parseWorkflowForWorkflowUsage, expected, expectedRelationships)
|
||||
}
|
||||
|
||||
func Test_parseWorkflowForVersionComments(t *testing.T) {
|
||||
fixture := "test-fixtures/workflow-with-version-comments.yaml"
|
||||
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||
|
||||
expected := []pkg.Package{
|
||||
{
|
||||
Name: "./.github/actions/bootstrap",
|
||||
Version: "",
|
||||
Type: pkg.GithubActionPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "", // don't have enough context without parsing the git origin, which still may not be accurate
|
||||
Metadata: pkg.GitHubActionsUseStatement{
|
||||
Value: "./.github/actions/bootstrap",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "actions/checkout",
|
||||
Version: "v4.2.2",
|
||||
Type: pkg.GithubActionPkg,
|
||||
Locations: fixtureLocationSet,
|
||||
PURL: "pkg:github/actions/checkout@v4.2.2",
|
||||
Metadata: pkg.GitHubActionsUseStatement{
|
||||
Value: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683",
|
||||
Comment: "v4.2.2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var expectedRelationships []artifact.Relationship
|
||||
pkgtest.TestFileParser(t, fixture, parseWorkflowForActionUsage, expected, expectedRelationships)
|
||||
}
|
||||
|
||||
func Test_corruptActionWorkflow(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/corrupt/workflow-multi-job.yaml").
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
name: "Validations"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
||||
call-workflow-1-in-local-repo:
|
||||
uses: octo-org/this-repo/.github/workflows/workflow-1.yml@172239021f7ba04fe7327647b213799853a9eb89 #v1.0.0
|
||||
|
||||
Static-Analysis:
|
||||
name: "Static analysis"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Run static analysis
|
||||
run: make static-analysis
|
||||
6
syft/pkg/github.go
Normal file
6
syft/pkg/github.go
Normal file
@ -0,0 +1,6 @@
|
||||
package pkg
|
||||
|
||||
type GitHubActionsUseStatement struct {
|
||||
Value string `json:"value"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user