Projects STRLCPY syft Commits 74013d7d
🤬
  • ■ ■ ■ ■ ■ ■
    .github/scripts/json-schema-drift-check.sh
    1 1  #!/usr/bin/env bash
    2 2  set -u
    3 3   
    4  -if ! git diff-index --quiet HEAD --; then
    5  - git diff-index HEAD --
    6  - git --no-pager diff
    7  - echo "there are uncommitted changes, please commit them before running this check"
     4 +if [ "$(git status --porcelain | wc -l)" -ne "0" ]; then
     5 + echo " 🔴 there are uncommitted changes, please commit them before running this check"
    8 6   exit 1
    9 7  fi
    10  - 
    11  -success=true
    12 8   
    13 9  if ! make generate-json-schema; then
    14 10   echo "Generating json schema failed"
    15  - success=false
    16  -fi
    17  - 
    18  -if ! git diff-index --quiet HEAD --; then
    19  - git diff-index HEAD --
    20  - git --no-pager diff
    21  - echo "JSON schema drift detected!"
    22  - success=false
     11 + exit 1
    23 12  fi
    24 13   
    25  -if ! $success; then
     14 +if [ "$(git status --porcelain | wc -l)" -ne "0" ]; then
     15 + echo " 🔴 there are uncommitted changes, please commit them before running this check"
    26 16   exit 1
    27 17  fi
    28 18   
  • ■ ■ ■ ■
    Makefile
    skipped 301 lines
    302 302   
    303 303  .PHONY: generate-json-schema
    304 304  generate-json-schema: ## Generate a new json schema
    305  - cd schema/json && go run generate.go
     305 + cd schema/json && go generate . && go run .
    306 306   
    307 307  .PHONY: generate-license-list
    308 308  generate-license-list: ## Generate an updated spdx license list
    skipped 88 lines
  • ■ ■ ■ ■ ■
    go.mod
    skipped 53 lines
    54 54   github.com/Masterminds/sprig/v3 v3.2.3
    55 55   github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
    56 56   github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e
     57 + github.com/dave/jennifer v1.6.1
    57 58   github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
    58 59   github.com/docker/docker v24.0.1+incompatible
    59 60   github.com/github/go-spdx/v2 v2.1.2
    skipped 120 lines
  • ■ ■ ■ ■ ■ ■
    go.sum
    skipped 154 lines
    155 155  github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
    156 156  github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
    157 157  github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
     158 +github.com/dave/jennifer v1.6.1 h1:T4T/67t6RAA5AIV6+NP8Uk/BIsXgDoqEowgycdQQLuk=
     159 +github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
    158 160  github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
    159 161  github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
    160 162  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
    skipped 1032 lines
  • ■ ■ ■ ■ ■ ■
    schema/json/generate/main.go
     1 +package main
     2 + 
     3 +import (
     4 + "fmt"
     5 + "os"
     6 + 
     7 + "github.com/dave/jennifer/jen"
     8 + 
     9 + "github.com/anchore/syft/schema/json/internal"
     10 +)
     11 + 
     12 +// This program generates internal/generated.go.
     13 + 
     14 +const (
     15 + pkgImport = "github.com/anchore/syft/syft/pkg"
     16 + path = "internal/generated.go"
     17 +)
     18 + 
     19 +func main() {
     20 + typeNames, err := internal.AllSyftMetadataTypeNames()
     21 + if err != nil {
     22 + panic(fmt.Errorf("unable to get all metadata type names: %w", err))
     23 + }
     24 + 
     25 + fmt.Printf("updating metadata container object with %+v types\n", len(typeNames))
     26 + 
     27 + f := jen.NewFile("internal")
     28 + f.HeaderComment("DO NOT EDIT: generated by schema/json/generate/main.go")
     29 + f.ImportName(pkgImport, "pkg")
     30 + f.Comment("ArtifactMetadataContainer is a struct that contains all the metadata types for a package, as represented in the pkg.Package.Metadata field.")
     31 + f.Type().Id("ArtifactMetadataContainer").StructFunc(func(g *jen.Group) {
     32 + for _, typeName := range typeNames {
     33 + g.Id(typeName).Qual(pkgImport, typeName)
     34 + }
     35 + })
     36 + 
     37 + rendered := fmt.Sprintf("%#v", f)
     38 + 
     39 + fh, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
     40 + if err != nil {
     41 + panic(fmt.Errorf("unable to open file: %w", err))
     42 + }
     43 + _, err = fh.WriteString(rendered)
     44 + if err != nil {
     45 + panic(fmt.Errorf("unable to write file: %w", err))
     46 + }
     47 + if err := fh.Close(); err != nil {
     48 + panic(fmt.Errorf("unable to close file: %w", err))
     49 + }
     50 +}
     51 + 
  • ■ ■ ■ ■ ■ ■
    schema/json/internal/generated.go
     1 +// DO NOT EDIT: generated by schema/json/generate/main.go
     2 + 
     3 +package internal
     4 + 
     5 +import "github.com/anchore/syft/syft/pkg"
     6 + 
     7 +// ArtifactMetadataContainer is a struct that contains all the metadata types for a package, as represented in the pkg.Package.Metadata field.
     8 +type ArtifactMetadataContainer struct {
     9 + AlpmMetadata pkg.AlpmMetadata
     10 + ApkMetadata pkg.ApkMetadata
     11 + BinaryMetadata pkg.BinaryMetadata
     12 + CargoPackageMetadata pkg.CargoPackageMetadata
     13 + CocoapodsMetadata pkg.CocoapodsMetadata
     14 + ConanLockMetadata pkg.ConanLockMetadata
     15 + ConanMetadata pkg.ConanMetadata
     16 + DartPubMetadata pkg.DartPubMetadata
     17 + DotnetDepsMetadata pkg.DotnetDepsMetadata
     18 + DpkgMetadata pkg.DpkgMetadata
     19 + GemMetadata pkg.GemMetadata
     20 + GolangBinMetadata pkg.GolangBinMetadata
     21 + GolangModMetadata pkg.GolangModMetadata
     22 + HackageMetadata pkg.HackageMetadata
     23 + JavaMetadata pkg.JavaMetadata
     24 + KbPackageMetadata pkg.KbPackageMetadata
     25 + LinuxKernelMetadata pkg.LinuxKernelMetadata
     26 + LinuxKernelModuleMetadata pkg.LinuxKernelModuleMetadata
     27 + MixLockMetadata pkg.MixLockMetadata
     28 + NixStoreMetadata pkg.NixStoreMetadata
     29 + NpmPackageJSONMetadata pkg.NpmPackageJSONMetadata
     30 + NpmPackageLockJSONMetadata pkg.NpmPackageLockJSONMetadata
     31 + PhpComposerJSONMetadata pkg.PhpComposerJSONMetadata
     32 + PortageMetadata pkg.PortageMetadata
     33 + PythonPackageMetadata pkg.PythonPackageMetadata
     34 + PythonPipfileLockMetadata pkg.PythonPipfileLockMetadata
     35 + PythonRequirementsMetadata pkg.PythonRequirementsMetadata
     36 + RDescriptionFileMetadata pkg.RDescriptionFileMetadata
     37 + RebarLockMetadata pkg.RebarLockMetadata
     38 + RpmMetadata pkg.RpmMetadata
     39 +}
     40 + 
  • ■ ■ ■ ■ ■ ■
    schema/json/internal/metadata_types.go
     1 +package internal
     2 + 
     3 +import (
     4 + "fmt"
     5 + "go/ast"
     6 + "go/parser"
     7 + "go/token"
     8 + "os/exec"
     9 + "path/filepath"
     10 + "sort"
     11 + "strings"
     12 + "unicode"
     13 + 
     14 + "github.com/scylladb/go-set/strset"
     15 +)
     16 + 
     17 +var metadataExceptions = strset.New(
     18 + "FileMetadata",
     19 +)
     20 + 
     21 +func AllSyftMetadataTypeNames() ([]string, error) {
     22 + root, err := repoRoot()
     23 + if err != nil {
     24 + return nil, err
     25 + }
     26 + files, err := filepath.Glob(filepath.Join(root, "syft/pkg/*.go"))
     27 + if err != nil {
     28 + return nil, err
     29 + }
     30 + return findMetadataDefinitionNames(files...)
     31 +}
     32 + 
     33 +func repoRoot() (string, error) {
     34 + root, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
     35 + if err != nil {
     36 + return "", fmt.Errorf("unable to find repo root dir: %+v", err)
     37 + }
     38 + absRepoRoot, err := filepath.Abs(strings.TrimSpace(string(root)))
     39 + if err != nil {
     40 + return "", fmt.Errorf("unable to get abs path to repo root: %w", err)
     41 + }
     42 + return absRepoRoot, nil
     43 +}
     44 + 
     45 +func findMetadataDefinitionNames(paths ...string) ([]string, error) {
     46 + names := strset.New()
     47 + usedNames := strset.New()
     48 + for _, path := range paths {
     49 + metadataDefinitions, usedTypeNames, err := findMetadataDefinitionNamesInFile(path)
     50 + if err != nil {
     51 + return nil, err
     52 + }
     53 + 
     54 + // useful for debugging...
     55 + // fmt.Println(path)
     56 + // fmt.Println("Defs:", metadataDefinitions)
     57 + // fmt.Println("Used Types:", usedTypeNames)
     58 + // fmt.Println()
     59 + 
     60 + names.Add(metadataDefinitions...)
     61 + usedNames.Add(usedTypeNames...)
     62 + }
     63 + 
     64 + // any definition that is used within another struct should not be considered a top-level metadata definition
     65 + names.Remove(usedNames.List()...)
     66 + 
     67 + strNames := names.List()
     68 + sort.Strings(strNames)
     69 + 
     70 + // note: 30 is a point-in-time gut check. This number could be updated if new metadata definitions are added, but is not required.
     71 + // it is really intended to catch any major issues with the generation process that would generate, say, 0 definitions.
     72 + if len(strNames) < 30 {
     73 + return nil, fmt.Errorf("not enough metadata definitions found (discovered: " + fmt.Sprintf("%d", len(strNames)) + ")")
     74 + }
     75 + 
     76 + return strNames, nil
     77 +}
     78 + 
     79 +func findMetadataDefinitionNamesInFile(path string) ([]string, []string, error) {
     80 + // set up the parser
     81 + fs := token.NewFileSet()
     82 + f, err := parser.ParseFile(fs, path, nil, parser.ParseComments)
     83 + if err != nil {
     84 + return nil, nil, err
     85 + }
     86 + 
     87 + var metadataDefinitions []string
     88 + var usedTypeNames []string
     89 + for _, decl := range f.Decls {
     90 + // check if the declaration is a type declaration
     91 + spec, ok := decl.(*ast.GenDecl)
     92 + if !ok || spec.Tok != token.TYPE {
     93 + continue
     94 + }
     95 + 
     96 + // loop over all types declared in the type declaration
     97 + for _, typ := range spec.Specs {
     98 + // check if the type is a struct type
     99 + spec, ok := typ.(*ast.TypeSpec)
     100 + if !ok || spec.Type == nil {
     101 + continue
     102 + }
     103 + 
     104 + structType, ok := spec.Type.(*ast.StructType)
     105 + if !ok {
     106 + continue
     107 + }
     108 + 
     109 + // check if the struct type ends with "Metadata"
     110 + name := spec.Name.String()
     111 + 
     112 + // only look for exported types that end with "Metadata"
     113 + if isMetadataTypeCandidate(name) {
     114 + // print the full declaration of the struct type
     115 + metadataDefinitions = append(metadataDefinitions, name)
     116 + usedTypeNames = append(usedTypeNames, typeNamesUsedInStruct(structType)...)
     117 + }
     118 + }
     119 + }
     120 + return metadataDefinitions, usedTypeNames, nil
     121 +}
     122 + 
     123 +func typeNamesUsedInStruct(structType *ast.StructType) []string {
     124 + // recursively find all type names used in the struct type
     125 + var names []string
     126 + for i := range structType.Fields.List {
     127 + // capture names of all of the types (not field names)
     128 + ast.Inspect(structType.Fields.List[i].Type, func(n ast.Node) bool {
     129 + ident, ok := n.(*ast.Ident)
     130 + if !ok {
     131 + return true
     132 + }
     133 + 
     134 + // add the type name to the list
     135 + names = append(names, ident.Name)
     136 + 
     137 + // continue inspecting
     138 + return true
     139 + })
     140 + }
     141 + 
     142 + return names
     143 +}
     144 + 
     145 +func isMetadataTypeCandidate(name string) bool {
     146 + return len(name) > 0 &&
     147 + strings.HasSuffix(name, "Metadata") &&
     148 + unicode.IsUpper(rune(name[0])) && // must be exported
     149 + !metadataExceptions.Has(name)
     150 +}
     151 + 
  • ■ ■ ■ ■ ■
    schema/json/generate.go schema/json/main.go
    skipped 12 lines
    13 13   "github.com/invopop/jsonschema"
    14 14   
    15 15   "github.com/anchore/syft/internal"
     16 + genInt "github.com/anchore/syft/schema/json/internal"
    16 17   syftjsonModel "github.com/anchore/syft/syft/formats/syftjson/model"
    17  - "github.com/anchore/syft/syft/pkg"
    18 18  )
    19 19   
    20 20  /*
    skipped 3 lines
    24 24  can be extended to include specific package metadata struct shapes in the future.
    25 25  */
    26 26   
    27  -// This should represent all possible metadatas represented in the pkg.Package.Metadata field (an interface{}).
    28  -// When a new package metadata definition is created it will need to be manually added here. The variable name does
    29  -// not matter as long as it is exported.
     27 +//go:generate go run ./generate/main.go
    30 28   
    31  -// TODO: this should be generated from reflection of whats in the pkg package
    32  -// Should be created during generation below; use reflection's ability to
    33  -// create types at runtime.
    34  -// should be same name as struct minus metadata
    35  -type artifactMetadataContainer struct {
    36  - Alpm pkg.AlpmMetadata
    37  - Apk pkg.ApkMetadata
    38  - Binary pkg.BinaryMetadata
    39  - Cocopods pkg.CocoapodsMetadata
    40  - Conan pkg.ConanMetadata
    41  - ConanLock pkg.ConanLockMetadata
    42  - Dart pkg.DartPubMetadata
    43  - Dotnet pkg.DotnetDepsMetadata
    44  - Dpkg pkg.DpkgMetadata
    45  - Gem pkg.GemMetadata
    46  - GoBin pkg.GolangBinMetadata
    47  - GoMod pkg.GolangModMetadata
    48  - Hackage pkg.HackageMetadata
    49  - Java pkg.JavaMetadata
    50  - KbPackage pkg.KbPackageMetadata
    51  - LinuxKernel pkg.LinuxKernelMetadata
    52  - LinuxKernelModule pkg.LinuxKernelModuleMetadata
    53  - Nix pkg.NixStoreMetadata
    54  - NpmPackage pkg.NpmPackageJSONMetadata
    55  - NpmPackageLock pkg.NpmPackageLockJSONMetadata
    56  - MixLock pkg.MixLockMetadata
    57  - Php pkg.PhpComposerJSONMetadata
    58  - Portage pkg.PortageMetadata
    59  - PythonPackage pkg.PythonPackageMetadata
    60  - PythonPipfilelock pkg.PythonPipfileLockMetadata
    61  - PythonRequirements pkg.PythonRequirementsMetadata
    62  - RDescriptionFile pkg.RDescriptionFileMetadata
    63  - Rebar pkg.RebarLockMetadata
    64  - Rpm pkg.RpmMetadata
    65  - RustCargo pkg.CargoPackageMetadata
    66  -}
     29 +const schemaVersion = internal.JSONSchemaVersion
    67 30   
    68 31  func main() {
    69 32   write(encode(build()))
    skipped 7 lines
    77 40   },
    78 41   }
    79 42   documentSchema := reflector.ReflectFromType(reflect.TypeOf(&syftjsonModel.Document{}))
    80  - metadataSchema := reflector.ReflectFromType(reflect.TypeOf(&artifactMetadataContainer{}))
     43 + metadataSchema := reflector.ReflectFromType(reflect.TypeOf(&genInt.ArtifactMetadataContainer{}))
    81 44   // TODO: inject source definitions
    82 45   
    83 46   // inject the definitions of all metadatas into the schema definitions
    84 47   
    85 48   var metadataNames []string
    86 49   for name, definition := range metadataSchema.Definitions {
    87  - if name == "artifactMetadataContainer" {
     50 + if name == reflect.TypeOf(genInt.ArtifactMetadataContainer{}).Name() {
    88 51   // ignore the definition for the fake container
    89 52   continue
    90 53   }
    skipped 39 lines
    130 93  }
    131 94   
    132 95  func write(schema []byte) {
    133  - filename := fmt.Sprintf("schema-%s.json", internal.JSONSchemaVersion)
     96 + filename := fmt.Sprintf("schema-%s.json", schemaVersion)
    134 97   
    135 98   if _, err := os.Stat(filename); !os.IsNotExist(err) {
    136 99   // check if the schema is the same...
    skipped 30 lines
    167 130   
    168 131   defer fh.Close()
    169 132   
    170  - fmt.Printf("wrote new schema to %q\n", filename)
     133 + fmt.Printf("Wrote new schema to %q\n", filename)
    171 134  }
    172 135   
  • ■ ■ ■ ■ ■ ■
    schema/json/main_test.go
     1 +package main
     2 + 
     3 +import (
     4 + "reflect"
     5 + "sort"
     6 + "testing"
     7 + 
     8 + "github.com/google/go-cmp/cmp"
     9 + "github.com/stretchr/testify/assert"
     10 + "github.com/stretchr/testify/require"
     11 + 
     12 + "github.com/anchore/syft/schema/json/internal"
     13 +)
     14 + 
     15 +func TestAllMetadataRepresented(t *testing.T) {
     16 + // this test checks that all the metadata types are represented in the currently generated ArtifactMetadataContainer struct
     17 + // such that PRs will reflect when there is drift from the implemented set of metadata types and the generated struct
     18 + // which controls the JSON schema content.
     19 + expected, err := internal.AllSyftMetadataTypeNames()
     20 + require.NoError(t, err)
     21 + actual := allTypeNamesFromStruct(internal.ArtifactMetadataContainer{})
     22 + if !assert.ElementsMatch(t, expected, actual) {
     23 + t.Errorf("metadata types not fully represented: \n%s", cmp.Diff(expected, actual))
     24 + t.Log("did you add a new pkg.*Metadata type without updating the JSON schema?")
     25 + t.Log("if so, you need to update the schema version and regenerate the JSON schema (make generate-json-schema)")
     26 + }
     27 +}
     28 + 
     29 +func allTypeNamesFromStruct(instance any) []string {
     30 + // get all the type names from the struct (not recursively)
     31 + var typeNames []string
     32 + tt := reflect.TypeOf(instance)
     33 + for i := 0; i < tt.NumField(); i++ {
     34 + field := tt.Field(i)
     35 + typeNames = append(typeNames, field.Type.Name())
     36 + }
     37 + sort.Strings(typeNames)
     38 + return typeNames
     39 +}
     40 + 
Please wait...
Page is in error, reload to recover