mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 15:06:36 +01:00
Add nested archive cataloging (#5260)
Signed-off-by: Kudryavcev Nikolay <kydry.nikolau@gmail.com> Signed-off-by: Thomas Bechtold <thomasbechtold@jpberlin.de> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Signed-off-by: Keith Zantow <kzantow@gmail.com> Co-authored-by: Kudryavcev Nikolay <kydry.nikolau@gmail.com> Co-authored-by: Thomas Bechtold <thomasbechtold@jpberlin.de> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
co-authored by
Kudryavcev Nikolay
Thomas Bechtold
Alex Goodman
parent
14a0beb93c
commit
4b8a3db5f3
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/capabilities/internal"
|
||||
@@ -31,6 +32,10 @@ type ConfigInfo struct {
|
||||
|
||||
var appConfigAnnotationPattern = regexp.MustCompile(`^//\s*app-config:\s*(.+)$`)
|
||||
|
||||
// embeddedFieldsAnnotationPattern matches "// capabilities-fields: A, B" on an embedded struct, which limits
|
||||
// the embedded fields listed to the ones named (for a shared type the cataloger reads only part of).
|
||||
var embeddedFieldsAnnotationPattern = regexp.MustCompile(`^//\s*capabilities-fields:\s*(.+)$`)
|
||||
|
||||
// DiscoverConfigs walks the cataloger directory and discovers all configuration structs
|
||||
// Returns map where key is "packageName.StructName" (e.g., "golang.CatalogerConfig")
|
||||
func DiscoverConfigs(repoRoot string) (map[string]ConfigInfo, error) {
|
||||
@@ -160,6 +165,9 @@ func extractCatalogerConfigFieldsRecursive(structType *ast.StructType, visitedTy
|
||||
if len(field.Names) == 0 {
|
||||
// this is an embedded field - resolve and extract its fields
|
||||
embeddedFields := resolveEmbeddedStructFields(field.Type, visitedTypes)
|
||||
if only := embeddedFieldAllowlist(field.Doc); only != nil {
|
||||
embeddedFields = slices.DeleteFunc(embeddedFields, func(f ConfigField) bool { return !only[f.Name] })
|
||||
}
|
||||
fields = append(fields, embeddedFields...)
|
||||
continue
|
||||
}
|
||||
@@ -195,6 +203,26 @@ func extractCatalogerConfigFieldsRecursive(structType *ast.StructType, visitedTy
|
||||
return fields
|
||||
}
|
||||
|
||||
// embeddedFieldAllowlist returns the field names from a "capabilities-fields:" annotation, or nil when there
|
||||
// is none (every exported embedded field is listed).
|
||||
func embeddedFieldAllowlist(commentGroup *ast.CommentGroup) map[string]bool {
|
||||
if commentGroup == nil {
|
||||
return nil
|
||||
}
|
||||
for _, comment := range commentGroup.List {
|
||||
matches := embeddedFieldsAnnotationPattern.FindStringSubmatch(comment.Text)
|
||||
if len(matches) < 2 {
|
||||
continue
|
||||
}
|
||||
only := make(map[string]bool)
|
||||
for _, name := range strings.Split(matches[1], ",") {
|
||||
only[strings.TrimSpace(name)] = true
|
||||
}
|
||||
return only
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveEmbeddedStructFields resolves an embedded struct type and extracts its fields recursively
|
||||
func resolveEmbeddedStructFields(fieldType ast.Expr, visitedTypes map[string]bool) []ConfigField {
|
||||
// extract the type name from the expression
|
||||
|
||||
@@ -457,3 +457,32 @@ func TestDiscoverAllowedConfigStructs(t *testing.T) {
|
||||
t.Logf(" - %s", configType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbeddedFieldAllowlist(t *testing.T) {
|
||||
comments := func(lines ...string) *ast.CommentGroup {
|
||||
g := &ast.CommentGroup{}
|
||||
for _, l := range lines {
|
||||
g.List = append(g.List, &ast.Comment{Text: l})
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
doc *ast.CommentGroup
|
||||
want map[string]bool
|
||||
}{
|
||||
{name: "no doc lists every field", doc: nil, want: nil},
|
||||
{name: "no annotation lists every field", doc: comments("// Deprecated: use something else."), want: nil},
|
||||
{
|
||||
name: "annotation limits to the named fields",
|
||||
doc: comments("// Deprecated: use something else.", "// capabilities-fields: IncludeIndexedArchives, IncludeUnindexedArchives"),
|
||||
want: map[string]bool{"IncludeIndexedArchives": true, "IncludeUnindexedArchives": true},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, embeddedFieldAllowlist(tt.doc))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user