diff --git a/internal/testing/mocks/backend.go b/internal/testing/mocks/backend.go index 5a1c74808b9..7b4b1ab15da 100644 --- a/internal/testing/mocks/backend.go +++ b/internal/testing/mocks/backend.go @@ -486,33 +486,33 @@ func (mr *MockBackendMockRecorder) IngestHasMetadata(ctx, subject, pkgMatchType, } // IngestHasSBOMs mocks base method. -func (m *MockBackend) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) ([]*model.HasSbom, error) { +func (m *MockBackend) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec, includes []*model.HasSBOMIncludesInputSpec) ([]*model.HasSbom, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IngestHasSBOMs", ctx, subjects, hasSBOMs) + ret := m.ctrl.Call(m, "IngestHasSBOMs", ctx, subjects, hasSBOMs, includes) ret0, _ := ret[0].([]*model.HasSbom) ret1, _ := ret[1].(error) return ret0, ret1 } // IngestHasSBOMs indicates an expected call of IngestHasSBOMs. -func (mr *MockBackendMockRecorder) IngestHasSBOMs(ctx, subjects, hasSBOMs interface{}) *gomock.Call { +func (mr *MockBackendMockRecorder) IngestHasSBOMs(ctx, subjects, hasSBOMs, includes interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IngestHasSBOMs", reflect.TypeOf((*MockBackend)(nil).IngestHasSBOMs), ctx, subjects, hasSBOMs) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IngestHasSBOMs", reflect.TypeOf((*MockBackend)(nil).IngestHasSBOMs), ctx, subjects, hasSBOMs, includes) } // IngestHasSbom mocks base method. -func (m *MockBackend) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec) (*model.HasSbom, error) { +func (m *MockBackend) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec, includes model.HasSBOMIncludesInputSpec) (*model.HasSbom, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IngestHasSbom", ctx, subject, hasSbom) + ret := m.ctrl.Call(m, "IngestHasSbom", ctx, subject, hasSbom, includes) ret0, _ := ret[0].(*model.HasSbom) ret1, _ := ret[1].(error) return ret0, ret1 } // IngestHasSbom indicates an expected call of IngestHasSbom. -func (mr *MockBackendMockRecorder) IngestHasSbom(ctx, subject, hasSbom interface{}) *gomock.Call { +func (mr *MockBackendMockRecorder) IngestHasSbom(ctx, subject, hasSbom, includes interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IngestHasSbom", reflect.TypeOf((*MockBackend)(nil).IngestHasSbom), ctx, subject, hasSbom) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IngestHasSbom", reflect.TypeOf((*MockBackend)(nil).IngestHasSbom), ctx, subject, hasSbom, includes) } // IngestHasSourceAt mocks base method. diff --git a/pkg/assembler/assembler.go b/pkg/assembler/assembler.go index cd742043b5b..9946b1a3b3c 100644 --- a/pkg/assembler/assembler.go +++ b/pkg/assembler/assembler.go @@ -147,7 +147,8 @@ type HasSBOMIngest struct { Pkg *generated.PkgInputSpec `json:"pkg,omitempty"` Artifact *generated.ArtifactInputSpec `json:"artifact,omitempty"` - HasSBOM *generated.HasSBOMInputSpec `json:"hasSbom,omitempty"` + HasSBOM *generated.HasSBOMInputSpec `json:"hasSbom,omitempty"` + Includes *generated.HasSBOMIncludesInputSpec `json:"includes,omitempty"` } type VexIngest struct { diff --git a/pkg/assembler/backends/arangodb/hasSBOM.go b/pkg/assembler/backends/arangodb/hasSBOM.go index c3448c439aa..c4f215ce1e7 100644 --- a/pkg/assembler/backends/arangodb/hasSBOM.go +++ b/pkg/assembler/backends/arangodb/hasSBOM.go @@ -200,7 +200,8 @@ func getHasSBOMQueryValues(pkg *model.PkgInputSpec, artifact *model.ArtifactInpu return values } -func (c *arangoClient) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) ([]*model.HasSbom, error) { +func (c *arangoClient) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec, includes []*model.HasSBOMIncludesInputSpec) ([]*model.HasSbom, error) { + // TODO(knrc) - handle includes if len(subjects.Packages) > 0 { var listOfValues []map[string]any @@ -381,7 +382,8 @@ func (c *arangoClient) IngestHasSBOMs(ctx context.Context, subjects model.Packag } } -func (c *arangoClient) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec) (*model.HasSbom, error) { +func (c *arangoClient) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec, includes model.HasSBOMIncludesInputSpec) (*model.HasSbom, error) { + // TODO(knrc) - handle includes if subject.Artifact != nil { query := `LET artifact = FIRST(FOR art IN artifacts FILTER art.algorithm == @art_algorithm FILTER art.digest == @art_digest RETURN art) diff --git a/pkg/assembler/backends/arangodb/hasSBOM_test.go b/pkg/assembler/backends/arangodb/hasSBOM_test.go index a0f7cbbfd5b..657919af243 100644 --- a/pkg/assembler/backends/arangodb/hasSBOM_test.go +++ b/pkg/assembler/backends/arangodb/hasSBOM_test.go @@ -42,6 +42,7 @@ func TestHasSBOM(t *testing.T) { type call struct { Sub model.PackageOrArtifactInput HS *model.HasSBOMInputSpec + Inc *model.HasSBOMIncludesInputSpec } tests := []struct { Name string @@ -539,7 +540,8 @@ func TestHasSBOM(t *testing.T) { } } for _, o := range test.Calls { - found, err := b.IngestHasSbom(ctx, o.Sub, *o.HS) + // TODO (knrc) handle includes + found, err := b.IngestHasSbom(ctx, o.Sub, *o.HS, model.HasSBOMIncludesInputSpec{}) if (err != nil) != test.ExpIngestErr { t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } @@ -602,6 +604,7 @@ func TestIngestHasSBOM(t *testing.T) { type call struct { Sub model.PackageOrArtifactInputs HS []*model.HasSBOMInputSpec + Inc []*model.HasSBOMIncludesInputSpec } tests := []struct { Name string @@ -797,7 +800,7 @@ func TestIngestHasSBOM(t *testing.T) { } } for _, o := range test.Calls { - _, err := b.IngestHasSBOMs(ctx, o.Sub, o.HS) + _, err := b.IngestHasSBOMs(ctx, o.Sub, o.HS, o.Inc) if (err != nil) != test.ExpIngestErr { t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } diff --git a/pkg/assembler/backends/backends.go b/pkg/assembler/backends/backends.go index c30aa31989b..a4b236d927a 100644 --- a/pkg/assembler/backends/backends.go +++ b/pkg/assembler/backends/backends.go @@ -77,8 +77,8 @@ type Backend interface { IngestCertifyLegals(ctx context.Context, subjects model.PackageOrSourceInputs, declaredLicensesList [][]*model.LicenseInputSpec, discoveredLicensesList [][]*model.LicenseInputSpec, certifyLegals []*model.CertifyLegalInputSpec) ([]*model.CertifyLegal, error) IngestDependency(ctx context.Context, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec) (*model.IsDependency, error) IngestDependencies(ctx context.Context, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependencies []*model.IsDependencyInputSpec) ([]*model.IsDependency, error) - IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec) (*model.HasSbom, error) - IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) ([]*model.HasSbom, error) + IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec, includes model.HasSBOMIncludesInputSpec) (*model.HasSbom, error) + IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec, includes []*model.HasSBOMIncludesInputSpec) ([]*model.HasSbom, error) IngestHasSourceAt(ctx context.Context, pkg model.PkgInputSpec, pkgMatchType model.MatchFlags, source model.SourceInputSpec, hasSourceAt model.HasSourceAtInputSpec) (*model.HasSourceAt, error) IngestHasSourceAts(ctx context.Context, pkgs []*model.PkgInputSpec, pkgMatchType *model.MatchFlags, sources []*model.SourceInputSpec, hasSourceAts []*model.HasSourceAtInputSpec) ([]string, error) IngestHasMetadata(ctx context.Context, subject model.PackageSourceOrArtifactInput, pkgMatchType *model.MatchFlags, hasMetadata model.HasMetadataInputSpec) (*model.HasMetadata, error) diff --git a/pkg/assembler/backends/ent/backend/sbom.go b/pkg/assembler/backends/ent/backend/sbom.go index aa9d6b892eb..ae61ed5bc0d 100644 --- a/pkg/assembler/backends/ent/backend/sbom.go +++ b/pkg/assembler/backends/ent/backend/sbom.go @@ -68,7 +68,8 @@ func (b *EntBackend) HasSBOM(ctx context.Context, spec *model.HasSBOMSpec) ([]*m return collect(records, toModelHasSBOM), nil } -func (b *EntBackend) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, spec model.HasSBOMInputSpec) (*model.HasSbom, error) { +func (b *EntBackend) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, spec model.HasSBOMInputSpec, includes model.HasSBOMIncludesInputSpec) (*model.HasSbom, error) { + // TODO(knrc) - handle includes funcName := "IngestHasSbom" sbomId, err := WithinTX(ctx, b.client, func(ctx context.Context) (*int, error) { @@ -154,7 +155,7 @@ func (b *EntBackend) IngestHasSbom(ctx context.Context, subject model.PackageOrA return toModelHasSBOM(sbom), nil } -func (b *EntBackend) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) ([]*model.HasSbom, error) { +func (b *EntBackend) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec, includes []*model.HasSBOMIncludesInputSpec) ([]*model.HasSbom, error) { var modelHasSboms []*model.HasSbom for i, hasSbom := range hasSBOMs { var subject model.PackageOrArtifactInput @@ -163,7 +164,8 @@ func (b *EntBackend) IngestHasSBOMs(ctx context.Context, subjects model.PackageO } else { subject = model.PackageOrArtifactInput{Package: subjects.Packages[i]} } - modelHasSbom, err := b.IngestHasSbom(ctx, subject, *hasSbom) + // TODO(knrc) - handle includes + modelHasSbom, err := b.IngestHasSbom(ctx, subject, *hasSbom, model.HasSBOMIncludesInputSpec{}) if err != nil { return nil, gqlerror.Errorf("IngestHasSBOMs failed with err: %v", err) } diff --git a/pkg/assembler/backends/ent/backend/sbom_test.go b/pkg/assembler/backends/ent/backend/sbom_test.go index ee28d7af91c..1d99cb61059 100644 --- a/pkg/assembler/backends/ent/backend/sbom_test.go +++ b/pkg/assembler/backends/ent/backend/sbom_test.go @@ -29,6 +29,7 @@ func (s *Suite) Test_HasSBOM() { type call struct { Sub model.PackageOrArtifactInput Spec *model.HasSBOMInputSpec + Inc *model.HasSBOMIncludesInputSpec } tests := []struct { Name string @@ -481,7 +482,8 @@ func (s *Suite) Test_HasSBOM() { recordIDs := make([]string, len(test.Calls)) for i, o := range test.Calls { - dep, err := b.IngestHasSbom(ctx, o.Sub, *o.Spec) + // TODO (knrc) handle includes + dep, err := b.IngestHasSbom(ctx, o.Sub, *o.Spec, model.HasSBOMIncludesInputSpec{}) if (err != nil) != test.ExpIngestErr { s.T().Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } @@ -521,6 +523,7 @@ func (s *Suite) TestIngestHasSBOMs() { type call struct { Sub model.PackageOrArtifactInputs HS []*model.HasSBOMInputSpec + Inc []*model.HasSBOMIncludesInputSpec } tests := []struct { Name string @@ -719,7 +722,7 @@ func (s *Suite) TestIngestHasSBOMs() { } } for _, o := range test.Calls { - _, err := b.IngestHasSBOMs(ctx, o.Sub, o.HS) + _, err := b.IngestHasSBOMs(ctx, o.Sub, o.HS, o.Inc) if (err != nil) != test.ExpIngestErr { t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } diff --git a/pkg/assembler/backends/inmem/backend.go b/pkg/assembler/backends/inmem/backend.go index 28a3b1a1d05..599736b088a 100644 --- a/pkg/assembler/backends/inmem/backend.go +++ b/pkg/assembler/backends/inmem/backend.go @@ -20,6 +20,8 @@ import ( "errors" "fmt" "math" + "slices" + "strconv" "strings" "sync" "sync/atomic" @@ -201,3 +203,37 @@ func unlock(m *sync.RWMutex, readOnly bool) { m.Unlock() } } + +func parseIDs(ids []string) ([]uint32, error) { + keys := make([]uint32, 0, len(ids)) + for _, id := range ids { + if key, err := parseID(id); err != nil { + return nil, err + } else { + keys = append(keys, key) + } + } + return keys, nil +} + +func parseID(id string) (uint32, error) { + id64, err := strconv.ParseUint(id, 10, 32) + return uint32(id64), err +} + +func sortAndRemoveDups(ids []uint32) []uint32 { + numIDs := len(ids) + if numIDs > 1 { + slices.Sort(ids) + nextIndex := 1 + for index := 1; index < numIDs; index++ { + currentVal := ids[index] + if ids[index-1] != currentVal { + ids[nextIndex] = currentVal + nextIndex++ + } + } + ids = ids[:nextIndex] + } + return ids +} diff --git a/pkg/assembler/backends/inmem/hasSBOM.go b/pkg/assembler/backends/inmem/hasSBOM.go index 0b0f7866319..d6448dc8241 100644 --- a/pkg/assembler/backends/inmem/hasSBOM.go +++ b/pkg/assembler/backends/inmem/hasSBOM.go @@ -22,34 +22,48 @@ import ( "time" "github.com/vektah/gqlparser/v2/gqlerror" + "golang.org/x/exp/slices" "github.com/guacsec/guac/pkg/assembler/graphql/model" ) type hasSBOMList []*hasSBOMStruct type hasSBOMStruct struct { - id uint32 - pkg uint32 - artifact uint32 - uri string - algorithm string - digest string - downloadLocation string - origin string - collector string - knownSince time.Time + id uint32 + pkg uint32 + artifact uint32 + uri string + algorithm string + digest string + downloadLocation string + origin string + collector string + knownSince time.Time + includedSoftware []uint32 + includedDependencies []uint32 + includedOccurrences []uint32 } func (n *hasSBOMStruct) ID() uint32 { return n.id } func (n *hasSBOMStruct) Neighbors(allowedEdges edgeMap) []uint32 { + out := []uint32{} if n.pkg != 0 && allowedEdges[model.EdgeHasSbomPackage] { - return []uint32{n.pkg} + out = append(out, n.pkg) } - if allowedEdges[model.EdgeHasSbomArtifact] { - return []uint32{n.artifact} + if n.artifact != 0 && allowedEdges[model.EdgeHasSbomArtifact] { + out = append(out, n.artifact) } - return []uint32{} + if allowedEdges[model.EdgeHasSbomIncludedSoftware] { + out = append(out, n.includedSoftware...) + } + if allowedEdges[model.EdgeHasSbomIncludedDependencies] { + out = append(out, n.includedDependencies...) + } + if allowedEdges[model.EdgeHasSbomIncludedOccurrences] { + out = append(out, n.includedOccurrences...) + } + return sortAndRemoveDups(out) } func (n *hasSBOMStruct) BuildModelNode(c *demoClient) (model.Node, error) { @@ -58,7 +72,7 @@ func (n *hasSBOMStruct) BuildModelNode(c *demoClient) (model.Node, error) { // Ingest HasSBOM -func (c *demoClient) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) ([]*model.HasSbom, error) { +func (c *demoClient) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec, includes []*model.HasSBOMIncludesInputSpec) ([]*model.HasSbom, error) { var modelHasSboms []*model.HasSbom for i := range hasSBOMs { @@ -66,13 +80,13 @@ func (c *demoClient) IngestHasSBOMs(ctx context.Context, subjects model.PackageO var err error if len(subjects.Packages) > 0 { subject := model.PackageOrArtifactInput{Package: subjects.Packages[i]} - hasSBOM, err = c.IngestHasSbom(ctx, subject, *hasSBOMs[i]) + hasSBOM, err = c.IngestHasSbom(ctx, subject, *hasSBOMs[i], *includes[i]) if err != nil { return nil, gqlerror.Errorf("IngestHasSbom failed with err: %v", err) } } else { subject := model.PackageOrArtifactInput{Artifact: subjects.Artifacts[i]} - hasSBOM, err = c.IngestHasSbom(ctx, subject, *hasSBOMs[i]) + hasSBOM, err = c.IngestHasSbom(ctx, subject, *hasSBOMs[i], *includes[i]) if err != nil { return nil, gqlerror.Errorf("IngestHasSbom failed with err: %v", err) } @@ -82,11 +96,57 @@ func (c *demoClient) IngestHasSBOMs(ctx context.Context, subjects model.PackageO return modelHasSboms, nil } -func (c *demoClient) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, input model.HasSBOMInputSpec) (*model.HasSbom, error) { - return c.ingestHasSbom(ctx, subject, input, true) +func (c *demoClient) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, input model.HasSBOMInputSpec, includes model.HasSBOMIncludesInputSpec) (*model.HasSbom, error) { + funcName := "IngestHasSbom" + + var softwareIDs []uint32 + var dependencyIDs []uint32 + var occurrenceIDs []uint32 + var err error + + if includes.Software != nil { + if softwareIDs, err = parseIDs(includes.Software); err != nil { + return nil, gqlerror.Errorf("%v :: %s", funcName, err) + } + for _, id := range softwareIDs { + node, ok := c.index[id] + if ok { + if _, ok = node.(*pkgVersionNode); !ok { + _, ok = node.(*artStruct) + } + } + if !ok { + return nil, gqlerror.Errorf("%v :: software id %d is neither an ingested Package nor an ingested Artifact", funcName, id) + } + } + softwareIDs = sortAndRemoveDups(softwareIDs) + } + if includes.Dependencies != nil { + if dependencyIDs, err = parseIDs(includes.Dependencies); err != nil { + return nil, gqlerror.Errorf("%v :: %s", funcName, err) + } + for _, id := range dependencyIDs { + if isDependency, err := byID[*isDependencyLink](id, c); isDependency == nil || err != nil { + return nil, gqlerror.Errorf("%v :: dependency id %d is not an ingested isDependency", funcName, id) + } + } + dependencyIDs = sortAndRemoveDups(dependencyIDs) + } + if includes.Occurrences != nil { + if occurrenceIDs, err = parseIDs(includes.Occurrences); err != nil { + return nil, gqlerror.Errorf("%v :: %s", funcName, err) + } + for _, id := range occurrenceIDs { + if isOccurrence, err := byID[*isOccurrenceStruct](id, c); isOccurrence == nil || err != nil { + return nil, gqlerror.Errorf("%v :: occurrence id %d is not an ingested isOccurrence", funcName, id) + } + } + occurrenceIDs = sortAndRemoveDups(occurrenceIDs) + } + return c.ingestHasSbom(ctx, subject, input, softwareIDs, dependencyIDs, occurrenceIDs, true) } -func (c *demoClient) ingestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, input model.HasSBOMInputSpec, readOnly bool) (*model.HasSbom, error) { +func (c *demoClient) ingestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, input model.HasSBOMInputSpec, includedSoftware, includedDependencies, includedOccurrences []uint32, readOnly bool) (*model.HasSbom, error) { funcName := "IngestHasSbom" lock(&c.m, readOnly) defer unlock(&c.m, readOnly) @@ -134,29 +194,35 @@ func (c *demoClient) ingestHasSbom(ctx context.Context, subject model.PackageOrA h.downloadLocation == input.DownloadLocation && h.origin == input.Origin && h.collector == input.Collector && - input.KnownSince.Equal(h.knownSince) { + input.KnownSince.Equal(h.knownSince) && + slices.Equal(h.includedSoftware, includedSoftware) && + slices.Equal(h.includedDependencies, includedDependencies) && + slices.Equal(h.includedOccurrences, includedOccurrences) { return c.convHasSBOM(h) } } if readOnly { c.m.RUnlock() - b, err := c.ingestHasSbom(ctx, subject, input, false) + b, err := c.ingestHasSbom(ctx, subject, input, includedSoftware, includedDependencies, includedOccurrences, false) c.m.RLock() // relock so that defer unlock does not panic return b, err } h := &hasSBOMStruct{ - id: c.getNextID(), - pkg: packageID, - artifact: artID, - uri: input.URI, - algorithm: algorithm, - digest: digest, - downloadLocation: input.DownloadLocation, - origin: input.Origin, - collector: input.Collector, - knownSince: input.KnownSince.UTC(), + id: c.getNextID(), + pkg: packageID, + artifact: artID, + uri: input.URI, + algorithm: algorithm, + digest: digest, + downloadLocation: input.DownloadLocation, + origin: input.Origin, + collector: input.Collector, + knownSince: input.KnownSince.UTC(), + includedSoftware: includedSoftware, + includedDependencies: includedDependencies, + includedOccurrences: includedOccurrences, } c.index[h.id] = h c.hasSBOMs = append(c.hasSBOMs, h) @@ -192,6 +258,29 @@ func (c *demoClient) convHasSBOM(in *hasSBOMStruct) (*model.HasSbom, error) { } out.Subject = c.convArtifact(art) } + // TODO(knrc) - do we want to return full objects for the following? + if len(in.includedSoftware) > 0 { + out.IncludedSoftware = make([]model.PackageOrArtifact, 0, len(in.includedSoftware)) + for _, id := range in.includedSoftware { + if _, err := byID[*pkgVersionNode](id, c); err != nil { + out.IncludedSoftware = append(out.IncludedSoftware, model.Artifact{ID: nodeID(id)}) + } else { + out.IncludedSoftware = append(out.IncludedSoftware, model.Package{ID: nodeID(id)}) + } + } + } + if len(in.includedDependencies) > 0 { + out.IncludedDependencies = make([]*model.IsDependency, 0, len(in.includedDependencies)) + for _, id := range in.includedDependencies { + out.IncludedDependencies = append(out.IncludedDependencies, &model.IsDependency{ID: nodeID(id)}) + } + } + if len(in.includedOccurrences) > 0 { + out.IncludedOccurrences = make([]*model.IsOccurrence, 0, len(in.includedOccurrences)) + for _, id := range in.includedOccurrences { + out.IncludedOccurrences = append(out.IncludedOccurrences, &model.IsOccurrence{ID: nodeID(id)}) + } + } return out, nil } @@ -273,6 +362,8 @@ func (c *demoClient) addHasSBOMIfMatch(out []*model.HasSbom, filter *model.HasSBOMSpec, link *hasSBOMStruct) ( []*model.HasSbom, error) { + // TODO(knrc) - Should we include the included software, dependencies and occurrences? + // Need a better definition of what is required for this. if filter != nil { if noMatch(filter.URI, link.uri) || noMatch(toLower(filter.Algorithm), link.algorithm) || diff --git a/pkg/assembler/backends/inmem/hasSBOM_test.go b/pkg/assembler/backends/inmem/hasSBOM_test.go index 3f889b455fc..30b18fc6f00 100644 --- a/pkg/assembler/backends/inmem/hasSBOM_test.go +++ b/pkg/assembler/backends/inmem/hasSBOM_test.go @@ -17,6 +17,8 @@ package inmem_test import ( "context" + "fmt" + "reflect" "slices" "strings" "testing" @@ -28,6 +30,78 @@ import ( "github.com/guacsec/guac/pkg/assembler/graphql/model" ) +type testDependency struct { + pkg *model.PkgInputSpec + depPkg *model.PkgInputSpec + matchType model.MatchFlags + isDep *model.IsDependencyInputSpec +} + +type testOccurrence struct { + Subj *model.PackageOrSourceInput + Art *model.ArtifactInputSpec + isOcc *model.IsOccurrenceInputSpec +} + +func getPackageVersionFromIngestedPackage(pkg *model.Package) (string, error) { + if pkg != nil { + if len(pkg.Namespaces) == 1 { + namespace := pkg.Namespaces[0] + if len(namespace.Names) == 1 { + name := namespace.Names[0] + if len(name.Versions) == 1 { + version := name.Versions[0] + return version.ID, nil + } + } + } + } + return "", fmt.Errorf("could not retrieve ingested PackageVersion from package id %v", pkg) +} + +func getNodeIds(nodes []model.Node) ([]string, error) { + var ids []string + for _, node := range nodes { + var id *string + switch typed := node.(type) { + case *model.Package: + switch len(typed.Namespaces) { + case 0: + id = &typed.ID + case 1: + namespace := typed.Namespaces[0] + switch len(namespace.Names) { + case 0: + id = &namespace.ID + case 1: + name := namespace.Names[0] + switch len(name.Versions) { + case 0: + id = &name.ID + case 1: + version := name.Versions[0] + id = &version.ID + } + } + } + case *model.Artifact: + id = &typed.ID + case *model.HasSbom: + id = &typed.ID + case *model.IsDependency: + id = &typed.ID + case *model.IsOccurrence: + id = &typed.ID + } + if id == nil { + return nil, fmt.Errorf("Could not idenitfy correct id for node: %v", reflect.TypeOf(node)) + } else { + ids = append(ids, *id) + } + } + return ids, nil +} + func TestHasSBOM(t *testing.T) { curTime := time.Now() timeAfterOneSecond := curTime.Add(time.Second) @@ -39,6 +113,9 @@ func TestHasSBOM(t *testing.T) { Name string InPkg []*model.PkgInputSpec InArt []*model.ArtifactInputSpec + PkgArt *model.PackageOrArtifactInputs + IsDeps []testDependency + IsOccs []testOccurrence Calls []call Query *model.HasSBOMSpec ExpHS []*model.HasSbom @@ -48,6 +125,9 @@ func TestHasSBOM(t *testing.T) { { Name: "HappyPath", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -63,14 +143,18 @@ func TestHasSBOM(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p1out, - URI: "test uri", + Subject: p1out, + URI: "test uri", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, { Name: "Ingest same twice", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -94,14 +178,18 @@ func TestHasSBOM(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p1out, - URI: "test uri", + Subject: p1out, + URI: "test uri", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, { Name: "Query on URI", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -125,8 +213,9 @@ func TestHasSBOM(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p1out, - URI: "test uri one", + Subject: p1out, + URI: "test uri one", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, @@ -165,6 +254,23 @@ func TestHasSBOM(t *testing.T) { Name: "Query on Package", InPkg: []*model.PkgInputSpec{p1, p2}, InArt: []*model.ArtifactInputSpec{a1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1, p2}, + Artifacts: []*model.ArtifactInputSpec{a1}, + }, + IsDeps: []testDependency{{ + pkg: p1, + depPkg: p2, + matchType: mAll, + isDep: &model.IsDependencyInputSpec{ + Justification: "test justification", + }, + }}, + IsOccs: []testOccurrence{{ + Subj: &model.PackageOrSourceInput{Package: p2}, + Art: a1, + isOcc: &model.IsOccurrenceInputSpec{Justification: "test justification"}, + }}, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -200,8 +306,11 @@ func TestHasSBOM(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p2out, - URI: "test uri", + Subject: p2out, + URI: "test uri", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}, model.Package{ID: "5"}, model.Artifact{ID: "6"}}, + IncludedDependencies: []*model.IsDependency{{ID: "7"}}, + IncludedOccurrences: []*model.IsOccurrence{{ID: "8"}}, }, }, }, @@ -209,6 +318,10 @@ func TestHasSBOM(t *testing.T) { Name: "Query on Artifact", InPkg: []*model.PkgInputSpec{p1}, InArt: []*model.ArtifactInputSpec{a1, a2}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + Artifacts: []*model.ArtifactInputSpec{a1, a2}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -244,14 +357,18 @@ func TestHasSBOM(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: a2out, - URI: "test uri", + Subject: a2out, + URI: "test uri", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}, model.Artifact{ID: "5"}, model.Artifact{ID: "6"}}, }, }, }, { Name: "Query on Algorithm", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -275,14 +392,32 @@ func TestHasSBOM(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p1out, - Algorithm: "qwerasdf", + Subject: p1out, + Algorithm: "qwerasdf", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, { Name: "Query on Digest", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1, p2}, + Artifacts: []*model.ArtifactInputSpec{a1}, + }, + IsDeps: []testDependency{{ + pkg: p1, + depPkg: p2, + matchType: mAll, + isDep: &model.IsDependencyInputSpec{ + Justification: "test justification", + }, + }}, + IsOccs: []testOccurrence{{ + Subj: &model.PackageOrSourceInput{Package: p2}, + Art: a1, + isOcc: &model.IsOccurrenceInputSpec{Justification: "test justification"}, + }}, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -306,14 +441,20 @@ func TestHasSBOM(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p1out, - Digest: "qwerasdf", + Subject: p1out, + Digest: "qwerasdf", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}, model.Package{ID: "5"}, model.Artifact{ID: "6"}}, + IncludedDependencies: []*model.IsDependency{&model.IsDependency{ID: "7"}}, + IncludedOccurrences: []*model.IsOccurrence{&model.IsOccurrence{ID: "8"}}, }, }, }, { Name: "Query on DownloadLocation", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -339,12 +480,16 @@ func TestHasSBOM(t *testing.T) { { Subject: p1out, DownloadLocation: "location two", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, { Name: "Query none", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -414,6 +559,9 @@ func TestHasSBOM(t *testing.T) { { Name: "Query on ID", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -439,6 +587,7 @@ func TestHasSBOM(t *testing.T) { { Subject: p1out, DownloadLocation: "location two", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, @@ -459,6 +608,9 @@ func TestHasSBOM(t *testing.T) { { Name: "Query bad ID", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -485,6 +637,9 @@ func TestHasSBOM(t *testing.T) { { Name: "Query without hasSBOMSpec", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -500,6 +655,7 @@ func TestHasSBOM(t *testing.T) { { Subject: p1out, DownloadLocation: "location one", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, @@ -524,8 +680,45 @@ func TestHasSBOM(t *testing.T) { t.Fatalf("Could not ingest artifact: %v", err) } } + includes := model.HasSBOMIncludesInputSpec{} + if test.PkgArt != nil { + if pkgs, err := b.IngestPackages(ctx, test.PkgArt.Packages); err != nil { + t.Fatalf("Could not ingest package: %v", err) + } else { + for _, pkg := range pkgs { + if id, err := getPackageVersionFromIngestedPackage(pkg); err != nil { + t.Fatalf("Could not determine PackageVersion id: %v", err) + } else { + includes.Software = append(includes.Software, id) + } + } + } + if arts, err := b.IngestArtifacts(ctx, test.PkgArt.Artifacts); err != nil { + t.Fatalf("Could not ingest artifact: %v", err) + } else { + for _, art := range arts { + includes.Software = append(includes.Software, art.ID) + } + } + } + + for _, dep := range test.IsDeps { + if isDep, err := b.IngestDependency(ctx, *dep.pkg, *dep.depPkg, dep.matchType, *dep.isDep); err != nil { + t.Fatalf("Could not ingest dependency: %v", err) + } else { + includes.Dependencies = append(includes.Dependencies, isDep.ID) + } + } + + for _, occ := range test.IsOccs { + if isOcc, err := b.IngestOccurrence(ctx, *occ.Subj, *occ.Art, *occ.isOcc); err != nil { + t.Fatalf("Could not ingest occurrence: %v", err) + } else { + includes.Occurrences = append(includes.Occurrences, isOcc.ID) + } + } for _, o := range test.Calls { - _, err := b.IngestHasSbom(ctx, o.Sub, *o.HS) + _, err := b.IngestHasSbom(ctx, o.Sub, *o.HS, includes) if (err != nil) != test.ExpIngestErr { t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } @@ -543,6 +736,17 @@ func TestHasSBOM(t *testing.T) { if diff := cmp.Diff(test.ExpHS, got, ignoreID); diff != "" { t.Errorf("Unexpected results. (-want +got):\n%s", diff) } + for index, gotHasSbom := range got { + if diff := cmp.Diff(test.ExpHS[index].IncludedSoftware, gotHasSbom.IncludedSoftware); diff != "" { + t.Errorf("Unexpected IncludedSoftware difference for ExpHS index %d. (-want +got):\n%s", index, diff) + } + if diff := cmp.Diff(test.ExpHS[index].IncludedDependencies, gotHasSbom.IncludedDependencies); diff != "" { + t.Errorf("Unexpected IncludedDependencies difference for ExpHS index %d. (-want +got):\n%s", index, diff) + } + if diff := cmp.Diff(test.ExpHS[index].IncludedOccurrences, gotHasSbom.IncludedOccurrences); diff != "" { + t.Errorf("Unexpected IncludedOccurrences difference for ExpHS index %d. (-want +got):\n%s", index, diff) + } + } }) } } @@ -556,6 +760,9 @@ func TestIngestHasSBOMs(t *testing.T) { Name string InPkg []*model.PkgInputSpec InArt []*model.ArtifactInputSpec + PkgArt *model.PackageOrArtifactInputs + IsDeps []testDependency + IsOccs []testOccurrence Calls []call Query *model.HasSBOMSpec ExpHS []*model.HasSbom @@ -565,6 +772,9 @@ func TestIngestHasSBOMs(t *testing.T) { { Name: "HappyPath", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInputs{ @@ -582,14 +792,18 @@ func TestIngestHasSBOMs(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p1out, - URI: "test uri", + Subject: p1out, + URI: "test uri", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, { Name: "Ingest same twice", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInputs{ @@ -610,14 +824,18 @@ func TestIngestHasSBOMs(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p1out, - URI: "test uri", + Subject: p1out, + URI: "test uri", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, { Name: "Query on URI", InPkg: []*model.PkgInputSpec{p1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInputs{ @@ -638,8 +856,9 @@ func TestIngestHasSBOMs(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p1out, - URI: "test uri one", + Subject: p1out, + URI: "test uri one", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}}, }, }, }, @@ -647,6 +866,23 @@ func TestIngestHasSBOMs(t *testing.T) { Name: "Query on Package", InPkg: []*model.PkgInputSpec{p1, p2}, InArt: []*model.ArtifactInputSpec{a1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1, p2}, + Artifacts: []*model.ArtifactInputSpec{a1}, + }, + IsDeps: []testDependency{{ + pkg: p1, + depPkg: p2, + matchType: mAll, + isDep: &model.IsDependencyInputSpec{ + Justification: "test justification", + }, + }}, + IsOccs: []testOccurrence{{ + Subj: &model.PackageOrSourceInput{Package: p2}, + Art: a1, + isOcc: &model.IsOccurrenceInputSpec{Justification: "test justification"}, + }}, Calls: []call{ { Sub: model.PackageOrArtifactInputs{ @@ -681,8 +917,11 @@ func TestIngestHasSBOMs(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: p2out, - URI: "test uri", + Subject: p2out, + URI: "test uri", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}, model.Package{ID: "5"}, model.Artifact{ID: "6"}}, + IncludedDependencies: []*model.IsDependency{{ID: "7"}}, + IncludedOccurrences: []*model.IsOccurrence{{ID: "8"}}, }, }, }, @@ -690,6 +929,15 @@ func TestIngestHasSBOMs(t *testing.T) { Name: "Query on Artifact", InPkg: []*model.PkgInputSpec{p1}, InArt: []*model.ArtifactInputSpec{a1, a2}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + Artifacts: []*model.ArtifactInputSpec{a1, a2}, + }, + IsOccs: []testOccurrence{{ + Subj: &model.PackageOrSourceInput{Package: p1}, + Art: a2, + isOcc: &model.IsOccurrenceInputSpec{Justification: "test justification"}, + }}, Calls: []call{ { Sub: model.PackageOrArtifactInputs{ @@ -724,8 +972,10 @@ func TestIngestHasSBOMs(t *testing.T) { }, ExpHS: []*model.HasSbom{ { - Subject: a2out, - URI: "test uri", + Subject: a2out, + URI: "test uri", + IncludedSoftware: []model.PackageOrArtifact{model.Package{ID: "4"}, model.Artifact{ID: "5"}, model.Artifact{ID: "6"}}, + IncludedOccurrences: []*model.IsOccurrence{{ID: "7"}}, }, }, }, @@ -750,8 +1000,49 @@ func TestIngestHasSBOMs(t *testing.T) { t.Fatalf("Could not ingest artifact: %v", err) } } + includes := model.HasSBOMIncludesInputSpec{} + if test.PkgArt != nil { + if pkgs, err := b.IngestPackages(ctx, test.PkgArt.Packages); err != nil { + t.Fatalf("Could not ingest package: %v", err) + } else { + for _, pkg := range pkgs { + if id, err := getPackageVersionFromIngestedPackage(pkg); err != nil { + t.Fatalf("Could not determine PackageVersion id: %v", err) + } else { + includes.Software = append(includes.Software, id) + } + } + } + if arts, err := b.IngestArtifacts(ctx, test.PkgArt.Artifacts); err != nil { + t.Fatalf("Could not ingest artifact: %v", err) + } else { + for _, art := range arts { + includes.Software = append(includes.Software, art.ID) + } + } + } + + for _, dep := range test.IsDeps { + if isDep, err := b.IngestDependency(ctx, *dep.pkg, *dep.depPkg, dep.matchType, *dep.isDep); err != nil { + t.Fatalf("Could not ingest dependency: %v", err) + } else { + includes.Dependencies = append(includes.Dependencies, isDep.ID) + } + } + + for _, occ := range test.IsOccs { + if isOcc, err := b.IngestOccurrence(ctx, *occ.Subj, *occ.Art, *occ.isOcc); err != nil { + t.Fatalf("Could not ingest occurrence: %v", err) + } else { + includes.Occurrences = append(includes.Occurrences, isOcc.ID) + } + } for _, o := range test.Calls { - _, err := b.IngestHasSBOMs(ctx, o.Sub, o.HS) + var sbomIncludes []*model.HasSBOMIncludesInputSpec + for count := 0; count < len(o.HS); count++ { + sbomIncludes = append(sbomIncludes, &includes) + } + _, err := b.IngestHasSBOMs(ctx, o.Sub, o.HS, sbomIncludes) if (err != nil) != test.ExpIngestErr { t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } @@ -769,6 +1060,17 @@ func TestIngestHasSBOMs(t *testing.T) { if diff := cmp.Diff(test.ExpHS, got, ignoreID); diff != "" { t.Errorf("Unexpected results. (-want +got):\n%s", diff) } + for index, gotHasSbom := range got { + if diff := cmp.Diff(test.ExpHS[index].IncludedSoftware, gotHasSbom.IncludedSoftware); diff != "" { + t.Errorf("Unexpected IncludedSoftware difference for ExpHS index %d. (-want +got):\n%s", index, diff) + } + if diff := cmp.Diff(test.ExpHS[index].IncludedDependencies, gotHasSbom.IncludedDependencies); diff != "" { + t.Errorf("Unexpected IncludedDependencies difference for ExpHS index %d. (-want +got):\n%s", index, diff) + } + if diff := cmp.Diff(test.ExpHS[index].IncludedOccurrences, gotHasSbom.IncludedOccurrences); diff != "" { + t.Errorf("Unexpected IncludedOccurrences difference for ExpHS index %d. (-want +got):\n%s", index, diff) + } + } }) } } @@ -778,35 +1080,78 @@ func TestHasSBOMNeighbors(t *testing.T) { Sub model.PackageOrArtifactInput HS *model.HasSBOMInputSpec } + tests := []struct { Name string InPkg []*model.PkgInputSpec InArt []*model.ArtifactInputSpec + PkgArt *model.PackageOrArtifactInputs + IsDeps []testDependency + IsOccs []testOccurrence Calls []call ExpNeighbors map[string][]string }{ { Name: "HappyPath", - InPkg: []*model.PkgInputSpec{p1}, + InPkg: []*model.PkgInputSpec{p2, p4}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p2, p4}, + Artifacts: []*model.ArtifactInputSpec{a1}, + }, + IsDeps: []testDependency{{ + pkg: p2, + depPkg: p4, + matchType: mAll, + isDep: &model.IsDependencyInputSpec{ + Justification: "test justification", + }, + }}, + IsOccs: []testOccurrence{{ + Subj: &model.PackageOrSourceInput{Package: p4}, + Art: a1, + isOcc: &model.IsOccurrenceInputSpec{Justification: "test justification"}, + }}, Calls: []call{ { Sub: model.PackageOrArtifactInput{ - Package: p1, + Package: p2, }, HS: &model.HasSBOMInputSpec{ URI: "test uri", }, }, }, + /* + * 1 - p2 Package + * 2 - p2 PackageNamespace + * 3 - p2 PackageName + * 4 - p2 PackageVersion + * 5 - p4 Package + * 6 - p4 PackageNamespace + * 7 - p4 PackageName + * 8 - p4 PackageVersion + * 9 - a1 Artifact + * 10 - IsDependency + * 11 - IsOccurrence + * 12 - HasSBOM + */ ExpNeighbors: map[string][]string{ - "4": {"1", "5"}, // pkg version - "5": {"1"}, // hasSBOM + "4": {"3", "10", "12"}, // p2 PackageVersion -> p2 PackageName, IsDependency, HasSBOM + "8": {"7", "11"}, // p4 PackageVersion -> P4 PackageName, IsOccurrence + "9": {"11"}, // a1 Artifact -> IsOccurrence + "10": {"4", "7"}, // IsDependency -> p2 PackageVersion, p4 PackageName + "11": {"8", "9"}, // IsOccurrence -> p4 PackageVersion, a1 Artifact + "12": {"4", "8", "9", "10", "11"}, // HasSBOM -> p2 PackageVersion, p4 PackageVersion, a1 Artifact, IsDependency, IsOccurrence }, }, { Name: "Pkg and Artifact", InPkg: []*model.PkgInputSpec{p1}, InArt: []*model.ArtifactInputSpec{a1}, + PkgArt: &model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{p1}, + Artifacts: []*model.ArtifactInputSpec{a1}, + }, Calls: []call{ { Sub: model.PackageOrArtifactInput{ @@ -825,11 +1170,20 @@ func TestHasSBOMNeighbors(t *testing.T) { }, }, }, + /* + * 1 - p1 Package + * 2 - p1 PackageNamespace + * 3 - p1 PackageName + * 4 - p1 PackageVersion + * 5 - a1 Artifact + * 6 - HasSBOM + * 7 - HasSBOM + */ ExpNeighbors: map[string][]string{ - "4": {"1", "6"}, // pkg version -> hs1 - "5": {"7"}, // artifact -> hs2 - "6": {"1"}, // hs1 -> pkg version - "7": {"5"}, // hs2 -> artifact + "4": {"3", "6"}, // p1 PackageVersion -> p1 PackageName, p1 HasSBOM + "5": {"7"}, // artifact -> a1 HasSBOM + "6": {"4", "5"}, // p1 HasSBOM -> p1 PackageVersion, a1 Artifact + "7": {"4", "5"}, // p2 HasSBOM -> p1 PackageVersion, a1 Artifact }, }, } @@ -850,8 +1204,47 @@ func TestHasSBOMNeighbors(t *testing.T) { t.Fatalf("Could not ingest artifact: %v", err) } } + + includes := model.HasSBOMIncludesInputSpec{} + if test.PkgArt != nil { + if pkgs, err := b.IngestPackages(ctx, test.PkgArt.Packages); err != nil { + t.Fatalf("Could not ingest package: %v", err) + } else { + for _, pkg := range pkgs { + if id, err := getPackageVersionFromIngestedPackage(pkg); err != nil { + t.Fatalf("Could not determine PackageVersion id: %v", err) + } else { + includes.Software = append(includes.Software, id) + } + } + } + if arts, err := b.IngestArtifacts(ctx, test.PkgArt.Artifacts); err != nil { + t.Fatalf("Could not ingest artifact: %v", err) + } else { + for _, art := range arts { + includes.Software = append(includes.Software, art.ID) + } + } + } + + for _, dep := range test.IsDeps { + if isDep, err := b.IngestDependency(ctx, *dep.pkg, *dep.depPkg, dep.matchType, *dep.isDep); err != nil { + t.Fatalf("Could not ingest dependency: %v", err) + } else { + includes.Dependencies = append(includes.Dependencies, isDep.ID) + } + } + + for _, occ := range test.IsOccs { + if isOcc, err := b.IngestOccurrence(ctx, *occ.Subj, *occ.Art, *occ.isOcc); err != nil { + t.Fatalf("Could not ingest occurrence: %v", err) + } else { + includes.Occurrences = append(includes.Occurrences, isOcc.ID) + } + } + for _, o := range test.Calls { - if _, err := b.IngestHasSbom(ctx, o.Sub, *o.HS); err != nil { + if _, err := b.IngestHasSbom(ctx, o.Sub, *o.HS, includes); err != nil { t.Fatalf("Could not ingest HasSBOM: %v", err) } } @@ -860,7 +1253,10 @@ func TestHasSBOMNeighbors(t *testing.T) { if err != nil { t.Fatalf("Could not query neighbors: %s", err) } - gotIDs := convNodes(got) + gotIDs, err := getNodeIds(got) + if err != nil { + t.Fatalf("Could not retrieve neighbor ids: %s", err) + } slices.Sort(r) slices.Sort(gotIDs) if diff := cmp.Diff(r, gotIDs); diff != "" { diff --git a/pkg/assembler/backends/neo4j/hasSBOM.go b/pkg/assembler/backends/neo4j/hasSBOM.go index 285a1257406..79624e86435 100644 --- a/pkg/assembler/backends/neo4j/hasSBOM.go +++ b/pkg/assembler/backends/neo4j/hasSBOM.go @@ -187,10 +187,10 @@ func generateModelHasSBOM(subject model.PackageOrArtifact, uri, origin, collecto return &hasSBOM } -func (c *neo4jClient) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec) (*model.HasSbom, error) { +func (c *neo4jClient) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec, includes model.HasSBOMIncludesInputSpec) (*model.HasSbom, error) { panic(fmt.Errorf("not implemented: IngestHasSbom - IngestHasSbom")) } -func (c *neo4jClient) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) ([]*model.HasSbom, error) { +func (c *neo4jClient) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec, includes []*model.HasSBOMIncludesInputSpec) ([]*model.HasSbom, error) { return []*model.HasSbom{}, fmt.Errorf("not implemented: IngestHasSBOMs") } diff --git a/pkg/assembler/clients/generated/operations.go b/pkg/assembler/clients/generated/operations.go index 042595f6770..d8ca96d8c85 100644 --- a/pkg/assembler/clients/generated/operations.go +++ b/pkg/assembler/clients/generated/operations.go @@ -3076,6 +3076,12 @@ type AllHasSBOMTree struct { Collector string `json:"collector"` // Timestamp for SBOM creation KnownSince time.Time `json:"knownSince"` + // Included packages and artifacts + IncludedSoftware []AllHasSBOMTreeIncludedSoftwarePackageOrArtifact `json:"-"` + // Included dependencies + IncludedDependencies []AllHasSBOMTreeIncludedDependenciesIsDependency `json:"includedDependencies"` + // Included occurrences + IncludedOccurrences []AllHasSBOMTreeIncludedOccurrencesIsOccurrence `json:"includedOccurrences"` } // GetId returns AllHasSBOMTree.Id, and is useful for accessing the field via an interface. @@ -3105,6 +3111,21 @@ func (v *AllHasSBOMTree) GetCollector() string { return v.Collector } // GetKnownSince returns AllHasSBOMTree.KnownSince, and is useful for accessing the field via an interface. func (v *AllHasSBOMTree) GetKnownSince() time.Time { return v.KnownSince } +// GetIncludedSoftware returns AllHasSBOMTree.IncludedSoftware, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTree) GetIncludedSoftware() []AllHasSBOMTreeIncludedSoftwarePackageOrArtifact { + return v.IncludedSoftware +} + +// GetIncludedDependencies returns AllHasSBOMTree.IncludedDependencies, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTree) GetIncludedDependencies() []AllHasSBOMTreeIncludedDependenciesIsDependency { + return v.IncludedDependencies +} + +// GetIncludedOccurrences returns AllHasSBOMTree.IncludedOccurrences, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTree) GetIncludedOccurrences() []AllHasSBOMTreeIncludedOccurrencesIsOccurrence { + return v.IncludedOccurrences +} + func (v *AllHasSBOMTree) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -3113,7 +3134,8 @@ func (v *AllHasSBOMTree) UnmarshalJSON(b []byte) error { var firstPass struct { *AllHasSBOMTree - Subject json.RawMessage `json:"subject"` + Subject json.RawMessage `json:"subject"` + IncludedSoftware []json.RawMessage `json:"includedSoftware"` graphql.NoUnmarshalJSON } firstPass.AllHasSBOMTree = v @@ -3135,61 +3157,584 @@ func (v *AllHasSBOMTree) UnmarshalJSON(b []byte) error { } } } + + { + dst := &v.IncludedSoftware + src := firstPass.IncludedSoftware + *dst = make( + []AllHasSBOMTreeIncludedSoftwarePackageOrArtifact, + len(src)) + for i, src := range src { + dst := &(*dst)[i] + if len(src) != 0 && string(src) != "null" { + err = __unmarshalAllHasSBOMTreeIncludedSoftwarePackageOrArtifact( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal AllHasSBOMTree.IncludedSoftware: %w", err) + } + } + } + } + return nil +} + +type __premarshalAllHasSBOMTree struct { + Id string `json:"id"` + + Subject json.RawMessage `json:"subject"` + + Uri string `json:"uri"` + + Algorithm string `json:"algorithm"` + + Digest string `json:"digest"` + + DownloadLocation string `json:"downloadLocation"` + + Origin string `json:"origin"` + + Collector string `json:"collector"` + + KnownSince time.Time `json:"knownSince"` + + IncludedSoftware []json.RawMessage `json:"includedSoftware"` + + IncludedDependencies []AllHasSBOMTreeIncludedDependenciesIsDependency `json:"includedDependencies"` + + IncludedOccurrences []AllHasSBOMTreeIncludedOccurrencesIsOccurrence `json:"includedOccurrences"` +} + +func (v *AllHasSBOMTree) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *AllHasSBOMTree) __premarshalJSON() (*__premarshalAllHasSBOMTree, error) { + var retval __premarshalAllHasSBOMTree + + retval.Id = v.Id + { + + dst := &retval.Subject + src := v.Subject + var err error + *dst, err = __marshalAllHasSBOMTreeSubjectPackageOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal AllHasSBOMTree.Subject: %w", err) + } + } + retval.Uri = v.Uri + retval.Algorithm = v.Algorithm + retval.Digest = v.Digest + retval.DownloadLocation = v.DownloadLocation + retval.Origin = v.Origin + retval.Collector = v.Collector + retval.KnownSince = v.KnownSince + { + + dst := &retval.IncludedSoftware + src := v.IncludedSoftware + *dst = make( + []json.RawMessage, + len(src)) + for i, src := range src { + dst := &(*dst)[i] + var err error + *dst, err = __marshalAllHasSBOMTreeIncludedSoftwarePackageOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal AllHasSBOMTree.IncludedSoftware: %w", err) + } + } + } + retval.IncludedDependencies = v.IncludedDependencies + retval.IncludedOccurrences = v.IncludedOccurrences + return &retval, nil +} + +// AllHasSBOMTreeIncludedDependenciesIsDependency includes the requested fields of the GraphQL type IsDependency. +// The GraphQL type's documentation follows. +// +// IsDependency is an attestation to record that a package depends on another. +type AllHasSBOMTreeIncludedDependenciesIsDependency struct { + AllIsDependencyTree `json:"-"` +} + +// GetId returns AllHasSBOMTreeIncludedDependenciesIsDependency.Id, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) GetId() string { + return v.AllIsDependencyTree.Id +} + +// GetJustification returns AllHasSBOMTreeIncludedDependenciesIsDependency.Justification, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) GetJustification() string { + return v.AllIsDependencyTree.Justification +} + +// GetPackage returns AllHasSBOMTreeIncludedDependenciesIsDependency.Package, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) GetPackage() AllIsDependencyTreePackage { + return v.AllIsDependencyTree.Package +} + +// GetDependencyPackage returns AllHasSBOMTreeIncludedDependenciesIsDependency.DependencyPackage, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) GetDependencyPackage() AllIsDependencyTreeDependencyPackage { + return v.AllIsDependencyTree.DependencyPackage +} + +// GetDependencyType returns AllHasSBOMTreeIncludedDependenciesIsDependency.DependencyType, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) GetDependencyType() DependencyType { + return v.AllIsDependencyTree.DependencyType +} + +// GetVersionRange returns AllHasSBOMTreeIncludedDependenciesIsDependency.VersionRange, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) GetVersionRange() string { + return v.AllIsDependencyTree.VersionRange +} + +// GetOrigin returns AllHasSBOMTreeIncludedDependenciesIsDependency.Origin, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) GetOrigin() string { + return v.AllIsDependencyTree.Origin +} + +// GetCollector returns AllHasSBOMTreeIncludedDependenciesIsDependency.Collector, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) GetCollector() string { + return v.AllIsDependencyTree.Collector +} + +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *AllHasSBOMTreeIncludedDependenciesIsDependency + graphql.NoUnmarshalJSON + } + firstPass.AllHasSBOMTreeIncludedDependenciesIsDependency = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.AllIsDependencyTree) + if err != nil { + return err + } + return nil +} + +type __premarshalAllHasSBOMTreeIncludedDependenciesIsDependency struct { + Id string `json:"id"` + + Justification string `json:"justification"` + + Package AllIsDependencyTreePackage `json:"package"` + + DependencyPackage AllIsDependencyTreeDependencyPackage `json:"dependencyPackage"` + + DependencyType DependencyType `json:"dependencyType"` + + VersionRange string `json:"versionRange"` + + Origin string `json:"origin"` + + Collector string `json:"collector"` +} + +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *AllHasSBOMTreeIncludedDependenciesIsDependency) __premarshalJSON() (*__premarshalAllHasSBOMTreeIncludedDependenciesIsDependency, error) { + var retval __premarshalAllHasSBOMTreeIncludedDependenciesIsDependency + + retval.Id = v.AllIsDependencyTree.Id + retval.Justification = v.AllIsDependencyTree.Justification + retval.Package = v.AllIsDependencyTree.Package + retval.DependencyPackage = v.AllIsDependencyTree.DependencyPackage + retval.DependencyType = v.AllIsDependencyTree.DependencyType + retval.VersionRange = v.AllIsDependencyTree.VersionRange + retval.Origin = v.AllIsDependencyTree.Origin + retval.Collector = v.AllIsDependencyTree.Collector + return &retval, nil +} + +// AllHasSBOMTreeIncludedOccurrencesIsOccurrence includes the requested fields of the GraphQL type IsOccurrence. +// The GraphQL type's documentation follows. +// +// IsOccurrence is an attestation to link an artifact to a package or source. +// +// Attestation must occur at the PackageVersion or at the SourceName. +type AllHasSBOMTreeIncludedOccurrencesIsOccurrence struct { + AllIsOccurrencesTree `json:"-"` +} + +// GetId returns AllHasSBOMTreeIncludedOccurrencesIsOccurrence.Id, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedOccurrencesIsOccurrence) GetId() string { + return v.AllIsOccurrencesTree.Id +} + +// GetSubject returns AllHasSBOMTreeIncludedOccurrencesIsOccurrence.Subject, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedOccurrencesIsOccurrence) GetSubject() AllIsOccurrencesTreeSubjectPackageOrSource { + return v.AllIsOccurrencesTree.Subject +} + +// GetArtifact returns AllHasSBOMTreeIncludedOccurrencesIsOccurrence.Artifact, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedOccurrencesIsOccurrence) GetArtifact() AllIsOccurrencesTreeArtifact { + return v.AllIsOccurrencesTree.Artifact +} + +// GetJustification returns AllHasSBOMTreeIncludedOccurrencesIsOccurrence.Justification, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedOccurrencesIsOccurrence) GetJustification() string { + return v.AllIsOccurrencesTree.Justification +} + +// GetOrigin returns AllHasSBOMTreeIncludedOccurrencesIsOccurrence.Origin, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedOccurrencesIsOccurrence) GetOrigin() string { + return v.AllIsOccurrencesTree.Origin +} + +// GetCollector returns AllHasSBOMTreeIncludedOccurrencesIsOccurrence.Collector, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedOccurrencesIsOccurrence) GetCollector() string { + return v.AllIsOccurrencesTree.Collector +} + +func (v *AllHasSBOMTreeIncludedOccurrencesIsOccurrence) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *AllHasSBOMTreeIncludedOccurrencesIsOccurrence + graphql.NoUnmarshalJSON + } + firstPass.AllHasSBOMTreeIncludedOccurrencesIsOccurrence = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.AllIsOccurrencesTree) + if err != nil { + return err + } + return nil +} + +type __premarshalAllHasSBOMTreeIncludedOccurrencesIsOccurrence struct { + Id string `json:"id"` + + Subject json.RawMessage `json:"subject"` + + Artifact AllIsOccurrencesTreeArtifact `json:"artifact"` + + Justification string `json:"justification"` + + Origin string `json:"origin"` + + Collector string `json:"collector"` +} + +func (v *AllHasSBOMTreeIncludedOccurrencesIsOccurrence) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *AllHasSBOMTreeIncludedOccurrencesIsOccurrence) __premarshalJSON() (*__premarshalAllHasSBOMTreeIncludedOccurrencesIsOccurrence, error) { + var retval __premarshalAllHasSBOMTreeIncludedOccurrencesIsOccurrence + + retval.Id = v.AllIsOccurrencesTree.Id + { + + dst := &retval.Subject + src := v.AllIsOccurrencesTree.Subject + var err error + *dst, err = __marshalAllIsOccurrencesTreeSubjectPackageOrSource( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal AllHasSBOMTreeIncludedOccurrencesIsOccurrence.AllIsOccurrencesTree.Subject: %w", err) + } + } + retval.Artifact = v.AllIsOccurrencesTree.Artifact + retval.Justification = v.AllIsOccurrencesTree.Justification + retval.Origin = v.AllIsOccurrencesTree.Origin + retval.Collector = v.AllIsOccurrencesTree.Collector + return &retval, nil +} + +// AllHasSBOMTreeIncludedSoftwareArtifact includes the requested fields of the GraphQL type Artifact. +// The GraphQL type's documentation follows. +// +// Artifact represents an artifact identified by a checksum hash. +// +// The checksum is split into the digest value and the algorithm used to generate +// it. Both fields are mandatory and canonicalized to be lowercase. +// +// If having a checksum Go object, algorithm can be +// strings.ToLower(string(checksum.Algorithm)) and digest can be checksum.Value. +type AllHasSBOMTreeIncludedSoftwareArtifact struct { + Typename *string `json:"__typename"` + AllArtifactTree `json:"-"` +} + +// GetTypename returns AllHasSBOMTreeIncludedSoftwareArtifact.Typename, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedSoftwareArtifact) GetTypename() *string { return v.Typename } + +// GetId returns AllHasSBOMTreeIncludedSoftwareArtifact.Id, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedSoftwareArtifact) GetId() string { return v.AllArtifactTree.Id } + +// GetAlgorithm returns AllHasSBOMTreeIncludedSoftwareArtifact.Algorithm, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedSoftwareArtifact) GetAlgorithm() string { + return v.AllArtifactTree.Algorithm +} + +// GetDigest returns AllHasSBOMTreeIncludedSoftwareArtifact.Digest, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedSoftwareArtifact) GetDigest() string { return v.AllArtifactTree.Digest } + +func (v *AllHasSBOMTreeIncludedSoftwareArtifact) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *AllHasSBOMTreeIncludedSoftwareArtifact + graphql.NoUnmarshalJSON + } + firstPass.AllHasSBOMTreeIncludedSoftwareArtifact = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.AllArtifactTree) + if err != nil { + return err + } + return nil +} + +type __premarshalAllHasSBOMTreeIncludedSoftwareArtifact struct { + Typename *string `json:"__typename"` + + Id string `json:"id"` + + Algorithm string `json:"algorithm"` + + Digest string `json:"digest"` +} + +func (v *AllHasSBOMTreeIncludedSoftwareArtifact) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *AllHasSBOMTreeIncludedSoftwareArtifact) __premarshalJSON() (*__premarshalAllHasSBOMTreeIncludedSoftwareArtifact, error) { + var retval __premarshalAllHasSBOMTreeIncludedSoftwareArtifact + + retval.Typename = v.Typename + retval.Id = v.AllArtifactTree.Id + retval.Algorithm = v.AllArtifactTree.Algorithm + retval.Digest = v.AllArtifactTree.Digest + return &retval, nil +} + +// AllHasSBOMTreeIncludedSoftwarePackage includes the requested fields of the GraphQL type Package. +// The GraphQL type's documentation follows. +// +// Package represents the root of the package trie/tree. +// +// We map package information to a trie, closely matching the pURL specification +// (https://github.com/package-url/purl-spec/blob/0dd92f26f8bb11956ffdf5e8acfcee71e8560407/README.rst), +// but deviating from it where GUAC heuristics allow for better representation of +// package information. Each path in the trie fully represents a package; we split +// the trie based on the pURL components. +// +// This node matches a pkg: partial pURL. The type field matches the +// pURL types but we might also use "guac" for the cases where the pURL +// representation is not complete or when we have custom rules. +// +// Since this node is at the root of the package trie, it is named Package, not +// PackageType. +type AllHasSBOMTreeIncludedSoftwarePackage struct { + Typename *string `json:"__typename"` + AllPkgTree `json:"-"` +} + +// GetTypename returns AllHasSBOMTreeIncludedSoftwarePackage.Typename, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedSoftwarePackage) GetTypename() *string { return v.Typename } + +// GetId returns AllHasSBOMTreeIncludedSoftwarePackage.Id, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedSoftwarePackage) GetId() string { return v.AllPkgTree.Id } + +// GetType returns AllHasSBOMTreeIncludedSoftwarePackage.Type, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedSoftwarePackage) GetType() string { return v.AllPkgTree.Type } + +// GetNamespaces returns AllHasSBOMTreeIncludedSoftwarePackage.Namespaces, and is useful for accessing the field via an interface. +func (v *AllHasSBOMTreeIncludedSoftwarePackage) GetNamespaces() []AllPkgTreeNamespacesPackageNamespace { + return v.AllPkgTree.Namespaces +} + +func (v *AllHasSBOMTreeIncludedSoftwarePackage) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *AllHasSBOMTreeIncludedSoftwarePackage + graphql.NoUnmarshalJSON + } + firstPass.AllHasSBOMTreeIncludedSoftwarePackage = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.AllPkgTree) + if err != nil { + return err + } return nil } -type __premarshalAllHasSBOMTree struct { - Id string `json:"id"` +type __premarshalAllHasSBOMTreeIncludedSoftwarePackage struct { + Typename *string `json:"__typename"` - Subject json.RawMessage `json:"subject"` + Id string `json:"id"` - Uri string `json:"uri"` + Type string `json:"type"` - Algorithm string `json:"algorithm"` + Namespaces []AllPkgTreeNamespacesPackageNamespace `json:"namespaces"` +} - Digest string `json:"digest"` +func (v *AllHasSBOMTreeIncludedSoftwarePackage) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} - DownloadLocation string `json:"downloadLocation"` +func (v *AllHasSBOMTreeIncludedSoftwarePackage) __premarshalJSON() (*__premarshalAllHasSBOMTreeIncludedSoftwarePackage, error) { + var retval __premarshalAllHasSBOMTreeIncludedSoftwarePackage - Origin string `json:"origin"` + retval.Typename = v.Typename + retval.Id = v.AllPkgTree.Id + retval.Type = v.AllPkgTree.Type + retval.Namespaces = v.AllPkgTree.Namespaces + return &retval, nil +} - Collector string `json:"collector"` +// AllHasSBOMTreeIncludedSoftwarePackageOrArtifact includes the requested fields of the GraphQL interface PackageOrArtifact. +// +// AllHasSBOMTreeIncludedSoftwarePackageOrArtifact is implemented by the following types: +// AllHasSBOMTreeIncludedSoftwareArtifact +// AllHasSBOMTreeIncludedSoftwarePackage +// The GraphQL type's documentation follows. +// +// PackageOrArtifact is a union of Package and Artifact. +type AllHasSBOMTreeIncludedSoftwarePackageOrArtifact interface { + implementsGraphQLInterfaceAllHasSBOMTreeIncludedSoftwarePackageOrArtifact() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() *string +} - KnownSince time.Time `json:"knownSince"` +func (v *AllHasSBOMTreeIncludedSoftwareArtifact) implementsGraphQLInterfaceAllHasSBOMTreeIncludedSoftwarePackageOrArtifact() { +} +func (v *AllHasSBOMTreeIncludedSoftwarePackage) implementsGraphQLInterfaceAllHasSBOMTreeIncludedSoftwarePackageOrArtifact() { } -func (v *AllHasSBOMTree) MarshalJSON() ([]byte, error) { - premarshaled, err := v.__premarshalJSON() +func __unmarshalAllHasSBOMTreeIncludedSoftwarePackageOrArtifact(b []byte, v *AllHasSBOMTreeIncludedSoftwarePackageOrArtifact) error { + if string(b) == "null" { + return nil + } + + var tn struct { + TypeName string `json:"__typename"` + } + err := json.Unmarshal(b, &tn) if err != nil { - return nil, err + return err + } + + switch tn.TypeName { + case "Artifact": + *v = new(AllHasSBOMTreeIncludedSoftwareArtifact) + return json.Unmarshal(b, *v) + case "Package": + *v = new(AllHasSBOMTreeIncludedSoftwarePackage) + return json.Unmarshal(b, *v) + case "": + return fmt.Errorf( + "response was missing PackageOrArtifact.__typename") + default: + return fmt.Errorf( + `unexpected concrete type for AllHasSBOMTreeIncludedSoftwarePackageOrArtifact: "%v"`, tn.TypeName) } - return json.Marshal(premarshaled) } -func (v *AllHasSBOMTree) __premarshalJSON() (*__premarshalAllHasSBOMTree, error) { - var retval __premarshalAllHasSBOMTree +func __marshalAllHasSBOMTreeIncludedSoftwarePackageOrArtifact(v *AllHasSBOMTreeIncludedSoftwarePackageOrArtifact) ([]byte, error) { - retval.Id = v.Id - { + var typename string + switch v := (*v).(type) { + case *AllHasSBOMTreeIncludedSoftwareArtifact: + typename = "Artifact" - dst := &retval.Subject - src := v.Subject - var err error - *dst, err = __marshalAllHasSBOMTreeSubjectPackageOrArtifact( - &src) + premarshaled, err := v.__premarshalJSON() if err != nil { - return nil, fmt.Errorf( - "unable to marshal AllHasSBOMTree.Subject: %w", err) + return nil, err + } + result := struct { + TypeName string `json:"__typename"` + *__premarshalAllHasSBOMTreeIncludedSoftwareArtifact + }{typename, premarshaled} + return json.Marshal(result) + case *AllHasSBOMTreeIncludedSoftwarePackage: + typename = "Package" + + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err } + result := struct { + TypeName string `json:"__typename"` + *__premarshalAllHasSBOMTreeIncludedSoftwarePackage + }{typename, premarshaled} + return json.Marshal(result) + case nil: + return []byte("null"), nil + default: + return nil, fmt.Errorf( + `unexpected concrete type for AllHasSBOMTreeIncludedSoftwarePackageOrArtifact: "%T"`, v) } - retval.Uri = v.Uri - retval.Algorithm = v.Algorithm - retval.Digest = v.Digest - retval.DownloadLocation = v.DownloadLocation - retval.Origin = v.Origin - retval.Collector = v.Collector - retval.KnownSince = v.KnownSince - return &retval, nil } // AllHasSBOMTreeSubjectArtifact includes the requested fields of the GraphQL type Artifact. @@ -6802,6 +7347,9 @@ const ( EdgeHasMetadataSource Edge = "HAS_METADATA_SOURCE" EdgeHasSbomArtifact Edge = "HAS_SBOM_ARTIFACT" EdgeHasSbomPackage Edge = "HAS_SBOM_PACKAGE" + EdgeHasSbomIncludedSoftware Edge = "HAS_SBOM_INCLUDED_SOFTWARE" + EdgeHasSbomIncludedDependencies Edge = "HAS_SBOM_INCLUDED_DEPENDENCIES" + EdgeHasSbomIncludedOccurrences Edge = "HAS_SBOM_INCLUDED_OCCURRENCES" EdgeHasSlsaBuiltBy Edge = "HAS_SLSA_BUILT_BY" EdgeHasSlsaMaterials Edge = "HAS_SLSA_MATERIALS" EdgeHasSlsaSubject Edge = "HAS_SLSA_SUBJECT" @@ -7380,7 +7928,22 @@ type HasSBOMArtifactsResponse struct { // GetIngestHasSBOMs returns HasSBOMArtifactsResponse.IngestHasSBOMs, and is useful for accessing the field via an interface. func (v *HasSBOMArtifactsResponse) GetIngestHasSBOMs() []string { return v.IngestHasSBOMs } -// HasSBOMInputSpec is the same as HasSBOM but for mutation input. +type HasSBOMIncludesInputSpec struct { + Software []string `json:"software"` + Dependencies []string `json:"dependencies"` + Occurrences []string `json:"occurrences"` +} + +// GetSoftware returns HasSBOMIncludesInputSpec.Software, and is useful for accessing the field via an interface. +func (v *HasSBOMIncludesInputSpec) GetSoftware() []string { return v.Software } + +// GetDependencies returns HasSBOMIncludesInputSpec.Dependencies, and is useful for accessing the field via an interface. +func (v *HasSBOMIncludesInputSpec) GetDependencies() []string { return v.Dependencies } + +// GetOccurrences returns HasSBOMIncludesInputSpec.Occurrences, and is useful for accessing the field via an interface. +func (v *HasSBOMIncludesInputSpec) GetOccurrences() []string { return v.Occurrences } + +// HasSBOMInputSpec is similar to HasSBOM but for mutation input. type HasSBOMInputSpec struct { Uri string `json:"uri"` Algorithm string `json:"algorithm"` @@ -8929,6 +9492,21 @@ func (v *NeighborsNeighborsHasSBOM) GetCollector() string { return v.AllHasSBOMT // GetKnownSince returns NeighborsNeighborsHasSBOM.KnownSince, and is useful for accessing the field via an interface. func (v *NeighborsNeighborsHasSBOM) GetKnownSince() time.Time { return v.AllHasSBOMTree.KnownSince } +// GetIncludedSoftware returns NeighborsNeighborsHasSBOM.IncludedSoftware, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsHasSBOM) GetIncludedSoftware() []AllHasSBOMTreeIncludedSoftwarePackageOrArtifact { + return v.AllHasSBOMTree.IncludedSoftware +} + +// GetIncludedDependencies returns NeighborsNeighborsHasSBOM.IncludedDependencies, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsHasSBOM) GetIncludedDependencies() []AllHasSBOMTreeIncludedDependenciesIsDependency { + return v.AllHasSBOMTree.IncludedDependencies +} + +// GetIncludedOccurrences returns NeighborsNeighborsHasSBOM.IncludedOccurrences, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsHasSBOM) GetIncludedOccurrences() []AllHasSBOMTreeIncludedOccurrencesIsOccurrence { + return v.AllHasSBOMTree.IncludedOccurrences +} + func (v *NeighborsNeighborsHasSBOM) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -8974,6 +9552,12 @@ type __premarshalNeighborsNeighborsHasSBOM struct { Collector string `json:"collector"` KnownSince time.Time `json:"knownSince"` + + IncludedSoftware []json.RawMessage `json:"includedSoftware"` + + IncludedDependencies []AllHasSBOMTreeIncludedDependenciesIsDependency `json:"includedDependencies"` + + IncludedOccurrences []AllHasSBOMTreeIncludedOccurrencesIsOccurrence `json:"includedOccurrences"` } func (v *NeighborsNeighborsHasSBOM) MarshalJSON() ([]byte, error) { @@ -9008,6 +9592,26 @@ func (v *NeighborsNeighborsHasSBOM) __premarshalJSON() (*__premarshalNeighborsNe retval.Origin = v.AllHasSBOMTree.Origin retval.Collector = v.AllHasSBOMTree.Collector retval.KnownSince = v.AllHasSBOMTree.KnownSince + { + + dst := &retval.IncludedSoftware + src := v.AllHasSBOMTree.IncludedSoftware + *dst = make( + []json.RawMessage, + len(src)) + for i, src := range src { + dst := &(*dst)[i] + var err error + *dst, err = __marshalAllHasSBOMTreeIncludedSoftwarePackageOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal NeighborsNeighborsHasSBOM.AllHasSBOMTree.IncludedSoftware: %w", err) + } + } + } + retval.IncludedDependencies = v.AllHasSBOMTree.IncludedDependencies + retval.IncludedOccurrences = v.AllHasSBOMTree.IncludedOccurrences return &retval, nil } @@ -12327,6 +12931,21 @@ func (v *NodeNodeHasSBOM) GetCollector() string { return v.AllHasSBOMTree.Collec // GetKnownSince returns NodeNodeHasSBOM.KnownSince, and is useful for accessing the field via an interface. func (v *NodeNodeHasSBOM) GetKnownSince() time.Time { return v.AllHasSBOMTree.KnownSince } +// GetIncludedSoftware returns NodeNodeHasSBOM.IncludedSoftware, and is useful for accessing the field via an interface. +func (v *NodeNodeHasSBOM) GetIncludedSoftware() []AllHasSBOMTreeIncludedSoftwarePackageOrArtifact { + return v.AllHasSBOMTree.IncludedSoftware +} + +// GetIncludedDependencies returns NodeNodeHasSBOM.IncludedDependencies, and is useful for accessing the field via an interface. +func (v *NodeNodeHasSBOM) GetIncludedDependencies() []AllHasSBOMTreeIncludedDependenciesIsDependency { + return v.AllHasSBOMTree.IncludedDependencies +} + +// GetIncludedOccurrences returns NodeNodeHasSBOM.IncludedOccurrences, and is useful for accessing the field via an interface. +func (v *NodeNodeHasSBOM) GetIncludedOccurrences() []AllHasSBOMTreeIncludedOccurrencesIsOccurrence { + return v.AllHasSBOMTree.IncludedOccurrences +} + func (v *NodeNodeHasSBOM) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -12372,6 +12991,12 @@ type __premarshalNodeNodeHasSBOM struct { Collector string `json:"collector"` KnownSince time.Time `json:"knownSince"` + + IncludedSoftware []json.RawMessage `json:"includedSoftware"` + + IncludedDependencies []AllHasSBOMTreeIncludedDependenciesIsDependency `json:"includedDependencies"` + + IncludedOccurrences []AllHasSBOMTreeIncludedOccurrencesIsOccurrence `json:"includedOccurrences"` } func (v *NodeNodeHasSBOM) MarshalJSON() ([]byte, error) { @@ -12406,6 +13031,26 @@ func (v *NodeNodeHasSBOM) __premarshalJSON() (*__premarshalNodeNodeHasSBOM, erro retval.Origin = v.AllHasSBOMTree.Origin retval.Collector = v.AllHasSBOMTree.Collector retval.KnownSince = v.AllHasSBOMTree.KnownSince + { + + dst := &retval.IncludedSoftware + src := v.AllHasSBOMTree.IncludedSoftware + *dst = make( + []json.RawMessage, + len(src)) + for i, src := range src { + dst := &(*dst)[i] + var err error + *dst, err = __marshalAllHasSBOMTreeIncludedSoftwarePackageOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal NodeNodeHasSBOM.AllHasSBOMTree.IncludedSoftware: %w", err) + } + } + } + retval.IncludedDependencies = v.AllHasSBOMTree.IncludedDependencies + retval.IncludedOccurrences = v.AllHasSBOMTree.IncludedOccurrences return &retval, nil } @@ -14795,6 +15440,21 @@ func (v *NodesNodesHasSBOM) GetCollector() string { return v.AllHasSBOMTree.Coll // GetKnownSince returns NodesNodesHasSBOM.KnownSince, and is useful for accessing the field via an interface. func (v *NodesNodesHasSBOM) GetKnownSince() time.Time { return v.AllHasSBOMTree.KnownSince } +// GetIncludedSoftware returns NodesNodesHasSBOM.IncludedSoftware, and is useful for accessing the field via an interface. +func (v *NodesNodesHasSBOM) GetIncludedSoftware() []AllHasSBOMTreeIncludedSoftwarePackageOrArtifact { + return v.AllHasSBOMTree.IncludedSoftware +} + +// GetIncludedDependencies returns NodesNodesHasSBOM.IncludedDependencies, and is useful for accessing the field via an interface. +func (v *NodesNodesHasSBOM) GetIncludedDependencies() []AllHasSBOMTreeIncludedDependenciesIsDependency { + return v.AllHasSBOMTree.IncludedDependencies +} + +// GetIncludedOccurrences returns NodesNodesHasSBOM.IncludedOccurrences, and is useful for accessing the field via an interface. +func (v *NodesNodesHasSBOM) GetIncludedOccurrences() []AllHasSBOMTreeIncludedOccurrencesIsOccurrence { + return v.AllHasSBOMTree.IncludedOccurrences +} + func (v *NodesNodesHasSBOM) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -14840,6 +15500,12 @@ type __premarshalNodesNodesHasSBOM struct { Collector string `json:"collector"` KnownSince time.Time `json:"knownSince"` + + IncludedSoftware []json.RawMessage `json:"includedSoftware"` + + IncludedDependencies []AllHasSBOMTreeIncludedDependenciesIsDependency `json:"includedDependencies"` + + IncludedOccurrences []AllHasSBOMTreeIncludedOccurrencesIsOccurrence `json:"includedOccurrences"` } func (v *NodesNodesHasSBOM) MarshalJSON() ([]byte, error) { @@ -14874,6 +15540,26 @@ func (v *NodesNodesHasSBOM) __premarshalJSON() (*__premarshalNodesNodesHasSBOM, retval.Origin = v.AllHasSBOMTree.Origin retval.Collector = v.AllHasSBOMTree.Collector retval.KnownSince = v.AllHasSBOMTree.KnownSince + { + + dst := &retval.IncludedSoftware + src := v.AllHasSBOMTree.IncludedSoftware + *dst = make( + []json.RawMessage, + len(src)) + for i, src := range src { + dst := &(*dst)[i] + var err error + *dst, err = __marshalAllHasSBOMTreeIncludedSoftwarePackageOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal NodesNodesHasSBOM.AllHasSBOMTree.IncludedSoftware: %w", err) + } + } + } + retval.IncludedDependencies = v.AllHasSBOMTree.IncludedDependencies + retval.IncludedOccurrences = v.AllHasSBOMTree.IncludedOccurrences return &retval, nil } @@ -18265,6 +18951,21 @@ func (v *PathPathHasSBOM) GetCollector() string { return v.AllHasSBOMTree.Collec // GetKnownSince returns PathPathHasSBOM.KnownSince, and is useful for accessing the field via an interface. func (v *PathPathHasSBOM) GetKnownSince() time.Time { return v.AllHasSBOMTree.KnownSince } +// GetIncludedSoftware returns PathPathHasSBOM.IncludedSoftware, and is useful for accessing the field via an interface. +func (v *PathPathHasSBOM) GetIncludedSoftware() []AllHasSBOMTreeIncludedSoftwarePackageOrArtifact { + return v.AllHasSBOMTree.IncludedSoftware +} + +// GetIncludedDependencies returns PathPathHasSBOM.IncludedDependencies, and is useful for accessing the field via an interface. +func (v *PathPathHasSBOM) GetIncludedDependencies() []AllHasSBOMTreeIncludedDependenciesIsDependency { + return v.AllHasSBOMTree.IncludedDependencies +} + +// GetIncludedOccurrences returns PathPathHasSBOM.IncludedOccurrences, and is useful for accessing the field via an interface. +func (v *PathPathHasSBOM) GetIncludedOccurrences() []AllHasSBOMTreeIncludedOccurrencesIsOccurrence { + return v.AllHasSBOMTree.IncludedOccurrences +} + func (v *PathPathHasSBOM) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -18310,6 +19011,12 @@ type __premarshalPathPathHasSBOM struct { Collector string `json:"collector"` KnownSince time.Time `json:"knownSince"` + + IncludedSoftware []json.RawMessage `json:"includedSoftware"` + + IncludedDependencies []AllHasSBOMTreeIncludedDependenciesIsDependency `json:"includedDependencies"` + + IncludedOccurrences []AllHasSBOMTreeIncludedOccurrencesIsOccurrence `json:"includedOccurrences"` } func (v *PathPathHasSBOM) MarshalJSON() ([]byte, error) { @@ -18344,6 +19051,26 @@ func (v *PathPathHasSBOM) __premarshalJSON() (*__premarshalPathPathHasSBOM, erro retval.Origin = v.AllHasSBOMTree.Origin retval.Collector = v.AllHasSBOMTree.Collector retval.KnownSince = v.AllHasSBOMTree.KnownSince + { + + dst := &retval.IncludedSoftware + src := v.AllHasSBOMTree.IncludedSoftware + *dst = make( + []json.RawMessage, + len(src)) + for i, src := range src { + dst := &(*dst)[i] + var err error + *dst, err = __marshalAllHasSBOMTreeIncludedSoftwarePackageOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal PathPathHasSBOM.AllHasSBOMTree.IncludedSoftware: %w", err) + } + } + } + retval.IncludedDependencies = v.AllHasSBOMTree.IncludedDependencies + retval.IncludedOccurrences = v.AllHasSBOMTree.IncludedOccurrences return &retval, nil } @@ -21451,8 +22178,9 @@ func (v *__HasMetadataSrcsInput) GetHasMetadataList() []HasMetadataInputSpec { // __HasSBOMArtifactInput is used internally by genqlient type __HasSBOMArtifactInput struct { - Artifact ArtifactInputSpec `json:"artifact"` - HasSBOM HasSBOMInputSpec `json:"hasSBOM"` + Artifact ArtifactInputSpec `json:"artifact"` + HasSBOM HasSBOMInputSpec `json:"hasSBOM"` + Includes HasSBOMIncludesInputSpec `json:"includes"` } // GetArtifact returns __HasSBOMArtifactInput.Artifact, and is useful for accessing the field via an interface. @@ -21461,10 +22189,14 @@ func (v *__HasSBOMArtifactInput) GetArtifact() ArtifactInputSpec { return v.Arti // GetHasSBOM returns __HasSBOMArtifactInput.HasSBOM, and is useful for accessing the field via an interface. func (v *__HasSBOMArtifactInput) GetHasSBOM() HasSBOMInputSpec { return v.HasSBOM } +// GetIncludes returns __HasSBOMArtifactInput.Includes, and is useful for accessing the field via an interface. +func (v *__HasSBOMArtifactInput) GetIncludes() HasSBOMIncludesInputSpec { return v.Includes } + // __HasSBOMArtifactsInput is used internally by genqlient type __HasSBOMArtifactsInput struct { - Artifacts []ArtifactInputSpec `json:"artifacts"` - HasSBOMs []HasSBOMInputSpec `json:"hasSBOMs"` + Artifacts []ArtifactInputSpec `json:"artifacts"` + HasSBOMs []HasSBOMInputSpec `json:"hasSBOMs"` + Includes []HasSBOMIncludesInputSpec `json:"includes"` } // GetArtifacts returns __HasSBOMArtifactsInput.Artifacts, and is useful for accessing the field via an interface. @@ -21473,10 +22205,14 @@ func (v *__HasSBOMArtifactsInput) GetArtifacts() []ArtifactInputSpec { return v. // GetHasSBOMs returns __HasSBOMArtifactsInput.HasSBOMs, and is useful for accessing the field via an interface. func (v *__HasSBOMArtifactsInput) GetHasSBOMs() []HasSBOMInputSpec { return v.HasSBOMs } +// GetIncludes returns __HasSBOMArtifactsInput.Includes, and is useful for accessing the field via an interface. +func (v *__HasSBOMArtifactsInput) GetIncludes() []HasSBOMIncludesInputSpec { return v.Includes } + // __HasSBOMPkgInput is used internally by genqlient type __HasSBOMPkgInput struct { - Pkg PkgInputSpec `json:"pkg"` - HasSBOM HasSBOMInputSpec `json:"hasSBOM"` + Pkg PkgInputSpec `json:"pkg"` + HasSBOM HasSBOMInputSpec `json:"hasSBOM"` + Includes HasSBOMIncludesInputSpec `json:"includes"` } // GetPkg returns __HasSBOMPkgInput.Pkg, and is useful for accessing the field via an interface. @@ -21485,10 +22221,14 @@ func (v *__HasSBOMPkgInput) GetPkg() PkgInputSpec { return v.Pkg } // GetHasSBOM returns __HasSBOMPkgInput.HasSBOM, and is useful for accessing the field via an interface. func (v *__HasSBOMPkgInput) GetHasSBOM() HasSBOMInputSpec { return v.HasSBOM } +// GetIncludes returns __HasSBOMPkgInput.Includes, and is useful for accessing the field via an interface. +func (v *__HasSBOMPkgInput) GetIncludes() HasSBOMIncludesInputSpec { return v.Includes } + // __HasSBOMPkgsInput is used internally by genqlient type __HasSBOMPkgsInput struct { - Pkgs []PkgInputSpec `json:"pkgs"` - HasSBOMs []HasSBOMInputSpec `json:"hasSBOMs"` + Pkgs []PkgInputSpec `json:"pkgs"` + HasSBOMs []HasSBOMInputSpec `json:"hasSBOMs"` + Includes []HasSBOMIncludesInputSpec `json:"includes"` } // GetPkgs returns __HasSBOMPkgsInput.Pkgs, and is useful for accessing the field via an interface. @@ -21497,6 +22237,9 @@ func (v *__HasSBOMPkgsInput) GetPkgs() []PkgInputSpec { return v.Pkgs } // GetHasSBOMs returns __HasSBOMPkgsInput.HasSBOMs, and is useful for accessing the field via an interface. func (v *__HasSBOMPkgsInput) GetHasSBOMs() []HasSBOMInputSpec { return v.HasSBOMs } +// GetIncludes returns __HasSBOMPkgsInput.Includes, and is useful for accessing the field via an interface. +func (v *__HasSBOMPkgsInput) GetIncludes() []HasSBOMIncludesInputSpec { return v.Includes } + // __IngestArtifactInput is used internally by genqlient type __IngestArtifactInput struct { Artifact ArtifactInputSpec `json:"artifact"` @@ -23543,8 +24286,8 @@ func HasMetadataSrcs( // The query or mutation executed by HasSBOMArtifact. const HasSBOMArtifact_Operation = ` -mutation HasSBOMArtifact ($artifact: ArtifactInputSpec!, $hasSBOM: HasSBOMInputSpec!) { - ingestHasSBOM(subject: {artifact:$artifact}, hasSBOM: $hasSBOM) +mutation HasSBOMArtifact ($artifact: ArtifactInputSpec!, $hasSBOM: HasSBOMInputSpec!, $includes: HasSBOMIncludesInputSpec!) { + ingestHasSBOM(subject: {artifact:$artifact}, hasSBOM: $hasSBOM, includes: $includes) } ` @@ -23553,6 +24296,7 @@ func HasSBOMArtifact( client graphql.Client, artifact ArtifactInputSpec, hasSBOM HasSBOMInputSpec, + includes HasSBOMIncludesInputSpec, ) (*HasSBOMArtifactResponse, error) { req := &graphql.Request{ OpName: "HasSBOMArtifact", @@ -23560,6 +24304,7 @@ func HasSBOMArtifact( Variables: &__HasSBOMArtifactInput{ Artifact: artifact, HasSBOM: hasSBOM, + Includes: includes, }, } var err error @@ -23578,8 +24323,8 @@ func HasSBOMArtifact( // The query or mutation executed by HasSBOMArtifacts. const HasSBOMArtifacts_Operation = ` -mutation HasSBOMArtifacts ($artifacts: [ArtifactInputSpec!]!, $hasSBOMs: [HasSBOMInputSpec!]!) { - ingestHasSBOMs(subjects: {artifacts:$artifacts}, hasSBOMs: $hasSBOMs) +mutation HasSBOMArtifacts ($artifacts: [ArtifactInputSpec!]!, $hasSBOMs: [HasSBOMInputSpec!]!, $includes: [HasSBOMIncludesInputSpec!]!) { + ingestHasSBOMs(subjects: {artifacts:$artifacts}, hasSBOMs: $hasSBOMs, includes: $includes) } ` @@ -23588,6 +24333,7 @@ func HasSBOMArtifacts( client graphql.Client, artifacts []ArtifactInputSpec, hasSBOMs []HasSBOMInputSpec, + includes []HasSBOMIncludesInputSpec, ) (*HasSBOMArtifactsResponse, error) { req := &graphql.Request{ OpName: "HasSBOMArtifacts", @@ -23595,6 +24341,7 @@ func HasSBOMArtifacts( Variables: &__HasSBOMArtifactsInput{ Artifacts: artifacts, HasSBOMs: hasSBOMs, + Includes: includes, }, } var err error @@ -23613,8 +24360,8 @@ func HasSBOMArtifacts( // The query or mutation executed by HasSBOMPkg. const HasSBOMPkg_Operation = ` -mutation HasSBOMPkg ($pkg: PkgInputSpec!, $hasSBOM: HasSBOMInputSpec!) { - ingestHasSBOM(subject: {package:$pkg}, hasSBOM: $hasSBOM) +mutation HasSBOMPkg ($pkg: PkgInputSpec!, $hasSBOM: HasSBOMInputSpec!, $includes: HasSBOMIncludesInputSpec!) { + ingestHasSBOM(subject: {package:$pkg}, hasSBOM: $hasSBOM, includes: $includes) } ` @@ -23623,13 +24370,15 @@ func HasSBOMPkg( client graphql.Client, pkg PkgInputSpec, hasSBOM HasSBOMInputSpec, + includes HasSBOMIncludesInputSpec, ) (*HasSBOMPkgResponse, error) { req := &graphql.Request{ OpName: "HasSBOMPkg", Query: HasSBOMPkg_Operation, Variables: &__HasSBOMPkgInput{ - Pkg: pkg, - HasSBOM: hasSBOM, + Pkg: pkg, + HasSBOM: hasSBOM, + Includes: includes, }, } var err error @@ -23648,8 +24397,8 @@ func HasSBOMPkg( // The query or mutation executed by HasSBOMPkgs. const HasSBOMPkgs_Operation = ` -mutation HasSBOMPkgs ($pkgs: [PkgInputSpec!]!, $hasSBOMs: [HasSBOMInputSpec!]!) { - ingestHasSBOMs(subjects: {packages:$pkgs}, hasSBOMs: $hasSBOMs) +mutation HasSBOMPkgs ($pkgs: [PkgInputSpec!]!, $hasSBOMs: [HasSBOMInputSpec!]!, $includes: [HasSBOMIncludesInputSpec!]!) { + ingestHasSBOMs(subjects: {packages:$pkgs}, hasSBOMs: $hasSBOMs, includes: $includes) } ` @@ -23658,6 +24407,7 @@ func HasSBOMPkgs( client graphql.Client, pkgs []PkgInputSpec, hasSBOMs []HasSBOMInputSpec, + includes []HasSBOMIncludesInputSpec, ) (*HasSBOMPkgsResponse, error) { req := &graphql.Request{ OpName: "HasSBOMPkgs", @@ -23665,6 +24415,7 @@ func HasSBOMPkgs( Variables: &__HasSBOMPkgsInput{ Pkgs: pkgs, HasSBOMs: hasSBOMs, + Includes: includes, }, } var err error @@ -24930,6 +25681,21 @@ fragment AllHasSBOMTree on HasSBOM { origin collector knownSince + includedSoftware { + __typename + ... on Artifact { + ... AllArtifactTree + } + ... on Package { + ... AllPkgTree + } + } + includedDependencies { + ... AllIsDependencyTree + } + includedOccurrences { + ... AllIsOccurrencesTree + } } fragment AllHasSourceAt on HasSourceAt { id @@ -25392,6 +26158,21 @@ fragment AllHasSBOMTree on HasSBOM { origin collector knownSince + includedSoftware { + __typename + ... on Artifact { + ... AllArtifactTree + } + ... on Package { + ... AllPkgTree + } + } + includedDependencies { + ... AllIsDependencyTree + } + includedOccurrences { + ... AllIsOccurrencesTree + } } fragment AllHasSourceAt on HasSourceAt { id @@ -25852,6 +26633,21 @@ fragment AllHasSBOMTree on HasSBOM { origin collector knownSince + includedSoftware { + __typename + ... on Artifact { + ... AllArtifactTree + } + ... on Package { + ... AllPkgTree + } + } + includedDependencies { + ... AllIsDependencyTree + } + includedOccurrences { + ... AllIsOccurrencesTree + } } fragment AllHasSourceAt on HasSourceAt { id @@ -26541,6 +27337,21 @@ fragment AllHasSBOMTree on HasSBOM { origin collector knownSince + includedSoftware { + __typename + ... on Artifact { + ... AllArtifactTree + } + ... on Package { + ... AllPkgTree + } + } + includedDependencies { + ... AllIsDependencyTree + } + includedOccurrences { + ... AllIsOccurrencesTree + } } fragment AllHasSourceAt on HasSourceAt { id diff --git a/pkg/assembler/clients/helpers/assembler.go b/pkg/assembler/clients/helpers/assembler.go index b7d1810ca18..2a41f4c030e 100644 --- a/pkg/assembler/clients/helpers/assembler.go +++ b/pkg/assembler/clients/helpers/assembler.go @@ -30,11 +30,14 @@ func GetAssembler(ctx context.Context, gqlclient graphql.Client) func([]assemble logger := logging.FromContext(ctx) return func(preds []assembler.IngestPredicates) error { for _, p := range preds { + var packageAndArtifactIDs []string packages := p.GetPackages(ctx) logger.Infof("assembling Package: %v", len(packages)) for _, v := range packages { - if err := ingestPackage(ctx, gqlclient, v); err != nil { + if id, err := ingestPackage(ctx, gqlclient, v); err != nil { return err + } else { + packageAndArtifactIDs = append(packageAndArtifactIDs, *id) } } @@ -49,15 +52,19 @@ func GetAssembler(ctx context.Context, gqlclient graphql.Client) func([]assemble artifacts := p.GetArtifacts(ctx) logger.Infof("assembling Artifact: %v", len(artifacts)) for _, v := range artifacts { - if err := ingestArtifact(ctx, gqlclient, v); err != nil { + if id, err := ingestArtifact(ctx, gqlclient, v); err != nil { return err + } else { + packageAndArtifactIDs = append(packageAndArtifactIDs, *id) } } materials := p.GetMaterials(ctx) logger.Infof("assembling Materials (Artifact): %v", len(materials)) - if err := ingestArtifacts(ctx, gqlclient, materials); err != nil { + if ids, err := ingestArtifacts(ctx, gqlclient, materials); err != nil { return err + } else { + packageAndArtifactIDs = append(packageAndArtifactIDs, ids...) } builders := p.GetBuilders(ctx) @@ -91,17 +98,23 @@ func GetAssembler(ctx context.Context, gqlclient graphql.Client) func([]assemble } } + var isDependenciesIDs []string logger.Infof("assembling IsDependency: %v", len(p.IsDependency)) for _, v := range p.IsDependency { - if err := ingestIsDependency(ctx, gqlclient, v); err != nil { + if id, err := ingestIsDependency(ctx, gqlclient, v); err != nil { return err + } else { + isDependenciesIDs = append(isDependenciesIDs, *id) } } + var isOccurrencesIDs []string logger.Infof("assembling IsOccurrence: %v", len(p.IsOccurrence)) for _, v := range p.IsOccurrence { - if err := ingestIsOccurrence(ctx, gqlclient, v); err != nil { + if id, err := ingestIsOccurrence(ctx, gqlclient, v); err != nil { return err + } else { + isOccurrencesIDs = append(isOccurrencesIDs, *id) } } @@ -168,8 +181,15 @@ func GetAssembler(ctx context.Context, gqlclient graphql.Client) func([]assemble } } + includes := model.HasSBOMIncludesInputSpec{ + Software: packageAndArtifactIDs, + Dependencies: isDependenciesIDs, + Occurrences: isOccurrencesIDs, + } + logger.Infof("assembling HasSBOM: %v", len(p.HasSBOM)) for _, hb := range p.HasSBOM { + hb.Includes = &includes if err := ingestHasSBOM(ctx, gqlclient, hb); err != nil { return err } @@ -207,9 +227,12 @@ func GetAssembler(ctx context.Context, gqlclient graphql.Client) func([]assemble } } -func ingestPackage(ctx context.Context, client graphql.Client, v *model.PkgInputSpec) error { - _, err := model.IngestPackage(ctx, client, *v) - return err +func ingestPackage(ctx context.Context, client graphql.Client, v *model.PkgInputSpec) (*string, error) { + if result, err := model.IngestPackage(ctx, client, *v); err != nil { + return nil, err + } else { + return &result.IngestPackage, nil + } } func ingestSource(ctx context.Context, client graphql.Client, v *model.SourceInputSpec) error { @@ -217,9 +240,12 @@ func ingestSource(ctx context.Context, client graphql.Client, v *model.SourceInp return err } -func ingestArtifact(ctx context.Context, client graphql.Client, v *model.ArtifactInputSpec) error { - _, err := model.IngestArtifact(ctx, client, *v) - return err +func ingestArtifact(ctx context.Context, client graphql.Client, v *model.ArtifactInputSpec) (*string, error) { + if result, err := model.IngestArtifact(ctx, client, *v); err != nil { + return nil, err + } else { + return &result.IngestArtifact, err + } } func ingestBuilder(ctx context.Context, client graphql.Client, v *model.BuilderInputSpec) error { @@ -242,25 +268,34 @@ func ingestCertifyScorecard(ctx context.Context, client graphql.Client, v assemb return err } -func ingestIsDependency(ctx context.Context, client graphql.Client, v assembler.IsDependencyIngest) error { - _, err := model.IsDependency(ctx, client, *v.Pkg, *v.DepPkg, v.DepPkgMatchFlag, *v.IsDependency) - return err +func ingestIsDependency(ctx context.Context, client graphql.Client, v assembler.IsDependencyIngest) (*string, error) { + if response, err := model.IsDependency(ctx, client, *v.Pkg, *v.DepPkg, v.DepPkgMatchFlag, *v.IsDependency); err != nil { + return nil, err + } else { + return &response.IngestDependency, nil + } } -func ingestIsOccurrence(ctx context.Context, client graphql.Client, v assembler.IsOccurrenceIngest) error { +func ingestIsOccurrence(ctx context.Context, client graphql.Client, v assembler.IsOccurrenceIngest) (*string, error) { if v.Pkg != nil && v.Src != nil { - return fmt.Errorf("unable to create IsOccurrence with both Src and Pkg subject specified") + return nil, fmt.Errorf("unable to create IsOccurrence with both Src and Pkg subject specified") } if v.Pkg == nil && v.Src == nil { - return fmt.Errorf("unable to create IsOccurrence without either Src and Pkg subject specified") + return nil, fmt.Errorf("unable to create IsOccurrence without either Src and Pkg subject specified") } if v.Src != nil { - _, err := model.IsOccurrenceSrc(ctx, client, *v.Src, *v.Artifact, *v.IsOccurrence) - return err + if result, err := model.IsOccurrenceSrc(ctx, client, *v.Src, *v.Artifact, *v.IsOccurrence); err != nil { + return nil, err + } else { + return &result.IngestOccurrence, nil + } + } + if result, err := model.IsOccurrencePkg(ctx, client, *v.Pkg, *v.Artifact, *v.IsOccurrence); err != nil { + return nil, err + } else { + return &result.IngestOccurrence, err } - _, err := model.IsOccurrencePkg(ctx, client, *v.Pkg, *v.Artifact, *v.IsOccurrence) - return err } func ingestHasSlsa(ctx context.Context, client graphql.Client, v assembler.HasSlsaIngest) error { @@ -367,10 +402,10 @@ func ingestHasSBOM(ctx context.Context, client graphql.Client, hb assembler.HasS } if hb.Pkg != nil { - _, err := model.HasSBOMPkg(ctx, client, *hb.Pkg, *hb.HasSBOM) + _, err := model.HasSBOMPkg(ctx, client, *hb.Pkg, *hb.HasSBOM, *hb.Includes) return err } - _, err := model.HasSBOMArtifact(ctx, client, *hb.Artifact, *hb.HasSBOM) + _, err := model.HasSBOMArtifact(ctx, client, *hb.Artifact, *hb.HasSBOM, *hb.Includes) return err } diff --git a/pkg/assembler/clients/helpers/bulk.go b/pkg/assembler/clients/helpers/bulk.go index 8a48bd81500..2a0e783c363 100644 --- a/pkg/assembler/clients/helpers/bulk.go +++ b/pkg/assembler/clients/helpers/bulk.go @@ -30,6 +30,8 @@ func GetBulkAssembler(ctx context.Context, gqlclient graphql.Client) func([]asse logger := logging.FromContext(ctx) return func(preds []assembler.IngestPredicates) error { for _, p := range preds { + var packageAndArtifactIDs []string + packages := p.GetPackages(ctx) logger.Infof("assembling Package: %v", len(packages)) var collectedPackages []model.PkgInputSpec @@ -37,8 +39,10 @@ func GetBulkAssembler(ctx context.Context, gqlclient graphql.Client) func([]asse for _, v := range packages { collectedPackages = append(collectedPackages, *v) } - if err := ingestPackages(ctx, gqlclient, collectedPackages); err != nil { + if ids, err := ingestPackages(ctx, gqlclient, collectedPackages); err != nil { logger.Errorf("ingestPackages failed with error: %v", err) + } else { + packageAndArtifactIDs = append(packageAndArtifactIDs, ids...) } sources := p.GetSources(ctx) @@ -60,14 +64,18 @@ func GetBulkAssembler(ctx context.Context, gqlclient graphql.Client) func([]asse for _, v := range artifacts { collectedArtifacts = append(collectedArtifacts, *v) } - if err := ingestArtifacts(ctx, gqlclient, collectedArtifacts); err != nil { + if ids, err := ingestArtifacts(ctx, gqlclient, collectedArtifacts); err != nil { logger.Errorf("ingestArtifacts failed with error: %v", err) + } else { + packageAndArtifactIDs = append(packageAndArtifactIDs, ids...) } materials := p.GetMaterials(ctx) logger.Infof("assembling Materials (Artifact): %v", len(materials)) - if err := ingestArtifacts(ctx, gqlclient, materials); err != nil { + if ids, err := ingestArtifacts(ctx, gqlclient, materials); err != nil { logger.Errorf("ingestArtifacts failed with error: %v", err) + } else { + packageAndArtifactIDs = append(packageAndArtifactIDs, ids...) } builders := p.GetBuilders(ctx) @@ -104,13 +112,19 @@ func GetBulkAssembler(ctx context.Context, gqlclient graphql.Client) func([]asse } logger.Infof("assembling IsDependency: %v", len(p.IsDependency)) - if err := ingestIsDependencies(ctx, gqlclient, p.IsDependency); err != nil { + isDependenciesIDs := []string{} + if ingestedIsDependenciesIDs, err := ingestIsDependencies(ctx, gqlclient, p.IsDependency); err != nil { logger.Errorf("ingestIsDependencies failed with error: %v", err) + } else { + isDependenciesIDs = append(isDependenciesIDs, ingestedIsDependenciesIDs...) } logger.Infof("assembling IsOccurrence: %v", len(p.IsOccurrence)) - if err := ingestIsOccurrences(ctx, gqlclient, p.IsOccurrence); err != nil { + isOccurrencesIDs := []string{} + if ingestedIsOccurrencesIDs, err := ingestIsOccurrences(ctx, gqlclient, p.IsOccurrence); err != nil { logger.Errorf("ingestIsOccurrences failed with error: %v", err) + } else { + isOccurrencesIDs = append(isOccurrencesIDs, ingestedIsOccurrencesIDs...) } logger.Infof("assembling HasSLSA: %v", len(p.HasSlsa)) @@ -162,7 +176,11 @@ func GetBulkAssembler(ctx context.Context, gqlclient graphql.Client) func([]asse } logger.Infof("assembling HasSBOM: %v", len(p.HasSBOM)) - if err := ingestHasSBOMs(ctx, gqlclient, p.HasSBOM); err != nil { + if err := ingestHasSBOMs(ctx, gqlclient, p.HasSBOM, model.HasSBOMIncludesInputSpec{ + Software: packageAndArtifactIDs, + Dependencies: isDependenciesIDs, + Occurrences: isOccurrencesIDs, + }); err != nil { logger.Errorf("ingestHasSBOMs failed with error: %v", err) } @@ -190,12 +208,12 @@ func GetBulkAssembler(ctx context.Context, gqlclient graphql.Client) func([]asse } } -func ingestPackages(ctx context.Context, client graphql.Client, v []model.PkgInputSpec) error { - _, err := model.IngestPackages(ctx, client, v) +func ingestPackages(ctx context.Context, client graphql.Client, v []model.PkgInputSpec) ([]string, error) { + response, err := model.IngestPackages(ctx, client, v) if err != nil { - return fmt.Errorf("ingestPackages failed with error: %w", err) + return nil, fmt.Errorf("ingestPackages failed with error: %w", err) } - return nil + return response.IngestPackages, nil } func ingestSources(ctx context.Context, client graphql.Client, v []model.SourceInputSpec) error { @@ -206,12 +224,12 @@ func ingestSources(ctx context.Context, client graphql.Client, v []model.SourceI return nil } -func ingestArtifacts(ctx context.Context, client graphql.Client, v []model.ArtifactInputSpec) error { - _, err := model.IngestArtifacts(ctx, client, v) +func ingestArtifacts(ctx context.Context, client graphql.Client, v []model.ArtifactInputSpec) ([]string, error) { + response, err := model.IngestArtifacts(ctx, client, v) if err != nil { - return fmt.Errorf("ingestArtifacts failed with error: %w", err) + return nil, fmt.Errorf("ingestArtifacts failed with error: %w", err) } - return nil + return response.IngestArtifacts, nil } func ingestBuilders(ctx context.Context, client graphql.Client, v []model.BuilderInputSpec) error { @@ -400,7 +418,7 @@ func ingestCertifyScorecards(ctx context.Context, client graphql.Client, v []ass return nil } -func ingestIsDependencies(ctx context.Context, client graphql.Client, v []assembler.IsDependencyIngest) error { +func ingestIsDependencies(ctx context.Context, client graphql.Client, v []assembler.IsDependencyIngest) ([]string, error) { var depToVersion, depToName struct { pkgs []model.PkgInputSpec @@ -424,20 +442,23 @@ func ingestIsDependencies(ctx context.Context, client graphql.Client, v []assemb } } + var isDependenciesIDs []string if len(depToVersion.pkgs) > 0 { - _, err := model.IsDependencies(ctx, client, depToVersion.pkgs, depToVersion.depPkgs, depToVersion.depPkgMatchFlag, depToVersion.dependencies) + isDependencies, err := model.IsDependencies(ctx, client, depToVersion.pkgs, depToVersion.depPkgs, depToVersion.depPkgMatchFlag, depToVersion.dependencies) if err != nil { - return fmt.Errorf("isDependencies failed with error: %w", err) + return nil, fmt.Errorf("isDependencies failed with error: %w", err) } + isDependenciesIDs = append(isDependenciesIDs, isDependencies.IngestDependencies...) } if len(depToName.pkgs) > 0 { - _, err := model.IsDependencies(ctx, client, depToName.pkgs, depToName.depPkgs, depToName.depPkgMatchFlag, depToName.dependencies) + isDependencies, err := model.IsDependencies(ctx, client, depToName.pkgs, depToName.depPkgs, depToName.depPkgMatchFlag, depToName.dependencies) if err != nil { - return fmt.Errorf("isDependencies failed with error: %w", err) + return nil, fmt.Errorf("isDependencies failed with error: %w", err) } + isDependenciesIDs = append(isDependenciesIDs, isDependencies.IngestDependencies...) } - return nil + return isDependenciesIDs, nil } func ingestPkgEquals(ctx context.Context, client graphql.Client, v []assembler.PkgEqualIngest) error { @@ -476,11 +497,13 @@ func ingestHashEquals(ctx context.Context, client graphql.Client, v []assembler. return nil } -func ingestHasSBOMs(ctx context.Context, client graphql.Client, v []assembler.HasSBOMIngest) error { +func ingestHasSBOMs(ctx context.Context, client graphql.Client, v []assembler.HasSBOMIngest, includes model.HasSBOMIncludesInputSpec) error { var pkgs []model.PkgInputSpec var artifacts []model.ArtifactInputSpec var pkgSBOMs []model.HasSBOMInputSpec var artSBOMs []model.HasSBOMInputSpec + var pkgIncludes []model.HasSBOMIncludesInputSpec + var artIncludes []model.HasSBOMIncludesInputSpec for _, ingest := range v { if ingest.Pkg != nil && ingest.Artifact != nil { return fmt.Errorf("unable to create hasSBOM with both artifact and Pkg subject specified") @@ -492,19 +515,21 @@ func ingestHasSBOMs(ctx context.Context, client graphql.Client, v []assembler.Ha if ingest.Pkg != nil { pkgs = append(pkgs, *ingest.Pkg) pkgSBOMs = append(pkgSBOMs, *ingest.HasSBOM) + pkgIncludes = append(pkgIncludes, includes) } else { artifacts = append(artifacts, *ingest.Artifact) artSBOMs = append(artSBOMs, *ingest.HasSBOM) + artIncludes = append(artIncludes, includes) } } if len(artifacts) > 0 { - _, err := model.HasSBOMArtifacts(ctx, client, artifacts, artSBOMs) + _, err := model.HasSBOMArtifacts(ctx, client, artifacts, artSBOMs, artIncludes) if err != nil { return fmt.Errorf("hasSBOMArtifacts failed with error: %w", err) } } if len(pkgs) > 0 { - _, err := model.HasSBOMPkgs(ctx, client, pkgs, pkgSBOMs) + _, err := model.HasSBOMPkgs(ctx, client, pkgs, pkgSBOMs, pkgIncludes) if err != nil { return fmt.Errorf("hasSBOMPkgs failed with error: %w", err) } @@ -736,7 +761,7 @@ func ingestCertifyBads(ctx context.Context, client graphql.Client, v []assembler return nil } -func ingestIsOccurrences(ctx context.Context, client graphql.Client, v []assembler.IsOccurrenceIngest) error { +func ingestIsOccurrences(ctx context.Context, client graphql.Client, v []assembler.IsOccurrenceIngest) ([]string, error) { var pkgs []model.PkgInputSpec var sources []model.SourceInputSpec var pkgArtifacts []model.ArtifactInputSpec @@ -746,10 +771,10 @@ func ingestIsOccurrences(ctx context.Context, client graphql.Client, v []assembl for _, ingest := range v { if ingest.Pkg != nil && ingest.Src != nil { - return fmt.Errorf("unable to create IsOccurrence with both Src and Pkg subject specified") + return nil, fmt.Errorf("unable to create IsOccurrence with both Src and Pkg subject specified") } if ingest.Pkg == nil && ingest.Src == nil { - return fmt.Errorf("unable to create IsOccurrence without either Src and Pkg subject specified") + return nil, fmt.Errorf("unable to create IsOccurrence without either Src and Pkg subject specified") } if ingest.Pkg != nil { @@ -762,19 +787,22 @@ func ingestIsOccurrences(ctx context.Context, client graphql.Client, v []assembl srcOccurrences = append(srcOccurrences, *ingest.IsOccurrence) } } + var isOccurrencesIDs []string if len(sources) > 0 { - _, err := model.IsOccurrencesSrc(ctx, client, sources, srcArtifacts, srcOccurrences) + isOccurrences, err := model.IsOccurrencesSrc(ctx, client, sources, srcArtifacts, srcOccurrences) if err != nil { - return fmt.Errorf("isOccurrencesSrc failed with error: %w", err) + return nil, fmt.Errorf("isOccurrencesSrc failed with error: %w", err) } + isOccurrencesIDs = append(isOccurrencesIDs, isOccurrences.IngestOccurrences...) } if len(pkgs) > 0 { - _, err := model.IsOccurrencesPkg(ctx, client, pkgs, pkgArtifacts, pkgOccurrences) + isOccurrences, err := model.IsOccurrencesPkg(ctx, client, pkgs, pkgArtifacts, pkgOccurrences) if err != nil { - return fmt.Errorf("isOccurrencesPkg failed with error: %w", err) + return nil, fmt.Errorf("isOccurrencesPkg failed with error: %w", err) } + isOccurrencesIDs = append(isOccurrencesIDs, isOccurrences.IngestOccurrences...) } - return nil + return isOccurrencesIDs, nil } func ingestCertifyLegals(ctx context.Context, client graphql.Client, v []assembler.CertifyLegalIngest) error { diff --git a/pkg/assembler/clients/operations/hasSBOM.graphql b/pkg/assembler/clients/operations/hasSBOM.graphql index 5c6f2ba8cbe..de115ef15ef 100644 --- a/pkg/assembler/clients/operations/hasSBOM.graphql +++ b/pkg/assembler/clients/operations/hasSBOM.graphql @@ -17,26 +17,28 @@ # Defines the GraphQL operations to ingest that a package or source has an SBOM (specified by a URI) into GUAC -mutation HasSBOMPkg($pkg: PkgInputSpec!, $hasSBOM: HasSBOMInputSpec!) { - ingestHasSBOM(subject: { package: $pkg }, hasSBOM: $hasSBOM) +mutation HasSBOMPkg($pkg: PkgInputSpec!, $hasSBOM: HasSBOMInputSpec!, $includes: HasSBOMIncludesInputSpec!) { + ingestHasSBOM(subject: { package: $pkg }, hasSBOM: $hasSBOM, includes: $includes) } mutation HasSBOMArtifact( $artifact: ArtifactInputSpec! $hasSBOM: HasSBOMInputSpec! + $includes: HasSBOMIncludesInputSpec! ) { - ingestHasSBOM(subject: { artifact: $artifact }, hasSBOM: $hasSBOM) + ingestHasSBOM(subject: { artifact: $artifact }, hasSBOM: $hasSBOM, includes: $includes) } # Defines the GraphQL operations to bulk ingest hasSBOM information into GUAC -mutation HasSBOMPkgs($pkgs: [PkgInputSpec!]!, $hasSBOMs: [HasSBOMInputSpec!]!) { - ingestHasSBOMs(subjects: { packages: $pkgs }, hasSBOMs: $hasSBOMs) +mutation HasSBOMPkgs($pkgs: [PkgInputSpec!]!, $hasSBOMs: [HasSBOMInputSpec!]!, $includes: [HasSBOMIncludesInputSpec!]!) { + ingestHasSBOMs(subjects: { packages: $pkgs }, hasSBOMs: $hasSBOMs, includes: $includes) } mutation HasSBOMArtifacts( $artifacts: [ArtifactInputSpec!]! $hasSBOMs: [HasSBOMInputSpec!]! + $includes: [HasSBOMIncludesInputSpec!]! ) { - ingestHasSBOMs(subjects: { artifacts: $artifacts }, hasSBOMs: $hasSBOMs) + ingestHasSBOMs(subjects: { artifacts: $artifacts }, hasSBOMs: $hasSBOMs, includes: $includes) } diff --git a/pkg/assembler/clients/operations/trees.graphql b/pkg/assembler/clients/operations/trees.graphql index 866004363aa..c9aeba03a33 100644 --- a/pkg/assembler/clients/operations/trees.graphql +++ b/pkg/assembler/clients/operations/trees.graphql @@ -273,6 +273,21 @@ fragment AllHasSBOMTree on HasSBOM { origin collector knownSince + includedSoftware { + __typename + ... on Artifact { + ...AllArtifactTree + } + ... on Package { + ...AllPkgTree + } + } + includedDependencies { + ...AllIsDependencyTree + } + includedOccurrences { + ...AllIsOccurrencesTree + } } fragment AllHasSourceAt on HasSourceAt { diff --git a/pkg/assembler/graphql/README.md b/pkg/assembler/graphql/README.md index 03e8a3fe3bd..9aa69b4c1db 100644 --- a/pkg/assembler/graphql/README.md +++ b/pkg/assembler/graphql/README.md @@ -31,3 +31,7 @@ project. ## GraphQL Examples - `examples`: queries used to test the backend, from the playground + +## GraphQL Helpers + +- `helpers`: utility functions used within the graphql resolvers \ No newline at end of file diff --git a/pkg/assembler/graphql/generated/artifact.generated.go b/pkg/assembler/graphql/generated/artifact.generated.go index 1e9ec2c04e9..c8ccd905159 100644 --- a/pkg/assembler/graphql/generated/artifact.generated.go +++ b/pkg/assembler/graphql/generated/artifact.generated.go @@ -37,8 +37,8 @@ type MutationResolver interface { IngestCertifyVulns(ctx context.Context, pkgs []*model.PkgInputSpec, vulnerabilities []*model.VulnerabilityInputSpec, certifyVulns []*model.ScanMetadataInput) ([]string, error) IngestPointOfContact(ctx context.Context, subject model.PackageSourceOrArtifactInput, pkgMatchType model.MatchFlags, pointOfContact model.PointOfContactInputSpec) (string, error) IngestPointOfContacts(ctx context.Context, subjects model.PackageSourceOrArtifactInputs, pkgMatchType model.MatchFlags, pointOfContacts []*model.PointOfContactInputSpec) ([]string, error) - IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec) (string, error) - IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) ([]string, error) + IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec, includes model.HasSBOMIncludesInputSpec) (string, error) + IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec, includes []*model.HasSBOMIncludesInputSpec) ([]string, error) IngestSlsa(ctx context.Context, subject model.ArtifactInputSpec, builtFrom []*model.ArtifactInputSpec, builtBy model.BuilderInputSpec, slsa model.SLSAInputSpec) (string, error) IngestSLSAs(ctx context.Context, subjects []*model.ArtifactInputSpec, builtFromList [][]*model.ArtifactInputSpec, builtByList []*model.BuilderInputSpec, slsaList []*model.SLSAInputSpec) ([]string, error) IngestHasSourceAt(ctx context.Context, pkg model.PkgInputSpec, pkgMatchType model.MatchFlags, source model.SourceInputSpec, hasSourceAt model.HasSourceAtInputSpec) (string, error) @@ -638,6 +638,15 @@ func (ec *executionContext) field_Mutation_ingestHasSBOM_args(ctx context.Contex } } args["hasSBOM"] = arg1 + var arg2 model.HasSBOMIncludesInputSpec + if tmp, ok := rawArgs["includes"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includes")) + arg2, err = ec.unmarshalNHasSBOMIncludesInputSpec2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐHasSBOMIncludesInputSpec(ctx, tmp) + if err != nil { + return nil, err + } + } + args["includes"] = arg2 return args, nil } @@ -662,6 +671,15 @@ func (ec *executionContext) field_Mutation_ingestHasSBOMs_args(ctx context.Conte } } args["hasSBOMs"] = arg1 + var arg2 []*model.HasSBOMIncludesInputSpec + if tmp, ok := rawArgs["includes"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includes")) + arg2, err = ec.unmarshalNHasSBOMIncludesInputSpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐHasSBOMIncludesInputSpecᚄ(ctx, tmp) + if err != nil { + return nil, err + } + } + args["includes"] = arg2 return args, nil } @@ -3036,7 +3054,7 @@ func (ec *executionContext) _Mutation_ingestHasSBOM(ctx context.Context, field g }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().IngestHasSbom(rctx, fc.Args["subject"].(model.PackageOrArtifactInput), fc.Args["hasSBOM"].(model.HasSBOMInputSpec)) + return ec.resolvers.Mutation().IngestHasSbom(rctx, fc.Args["subject"].(model.PackageOrArtifactInput), fc.Args["hasSBOM"].(model.HasSBOMInputSpec), fc.Args["includes"].(model.HasSBOMIncludesInputSpec)) }) if err != nil { ec.Error(ctx, err) @@ -3091,7 +3109,7 @@ func (ec *executionContext) _Mutation_ingestHasSBOMs(ctx context.Context, field }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().IngestHasSBOMs(rctx, fc.Args["subjects"].(model.PackageOrArtifactInputs), fc.Args["hasSBOMs"].([]*model.HasSBOMInputSpec)) + return ec.resolvers.Mutation().IngestHasSBOMs(rctx, fc.Args["subjects"].(model.PackageOrArtifactInputs), fc.Args["hasSBOMs"].([]*model.HasSBOMInputSpec), fc.Args["includes"].([]*model.HasSBOMIncludesInputSpec)) }) if err != nil { ec.Error(ctx, err) @@ -5238,6 +5256,12 @@ func (ec *executionContext) fieldContext_Query_HasSBOM(ctx context.Context, fiel return ec.fieldContext_HasSBOM_collector(ctx, field) case "knownSince": return ec.fieldContext_HasSBOM_knownSince(ctx, field) + case "includedSoftware": + return ec.fieldContext_HasSBOM_includedSoftware(ctx, field) + case "includedDependencies": + return ec.fieldContext_HasSBOM_includedDependencies(ctx, field) + case "includedOccurrences": + return ec.fieldContext_HasSBOM_includedOccurrences(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type HasSBOM", field.Name) }, diff --git a/pkg/assembler/graphql/generated/certifyVEXStatement.generated.go b/pkg/assembler/graphql/generated/certifyVEXStatement.generated.go index 766f2e3e77e..696165ce915 100644 --- a/pkg/assembler/graphql/generated/certifyVEXStatement.generated.go +++ b/pkg/assembler/graphql/generated/certifyVEXStatement.generated.go @@ -977,6 +977,50 @@ func (ec *executionContext) marshalNPackageOrArtifact2githubᚗcomᚋguacsecᚋg return ec._PackageOrArtifact(ctx, sel, v) } +func (ec *executionContext) marshalNPackageOrArtifact2ᚕgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPackageOrArtifactᚄ(ctx context.Context, sel ast.SelectionSet, v []model.PackageOrArtifact) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNPackageOrArtifact2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPackageOrArtifact(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) unmarshalNPackageOrArtifactInput2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPackageOrArtifactInput(ctx context.Context, v interface{}) (model.PackageOrArtifactInput, error) { res, err := ec.unmarshalInputPackageOrArtifactInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -987,6 +1031,23 @@ func (ec *executionContext) unmarshalNPackageOrArtifactInputs2githubᚗcomᚋgua return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalNPackageOrArtifactSpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPackageOrArtifactSpec(ctx context.Context, v interface{}) ([]*model.PackageOrArtifactSpec, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]*model.PackageOrArtifactSpec, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalOPackageOrArtifactSpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPackageOrArtifactSpec(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + func (ec *executionContext) unmarshalNVexJustification2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐVexJustification(ctx context.Context, v interface{}) (model.VexJustification, error) { var res model.VexJustification err := res.UnmarshalGQL(v) diff --git a/pkg/assembler/graphql/generated/hasSBOM.generated.go b/pkg/assembler/graphql/generated/hasSBOM.generated.go index 529f4d6fbff..160d15e11ab 100644 --- a/pkg/assembler/graphql/generated/hasSBOM.generated.go +++ b/pkg/assembler/graphql/generated/hasSBOM.generated.go @@ -5,6 +5,7 @@ package generated import ( "context" "errors" + "fmt" "strconv" "sync" "sync/atomic" @@ -425,10 +426,221 @@ func (ec *executionContext) fieldContext_HasSBOM_knownSince(ctx context.Context, return fc, nil } +func (ec *executionContext) _HasSBOM_includedSoftware(ctx context.Context, field graphql.CollectedField, obj *model.HasSbom) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_HasSBOM_includedSoftware(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IncludedSoftware, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]model.PackageOrArtifact) + fc.Result = res + return ec.marshalNPackageOrArtifact2ᚕgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPackageOrArtifactᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_HasSBOM_includedSoftware(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "HasSBOM", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type PackageOrArtifact does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _HasSBOM_includedDependencies(ctx context.Context, field graphql.CollectedField, obj *model.HasSbom) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_HasSBOM_includedDependencies(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IncludedDependencies, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.IsDependency) + fc.Result = res + return ec.marshalNIsDependency2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsDependencyᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_HasSBOM_includedDependencies(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "HasSBOM", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_IsDependency_id(ctx, field) + case "package": + return ec.fieldContext_IsDependency_package(ctx, field) + case "dependencyPackage": + return ec.fieldContext_IsDependency_dependencyPackage(ctx, field) + case "versionRange": + return ec.fieldContext_IsDependency_versionRange(ctx, field) + case "dependencyType": + return ec.fieldContext_IsDependency_dependencyType(ctx, field) + case "justification": + return ec.fieldContext_IsDependency_justification(ctx, field) + case "origin": + return ec.fieldContext_IsDependency_origin(ctx, field) + case "collector": + return ec.fieldContext_IsDependency_collector(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type IsDependency", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _HasSBOM_includedOccurrences(ctx context.Context, field graphql.CollectedField, obj *model.HasSbom) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_HasSBOM_includedOccurrences(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IncludedOccurrences, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.IsOccurrence) + fc.Result = res + return ec.marshalNIsOccurrence2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsOccurrenceᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_HasSBOM_includedOccurrences(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "HasSBOM", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_IsOccurrence_id(ctx, field) + case "subject": + return ec.fieldContext_IsOccurrence_subject(ctx, field) + case "artifact": + return ec.fieldContext_IsOccurrence_artifact(ctx, field) + case "justification": + return ec.fieldContext_IsOccurrence_justification(ctx, field) + case "origin": + return ec.fieldContext_IsOccurrence_origin(ctx, field) + case "collector": + return ec.fieldContext_IsOccurrence_collector(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type IsOccurrence", field.Name) + }, + } + return fc, nil +} + // endregion **************************** field.gotpl ***************************** // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputHasSBOMIncludesInputSpec(ctx context.Context, obj interface{}) (model.HasSBOMIncludesInputSpec, error) { + var it model.HasSBOMIncludesInputSpec + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"software", "dependencies", "occurrences"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "software": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("software")) + data, err := ec.unmarshalNID2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + it.Software = data + case "dependencies": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dependencies")) + data, err := ec.unmarshalNID2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + it.Dependencies = data + case "occurrences": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("occurrences")) + data, err := ec.unmarshalNID2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + it.Occurrences = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputHasSBOMInputSpec(ctx context.Context, obj interface{}) (model.HasSBOMInputSpec, error) { var it model.HasSBOMInputSpec asMap := map[string]interface{}{} @@ -519,7 +731,7 @@ func (ec *executionContext) unmarshalInputHasSBOMSpec(ctx context.Context, obj i asMap[k] = v } - fieldsInOrder := [...]string{"id", "subject", "uri", "algorithm", "digest", "downloadLocation", "origin", "collector", "knownSince"} + fieldsInOrder := [...]string{"id", "subject", "uri", "algorithm", "digest", "downloadLocation", "origin", "collector", "knownSince", "includedSoftware", "includedDependencies", "includedOccurrences"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -607,6 +819,33 @@ func (ec *executionContext) unmarshalInputHasSBOMSpec(ctx context.Context, obj i return it, err } it.KnownSince = data + case "includedSoftware": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includedSoftware")) + data, err := ec.unmarshalNPackageOrArtifactSpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPackageOrArtifactSpec(ctx, v) + if err != nil { + return it, err + } + it.IncludedSoftware = data + case "includedDependencies": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includedDependencies")) + data, err := ec.unmarshalNIsDependencySpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsDependencySpec(ctx, v) + if err != nil { + return it, err + } + it.IncludedDependencies = data + case "includedOccurrences": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includedOccurrences")) + data, err := ec.unmarshalNIsOccurrenceSpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsOccurrenceSpec(ctx, v) + if err != nil { + return it, err + } + it.IncludedOccurrences = data } } @@ -677,6 +916,21 @@ func (ec *executionContext) _HasSBOM(ctx context.Context, sel ast.SelectionSet, if out.Values[i] == graphql.Null { out.Invalids++ } + case "includedSoftware": + out.Values[i] = ec._HasSBOM_includedSoftware(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "includedDependencies": + out.Values[i] = ec._HasSBOM_includedDependencies(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "includedOccurrences": + out.Values[i] = ec._HasSBOM_includedOccurrences(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -758,6 +1012,33 @@ func (ec *executionContext) marshalNHasSBOM2ᚖgithubᚗcomᚋguacsecᚋguacᚋp return ec._HasSBOM(ctx, sel, v) } +func (ec *executionContext) unmarshalNHasSBOMIncludesInputSpec2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐHasSBOMIncludesInputSpec(ctx context.Context, v interface{}) (model.HasSBOMIncludesInputSpec, error) { + res, err := ec.unmarshalInputHasSBOMIncludesInputSpec(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalNHasSBOMIncludesInputSpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐHasSBOMIncludesInputSpecᚄ(ctx context.Context, v interface{}) ([]*model.HasSBOMIncludesInputSpec, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]*model.HasSBOMIncludesInputSpec, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNHasSBOMIncludesInputSpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐHasSBOMIncludesInputSpec(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) unmarshalNHasSBOMIncludesInputSpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐHasSBOMIncludesInputSpec(ctx context.Context, v interface{}) (*model.HasSBOMIncludesInputSpec, error) { + res, err := ec.unmarshalInputHasSBOMIncludesInputSpec(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNHasSBOMInputSpec2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐHasSBOMInputSpec(ctx context.Context, v interface{}) (model.HasSBOMInputSpec, error) { res, err := ec.unmarshalInputHasSBOMInputSpec(ctx, v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/pkg/assembler/graphql/generated/isDependency.generated.go b/pkg/assembler/graphql/generated/isDependency.generated.go index 78679f4f88c..e8b3af2e18b 100644 --- a/pkg/assembler/graphql/generated/isDependency.generated.go +++ b/pkg/assembler/graphql/generated/isDependency.generated.go @@ -740,6 +740,23 @@ func (ec *executionContext) unmarshalNIsDependencySpec2githubᚗcomᚋguacsecᚋ return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalNIsDependencySpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsDependencySpec(ctx context.Context, v interface{}) ([]*model.IsDependencySpec, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]*model.IsDependencySpec, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalOIsDependencySpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsDependencySpec(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + func (ec *executionContext) unmarshalODependencyType2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐDependencyType(ctx context.Context, v interface{}) (*model.DependencyType, error) { if v == nil { return nil, nil @@ -756,4 +773,12 @@ func (ec *executionContext) marshalODependencyType2ᚖgithubᚗcomᚋguacsecᚋg return v } +func (ec *executionContext) unmarshalOIsDependencySpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsDependencySpec(ctx context.Context, v interface{}) (*model.IsDependencySpec, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputIsDependencySpec(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + // endregion ***************************** type.gotpl ***************************** diff --git a/pkg/assembler/graphql/generated/isOccurrence.generated.go b/pkg/assembler/graphql/generated/isOccurrence.generated.go index c49ba693358..f9b4f6d21d0 100644 --- a/pkg/assembler/graphql/generated/isOccurrence.generated.go +++ b/pkg/assembler/graphql/generated/isOccurrence.generated.go @@ -725,6 +725,23 @@ func (ec *executionContext) unmarshalNIsOccurrenceSpec2githubᚗcomᚋguacsecᚋ return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalNIsOccurrenceSpec2ᚕᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsOccurrenceSpec(ctx context.Context, v interface{}) ([]*model.IsOccurrenceSpec, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]*model.IsOccurrenceSpec, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalOIsOccurrenceSpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsOccurrenceSpec(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + func (ec *executionContext) marshalNPackageOrSource2githubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPackageOrSource(ctx context.Context, sel ast.SelectionSet, v model.PackageOrSource) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -745,6 +762,14 @@ func (ec *executionContext) unmarshalNPackageOrSourceInputs2githubᚗcomᚋguacs return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOIsOccurrenceSpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐIsOccurrenceSpec(ctx context.Context, v interface{}) (*model.IsOccurrenceSpec, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputIsOccurrenceSpec(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOPackageOrSourceSpec2ᚖgithubᚗcomᚋguacsecᚋguacᚋpkgᚋassemblerᚋgraphqlᚋmodelᚐPackageOrSourceSpec(ctx context.Context, v interface{}) (*model.PackageOrSourceSpec, error) { if v == nil { return nil, nil diff --git a/pkg/assembler/graphql/generated/root_.generated.go b/pkg/assembler/graphql/generated/root_.generated.go index 99da8d1ff74..b2083aa9a3a 100644 --- a/pkg/assembler/graphql/generated/root_.generated.go +++ b/pkg/assembler/graphql/generated/root_.generated.go @@ -120,15 +120,18 @@ type ComplexityRoot struct { } HasSBOM struct { - Algorithm func(childComplexity int) int - Collector func(childComplexity int) int - Digest func(childComplexity int) int - DownloadLocation func(childComplexity int) int - ID func(childComplexity int) int - KnownSince func(childComplexity int) int - Origin func(childComplexity int) int - Subject func(childComplexity int) int - URI func(childComplexity int) int + Algorithm func(childComplexity int) int + Collector func(childComplexity int) int + Digest func(childComplexity int) int + DownloadLocation func(childComplexity int) int + ID func(childComplexity int) int + IncludedDependencies func(childComplexity int) int + IncludedOccurrences func(childComplexity int) int + IncludedSoftware func(childComplexity int) int + KnownSince func(childComplexity int) int + Origin func(childComplexity int) int + Subject func(childComplexity int) int + URI func(childComplexity int) int } HasSLSA struct { @@ -200,8 +203,8 @@ type ComplexityRoot struct { IngestDependencies func(childComplexity int, pkgs []*model.PkgInputSpec, depPkgs []*model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependencies []*model.IsDependencyInputSpec) int IngestDependency func(childComplexity int, pkg model.PkgInputSpec, depPkg model.PkgInputSpec, depPkgMatchType model.MatchFlags, dependency model.IsDependencyInputSpec) int IngestHasMetadata func(childComplexity int, subject model.PackageSourceOrArtifactInput, pkgMatchType model.MatchFlags, hasMetadata model.HasMetadataInputSpec) int - IngestHasSBOMs func(childComplexity int, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) int - IngestHasSbom func(childComplexity int, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec) int + IngestHasSBOMs func(childComplexity int, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec, includes []*model.HasSBOMIncludesInputSpec) int + IngestHasSbom func(childComplexity int, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec, includes model.HasSBOMIncludesInputSpec) int IngestHasSourceAt func(childComplexity int, pkg model.PkgInputSpec, pkgMatchType model.MatchFlags, source model.SourceInputSpec, hasSourceAt model.HasSourceAtInputSpec) int IngestHasSourceAts func(childComplexity int, pkgs []*model.PkgInputSpec, pkgMatchType model.MatchFlags, sources []*model.SourceInputSpec, hasSourceAts []*model.HasSourceAtInputSpec) int IngestHashEqual func(childComplexity int, artifact model.ArtifactInputSpec, otherArtifact model.ArtifactInputSpec, hashEqual model.HashEqualInputSpec) int @@ -823,6 +826,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.HasSBOM.ID(childComplexity), true + case "HasSBOM.includedDependencies": + if e.complexity.HasSBOM.IncludedDependencies == nil { + break + } + + return e.complexity.HasSBOM.IncludedDependencies(childComplexity), true + + case "HasSBOM.includedOccurrences": + if e.complexity.HasSBOM.IncludedOccurrences == nil { + break + } + + return e.complexity.HasSBOM.IncludedOccurrences(childComplexity), true + + case "HasSBOM.includedSoftware": + if e.complexity.HasSBOM.IncludedSoftware == nil { + break + } + + return e.complexity.HasSBOM.IncludedSoftware(childComplexity), true + case "HasSBOM.knownSince": if e.complexity.HasSBOM.KnownSince == nil { break @@ -1296,7 +1320,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.IngestHasSBOMs(childComplexity, args["subjects"].(model.PackageOrArtifactInputs), args["hasSBOMs"].([]*model.HasSBOMInputSpec)), true + return e.complexity.Mutation.IngestHasSBOMs(childComplexity, args["subjects"].(model.PackageOrArtifactInputs), args["hasSBOMs"].([]*model.HasSBOMInputSpec), args["includes"].([]*model.HasSBOMIncludesInputSpec)), true case "Mutation.ingestHasSBOM": if e.complexity.Mutation.IngestHasSbom == nil { @@ -1308,7 +1332,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.IngestHasSbom(childComplexity, args["subject"].(model.PackageOrArtifactInput), args["hasSBOM"].(model.HasSBOMInputSpec)), true + return e.complexity.Mutation.IngestHasSbom(childComplexity, args["subject"].(model.PackageOrArtifactInput), args["hasSBOM"].(model.HasSBOMInputSpec), args["includes"].(model.HasSBOMIncludesInputSpec)), true case "Mutation.ingestHasSourceAt": if e.complexity.Mutation.IngestHasSourceAt == nil { @@ -2567,6 +2591,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputCertifyVulnSpec, ec.unmarshalInputHasMetadataInputSpec, ec.unmarshalInputHasMetadataSpec, + ec.unmarshalInputHasSBOMIncludesInputSpec, ec.unmarshalInputHasSBOMInputSpec, ec.unmarshalInputHasSBOMSpec, ec.unmarshalInputHasSLSASpec, @@ -3724,6 +3749,12 @@ type HasSBOM { collector: String! "Timestamp for SBOM creation" knownSince: Time! + "Included packages and artifacts" + includedSoftware: [PackageOrArtifact!]! + "Included dependencies" + includedDependencies: [IsDependency!]! + "Included occurrences" + includedOccurrences: [IsOccurrence!]! } """ @@ -3744,9 +3775,18 @@ input HasSBOMSpec { origin: String collector: String knownSince: Time + includedSoftware: [PackageOrArtifactSpec]! + includedDependencies: [IsDependencySpec]! + includedOccurrences: [IsOccurrenceSpec]! +} + +input HasSBOMIncludesInputSpec { + software: [ID!]! + dependencies: [ID!]! + occurrences: [ID!]! } -"HasSBOMInputSpec is the same as HasSBOM but for mutation input." +"HasSBOMInputSpec is similar to HasSBOM but for mutation input." input HasSBOMInputSpec { uri: String! algorithm: String! @@ -3767,11 +3807,13 @@ extend type Mutation { ingestHasSBOM( subject: PackageOrArtifactInput! hasSBOM: HasSBOMInputSpec! + includes: HasSBOMIncludesInputSpec! ): ID! "Bulk ingest that package or artifact has an SBOM. The returned array of IDs can be a an array of empty string." ingestHasSBOMs( subjects: PackageOrArtifactInputs! hasSBOMs: [HasSBOMInputSpec!]! + includes: [HasSBOMIncludesInputSpec!]! ): [ID!]! } `, BuiltIn: false}, @@ -4783,6 +4825,9 @@ enum Edge { HAS_METADATA_SOURCE HAS_SBOM_ARTIFACT HAS_SBOM_PACKAGE + HAS_SBOM_INCLUDED_SOFTWARE + HAS_SBOM_INCLUDED_DEPENDENCIES + HAS_SBOM_INCLUDED_OCCURRENCES HAS_SLSA_BUILT_BY HAS_SLSA_MATERIALS HAS_SLSA_SUBJECT diff --git a/pkg/assembler/graphql/helpers/package.go b/pkg/assembler/graphql/helpers/package.go new file mode 100644 index 00000000000..40d84127e8f --- /dev/null +++ b/pkg/assembler/graphql/helpers/package.go @@ -0,0 +1,38 @@ +// +// Copyright 2023 The GUAC Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "fmt" + + "github.com/guacsec/guac/pkg/assembler/graphql/model" +) + +func GetPackageVersionId(Package *model.Package) (*string, error) { + if Package != nil { + if len(Package.Namespaces) == 1 { + namespace := Package.Namespaces[0] + if len(namespace.Names) == 1 { + name := namespace.Names[0] + if len(name.Versions) == 1 { + version := name.Versions[0] + return &version.ID, nil + } + } + } + } + return nil, fmt.Errorf("could not retrieve ingested PackageVersion from package id %v", Package) +} diff --git a/pkg/assembler/graphql/model/nodes.go b/pkg/assembler/graphql/model/nodes.go index 84aca2b89ef..5e6c2ff1f54 100644 --- a/pkg/assembler/graphql/model/nodes.go +++ b/pkg/assembler/graphql/model/nodes.go @@ -449,11 +449,23 @@ type HasSbom struct { Collector string `json:"collector"` // Timestamp for SBOM creation KnownSince time.Time `json:"knownSince"` + // Included packages and artifacts + IncludedSoftware []PackageOrArtifact `json:"includedSoftware"` + // Included dependencies + IncludedDependencies []*IsDependency `json:"includedDependencies"` + // Included occurrences + IncludedOccurrences []*IsOccurrence `json:"includedOccurrences"` } func (HasSbom) IsNode() {} -// HasSBOMInputSpec is the same as HasSBOM but for mutation input. +type HasSBOMIncludesInputSpec struct { + Software []string `json:"software"` + Dependencies []string `json:"dependencies"` + Occurrences []string `json:"occurrences"` +} + +// HasSBOMInputSpec is similar to HasSBOM but for mutation input. type HasSBOMInputSpec struct { URI string `json:"uri"` Algorithm string `json:"algorithm"` @@ -471,15 +483,18 @@ type HasSBOMInputSpec struct { // If KnownSince is specified, the returned value will be after or equal to the specified time. // Any nodes time that is before KnownSince is excluded. type HasSBOMSpec struct { - ID *string `json:"id,omitempty"` - Subject *PackageOrArtifactSpec `json:"subject,omitempty"` - URI *string `json:"uri,omitempty"` - Algorithm *string `json:"algorithm,omitempty"` - Digest *string `json:"digest,omitempty"` - DownloadLocation *string `json:"downloadLocation,omitempty"` - Origin *string `json:"origin,omitempty"` - Collector *string `json:"collector,omitempty"` - KnownSince *time.Time `json:"knownSince,omitempty"` + ID *string `json:"id,omitempty"` + Subject *PackageOrArtifactSpec `json:"subject,omitempty"` + URI *string `json:"uri,omitempty"` + Algorithm *string `json:"algorithm,omitempty"` + Digest *string `json:"digest,omitempty"` + DownloadLocation *string `json:"downloadLocation,omitempty"` + Origin *string `json:"origin,omitempty"` + Collector *string `json:"collector,omitempty"` + KnownSince *time.Time `json:"knownSince,omitempty"` + IncludedSoftware []*PackageOrArtifactSpec `json:"includedSoftware"` + IncludedDependencies []*IsDependencySpec `json:"includedDependencies"` + IncludedOccurrences []*IsOccurrenceSpec `json:"includedOccurrences"` } // HasSLSA records that a subject node has a SLSA attestation. @@ -1651,6 +1666,9 @@ const ( EdgeHasMetadataSource Edge = "HAS_METADATA_SOURCE" EdgeHasSbomArtifact Edge = "HAS_SBOM_ARTIFACT" EdgeHasSbomPackage Edge = "HAS_SBOM_PACKAGE" + EdgeHasSbomIncludedSoftware Edge = "HAS_SBOM_INCLUDED_SOFTWARE" + EdgeHasSbomIncludedDependencies Edge = "HAS_SBOM_INCLUDED_DEPENDENCIES" + EdgeHasSbomIncludedOccurrences Edge = "HAS_SBOM_INCLUDED_OCCURRENCES" EdgeHasSlsaBuiltBy Edge = "HAS_SLSA_BUILT_BY" EdgeHasSlsaMaterials Edge = "HAS_SLSA_MATERIALS" EdgeHasSlsaSubject Edge = "HAS_SLSA_SUBJECT" @@ -1725,6 +1743,9 @@ var AllEdge = []Edge{ EdgeHasMetadataSource, EdgeHasSbomArtifact, EdgeHasSbomPackage, + EdgeHasSbomIncludedSoftware, + EdgeHasSbomIncludedDependencies, + EdgeHasSbomIncludedOccurrences, EdgeHasSlsaBuiltBy, EdgeHasSlsaMaterials, EdgeHasSlsaSubject, @@ -1744,7 +1765,7 @@ var AllEdge = []Edge{ func (e Edge) IsValid() bool { switch e { - case EdgeArtifactCertifyBad, EdgeArtifactCertifyGood, EdgeArtifactCertifyVexStatement, EdgeArtifactHashEqual, EdgeArtifactHasMetadata, EdgeArtifactHasSbom, EdgeArtifactHasSlsa, EdgeArtifactIsOccurrence, EdgeArtifactPointOfContact, EdgeBuilderHasSlsa, EdgeLicenseCertifyLegal, EdgePackageCertifyBad, EdgePackageCertifyGood, EdgePackageCertifyLegal, EdgePackageCertifyVexStatement, EdgePackageCertifyVuln, EdgePackageHasMetadata, EdgePackageHasSbom, EdgePackageHasSourceAt, EdgePackageIsDependency, EdgePackageIsOccurrence, EdgePackagePkgEqual, EdgePackagePointOfContact, EdgeSourceCertifyBad, EdgeSourceCertifyGood, EdgeSourceCertifyLegal, EdgeSourceCertifyScorecard, EdgeSourceHasMetadata, EdgeSourceHasSourceAt, EdgeSourceIsOccurrence, EdgeSourcePointOfContact, EdgeVulnerabilityCertifyVexStatement, EdgeVulnerabilityCertifyVuln, EdgeVulnerabilityVulnEqual, EdgeVulnerabilityVulnMetadata, EdgeCertifyBadArtifact, EdgeCertifyBadPackage, EdgeCertifyBadSource, EdgeCertifyGoodArtifact, EdgeCertifyGoodPackage, EdgeCertifyGoodSource, EdgeCertifyLegalLicense, EdgeCertifyLegalPackage, EdgeCertifyLegalSource, EdgeCertifyScorecardSource, EdgeCertifyVexStatementArtifact, EdgeCertifyVexStatementPackage, EdgeCertifyVexStatementVulnerability, EdgeCertifyVulnPackage, EdgeCertifyVulnVulnerability, EdgeHashEqualArtifact, EdgeHasMetadataArtifact, EdgeHasMetadataPackage, EdgeHasMetadataSource, EdgeHasSbomArtifact, EdgeHasSbomPackage, EdgeHasSlsaBuiltBy, EdgeHasSlsaMaterials, EdgeHasSlsaSubject, EdgeHasSourceAtPackage, EdgeHasSourceAtSource, EdgeIsDependencyPackage, EdgeIsOccurrenceArtifact, EdgeIsOccurrencePackage, EdgeIsOccurrenceSource, EdgePkgEqualPackage, EdgePointOfContactArtifact, EdgePointOfContactPackage, EdgePointOfContactSource, EdgeVulnEqualVulnerability, EdgeVulnMetadataVulnerability: + case EdgeArtifactCertifyBad, EdgeArtifactCertifyGood, EdgeArtifactCertifyVexStatement, EdgeArtifactHashEqual, EdgeArtifactHasMetadata, EdgeArtifactHasSbom, EdgeArtifactHasSlsa, EdgeArtifactIsOccurrence, EdgeArtifactPointOfContact, EdgeBuilderHasSlsa, EdgeLicenseCertifyLegal, EdgePackageCertifyBad, EdgePackageCertifyGood, EdgePackageCertifyLegal, EdgePackageCertifyVexStatement, EdgePackageCertifyVuln, EdgePackageHasMetadata, EdgePackageHasSbom, EdgePackageHasSourceAt, EdgePackageIsDependency, EdgePackageIsOccurrence, EdgePackagePkgEqual, EdgePackagePointOfContact, EdgeSourceCertifyBad, EdgeSourceCertifyGood, EdgeSourceCertifyLegal, EdgeSourceCertifyScorecard, EdgeSourceHasMetadata, EdgeSourceHasSourceAt, EdgeSourceIsOccurrence, EdgeSourcePointOfContact, EdgeVulnerabilityCertifyVexStatement, EdgeVulnerabilityCertifyVuln, EdgeVulnerabilityVulnEqual, EdgeVulnerabilityVulnMetadata, EdgeCertifyBadArtifact, EdgeCertifyBadPackage, EdgeCertifyBadSource, EdgeCertifyGoodArtifact, EdgeCertifyGoodPackage, EdgeCertifyGoodSource, EdgeCertifyLegalLicense, EdgeCertifyLegalPackage, EdgeCertifyLegalSource, EdgeCertifyScorecardSource, EdgeCertifyVexStatementArtifact, EdgeCertifyVexStatementPackage, EdgeCertifyVexStatementVulnerability, EdgeCertifyVulnPackage, EdgeCertifyVulnVulnerability, EdgeHashEqualArtifact, EdgeHasMetadataArtifact, EdgeHasMetadataPackage, EdgeHasMetadataSource, EdgeHasSbomArtifact, EdgeHasSbomPackage, EdgeHasSbomIncludedSoftware, EdgeHasSbomIncludedDependencies, EdgeHasSbomIncludedOccurrences, EdgeHasSlsaBuiltBy, EdgeHasSlsaMaterials, EdgeHasSlsaSubject, EdgeHasSourceAtPackage, EdgeHasSourceAtSource, EdgeIsDependencyPackage, EdgeIsOccurrenceArtifact, EdgeIsOccurrencePackage, EdgeIsOccurrenceSource, EdgePkgEqualPackage, EdgePointOfContactArtifact, EdgePointOfContactPackage, EdgePointOfContactSource, EdgeVulnEqualVulnerability, EdgeVulnMetadataVulnerability: return true } return false diff --git a/pkg/assembler/graphql/resolvers/hasSBOM.resolvers.go b/pkg/assembler/graphql/resolvers/hasSBOM.resolvers.go index 7c8d2f37fc1..174a46456c7 100644 --- a/pkg/assembler/graphql/resolvers/hasSBOM.resolvers.go +++ b/pkg/assembler/graphql/resolvers/hasSBOM.resolvers.go @@ -13,7 +13,7 @@ import ( ) // IngestHasSbom is the resolver for the ingestHasSBOM field. -func (r *mutationResolver) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec) (string, error) { +func (r *mutationResolver) IngestHasSbom(ctx context.Context, subject model.PackageOrArtifactInput, hasSbom model.HasSBOMInputSpec, includes model.HasSBOMIncludesInputSpec) (string, error) { funcName := "IngestHasSbom" if err := helper.ValidatePackageOrArtifactInput(&subject, funcName); err != nil { return "", gqlerror.Errorf("%v :: %s", funcName, err) @@ -22,7 +22,7 @@ func (r *mutationResolver) IngestHasSbom(ctx context.Context, subject model.Pack return "", gqlerror.Errorf("hasSbom.KnownSince is a zero time") } - ingestedHasSbom, err := r.Backend.IngestHasSbom(ctx, subject, hasSbom) + ingestedHasSbom, err := r.Backend.IngestHasSbom(ctx, subject, hasSbom, includes) if err != nil { return "", err } @@ -30,7 +30,7 @@ func (r *mutationResolver) IngestHasSbom(ctx context.Context, subject model.Pack } // IngestHasSBOMs is the resolver for the ingestHasSBOMs field. -func (r *mutationResolver) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec) ([]string, error) { +func (r *mutationResolver) IngestHasSBOMs(ctx context.Context, subjects model.PackageOrArtifactInputs, hasSBOMs []*model.HasSBOMInputSpec, includes []*model.HasSBOMIncludesInputSpec) ([]string, error) { funcName := "IngestHasSBOMs" valuesDefined := 0 ingestedHasSBOMSIDS := []string{} @@ -56,7 +56,10 @@ func (r *mutationResolver) IngestHasSBOMs(ctx context.Context, subjects model.Pa } } - ingestedHasSBOMs, err := r.Backend.IngestHasSBOMs(ctx, subjects, hasSBOMs) + if len(hasSBOMs) != len(includes) { + return ingestedHasSBOMSIDS, gqlerror.Errorf("%v :: uneven hasSBOMs and includes for ingestion", funcName) + } + ingestedHasSBOMs, err := r.Backend.IngestHasSBOMs(ctx, subjects, hasSBOMs, includes) if err == nil { for _, hasSBOM := range ingestedHasSBOMs { ingestedHasSBOMSIDS = append(ingestedHasSBOMSIDS, hasSBOM.ID) diff --git a/pkg/assembler/graphql/resolvers/hasSBOM.resolvers_test.go b/pkg/assembler/graphql/resolvers/hasSBOM.resolvers_test.go index a1feed22449..ae10167ae5e 100644 --- a/pkg/assembler/graphql/resolvers/hasSBOM.resolvers_test.go +++ b/pkg/assembler/graphql/resolvers/hasSBOM.resolvers_test.go @@ -31,6 +31,7 @@ func TestIngestHasSbom(t *testing.T) { type call struct { Sub model.PackageOrArtifactInput HS *model.HasSBOMInputSpec + Inc *model.HasSBOMIncludesInputSpec } tests := []struct { Name string @@ -48,6 +49,7 @@ func TestIngestHasSbom(t *testing.T) { HS: &model.HasSBOMInputSpec{ DownloadLocation: "location one", }, + Inc: &model.HasSBOMIncludesInputSpec{}, }, }, ExpIngestErr: true, @@ -63,6 +65,7 @@ func TestIngestHasSbom(t *testing.T) { URI: "test uri", KnownSince: ZeroTime, }, + Inc: &model.HasSBOMIncludesInputSpec{}, }, }, ExpIngestErr: false, @@ -81,10 +84,10 @@ func TestIngestHasSbom(t *testing.T) { } b. EXPECT(). - IngestHasSbom(ctx, o.Sub, *o.HS). + IngestHasSbom(ctx, o.Sub, *o.HS, *o.Inc). Return(&model.HasSbom{ID: "a"}, nil). Times(times) - _, err := r.Mutation().IngestHasSbom(ctx, o.Sub, *o.HS) + _, err := r.Mutation().IngestHasSbom(ctx, o.Sub, *o.HS, *o.Inc) if (err != nil) != test.ExpIngestErr { t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } @@ -100,6 +103,7 @@ func TestIngestHasSBOMs(t *testing.T) { type call struct { Sub model.PackageOrArtifactInputs HS []*model.HasSBOMInputSpec + Inc []*model.HasSBOMIncludesInputSpec } tests := []struct { Name string @@ -107,7 +111,7 @@ func TestIngestHasSBOMs(t *testing.T) { ExpIngestErr bool }{ { - Name: "Ingest with two packages and one HasSbom", + Name: "Ingest with two packages, one HasSbom and one includes", Calls: []call{ { Sub: model.PackageOrArtifactInputs{ @@ -118,12 +122,13 @@ func TestIngestHasSBOMs(t *testing.T) { URI: "test uri", }, }, + Inc: []*model.HasSBOMIncludesInputSpec{{}}, }, }, ExpIngestErr: true, }, { - Name: "Ingest with two artifacts and one HasSbom", + Name: "Ingest with two artifacts, one HasSbom and one includes", Calls: []call{ { Sub: model.PackageOrArtifactInputs{ @@ -134,6 +139,24 @@ func TestIngestHasSBOMs(t *testing.T) { URI: "test uri", }, }, + Inc: []*model.HasSBOMIncludesInputSpec{{}}, + }, + }, + ExpIngestErr: true, + }, + { + Name: "Ingest with one package, one HasSbom and two includes", + Calls: []call{ + { + Sub: model.PackageOrArtifactInputs{ + Packages: []*model.PkgInputSpec{testdata.P1, testdata.P2}, + }, + HS: []*model.HasSBOMInputSpec{ + { + URI: "test uri", + }, + }, + Inc: []*model.HasSBOMIncludesInputSpec{{}, {}}, }, }, ExpIngestErr: true, @@ -152,6 +175,7 @@ func TestIngestHasSBOMs(t *testing.T) { KnownSince: ZeroTime, }, }, + Inc: []*model.HasSBOMIncludesInputSpec{{}}, }, }, ExpIngestErr: true, @@ -169,6 +193,7 @@ func TestIngestHasSBOMs(t *testing.T) { KnownSince: ZeroTime, }, }, + Inc: []*model.HasSBOMIncludesInputSpec{{}}, }, }, }, @@ -186,10 +211,10 @@ func TestIngestHasSBOMs(t *testing.T) { } b. EXPECT(). - IngestHasSBOMs(ctx, o.Sub, o.HS). + IngestHasSBOMs(ctx, o.Sub, o.HS, o.Inc). Return([]*model.HasSbom{{ID: "a"}}, nil). Times(times) - _, err := r.Mutation().IngestHasSBOMs(ctx, o.Sub, o.HS) + _, err := r.Mutation().IngestHasSBOMs(ctx, o.Sub, o.HS, o.Inc) if (err != nil) != test.ExpIngestErr { t.Fatalf("did not get expected ingest error, want: %v, got: %v", test.ExpIngestErr, err) } diff --git a/pkg/assembler/graphql/resolvers/package.resolvers.go b/pkg/assembler/graphql/resolvers/package.resolvers.go index 4334784672f..176c5beae9a 100644 --- a/pkg/assembler/graphql/resolvers/package.resolvers.go +++ b/pkg/assembler/graphql/resolvers/package.resolvers.go @@ -7,28 +7,43 @@ package resolvers import ( "context" + "github.com/guacsec/guac/pkg/assembler/graphql/helpers" "github.com/guacsec/guac/pkg/assembler/graphql/model" ) // IngestPackage is the resolver for the ingestPackage field. func (r *mutationResolver) IngestPackage(ctx context.Context, pkg model.PkgInputSpec) (string, error) { + // Return the id of the PackageVersion which has been ingested + // TODO (knrc) - check why this was originally returning the ID for the Package, since that is not specific enough + ingestedPackage, err := r.Backend.IngestPackage(ctx, pkg) if err != nil { return "", err } - return ingestedPackage.ID, err + if id, err := helpers.GetPackageVersionId(ingestedPackage); err != nil { + return "", err + } else { + return *id, nil + } } // IngestPackages is the resolver for the ingestPackages field. func (r *mutationResolver) IngestPackages(ctx context.Context, pkgs []*model.PkgInputSpec) ([]string, error) { + // TODO (knrc) - check why this was originally returning the IDs of the Packages, since they are not specific enough + // Return the ids of the PackageVersion which have been ingested + ingestedPackages, err := r.Backend.IngestPackages(ctx, pkgs) - ingestedPackagesIDS := []string{} + ingestedPackagesIDs := []string{} if err == nil { for _, Package := range ingestedPackages { - ingestedPackagesIDS = append(ingestedPackagesIDS, Package.ID) + if id, err := helpers.GetPackageVersionId(Package); err != nil { + return nil, err + } else { + ingestedPackagesIDs = append(ingestedPackagesIDs, *id) + } } } - return ingestedPackagesIDS, err + return ingestedPackagesIDs, err } // Packages is the resolver for the packages field. diff --git a/pkg/assembler/graphql/schema/hasSBOM.graphql b/pkg/assembler/graphql/schema/hasSBOM.graphql index 5c1525e30d5..c9a9a953050 100644 --- a/pkg/assembler/graphql/schema/hasSBOM.graphql +++ b/pkg/assembler/graphql/schema/hasSBOM.graphql @@ -35,6 +35,12 @@ type HasSBOM { collector: String! "Timestamp for SBOM creation" knownSince: Time! + "Included packages and artifacts" + includedSoftware: [PackageOrArtifact!]! + "Included dependencies" + includedDependencies: [IsDependency!]! + "Included occurrences" + includedOccurrences: [IsOccurrence!]! } """ @@ -55,9 +61,18 @@ input HasSBOMSpec { origin: String collector: String knownSince: Time + includedSoftware: [PackageOrArtifactSpec]! + includedDependencies: [IsDependencySpec]! + includedOccurrences: [IsOccurrenceSpec]! } -"HasSBOMInputSpec is the same as HasSBOM but for mutation input." +input HasSBOMIncludesInputSpec { + software: [ID!]! + dependencies: [ID!]! + occurrences: [ID!]! +} + +"HasSBOMInputSpec is similar to HasSBOM but for mutation input." input HasSBOMInputSpec { uri: String! algorithm: String! @@ -78,10 +93,12 @@ extend type Mutation { ingestHasSBOM( subject: PackageOrArtifactInput! hasSBOM: HasSBOMInputSpec! + includes: HasSBOMIncludesInputSpec! ): ID! "Bulk ingest that package or artifact has an SBOM. The returned array of IDs can be a an array of empty string." ingestHasSBOMs( subjects: PackageOrArtifactInputs! hasSBOMs: [HasSBOMInputSpec!]! + includes: [HasSBOMIncludesInputSpec!]! ): [ID!]! } diff --git a/pkg/assembler/graphql/schema/path.graphql b/pkg/assembler/graphql/schema/path.graphql index e779d2d781d..bb61299eb7d 100644 --- a/pkg/assembler/graphql/schema/path.graphql +++ b/pkg/assembler/graphql/schema/path.graphql @@ -119,6 +119,9 @@ enum Edge { HAS_METADATA_SOURCE HAS_SBOM_ARTIFACT HAS_SBOM_PACKAGE + HAS_SBOM_INCLUDED_SOFTWARE + HAS_SBOM_INCLUDED_DEPENDENCIES + HAS_SBOM_INCLUDED_OCCURRENCES HAS_SLSA_BUILT_BY HAS_SLSA_MATERIALS HAS_SLSA_SUBJECT