diff --git a/internal/testing/backend/helpers_test.go b/internal/testing/backend/helpers_test.go index 1e454c2ee6..89a13b0303 100644 --- a/internal/testing/backend/helpers_test.go +++ b/internal/testing/backend/helpers_test.go @@ -59,6 +59,7 @@ var commonOpts = cmp.Options{ cmpopts.SortSlices(lessHM), cmpopts.SortSlices(lessPackageOrArtifact), cmpopts.SortSlices(lessSLSAPred), + cmpopts.SortSlices(hasSlsaLess), cmpopts.SortSlices(lessHSA), cmpopts.SortSlices(lessIsDep), cmpopts.SortSlices(lessIsOcc), @@ -586,6 +587,10 @@ func lessSLSAPred(a, b *model.SLSAPredicate) bool { return false } +func hasSlsaLess(a, b *model.HasSlsa) bool { + return cmpArt(a.Subject, b.Subject) < 0 +} + func lessPackageOrArtifact(a, b model.PackageOrArtifact) bool { return cmpPackageOrArtifact(a, b) < 0 } diff --git a/internal/testing/backend/main_test.go b/internal/testing/backend/main_test.go index f712ab1975..725b1f6e84 100644 --- a/internal/testing/backend/main_test.go +++ b/internal/testing/backend/main_test.go @@ -38,40 +38,40 @@ const ( var skipMatrix = map[string]map[string]bool{ // pagination not implemented - "TestArtifacts": {arango: true, memmap: true, redis: true, tikv: true}, - "TestBuilder": {arango: true, memmap: true, redis: true, tikv: true}, - "TestBuilders": {arango: true, memmap: true, redis: true, tikv: true}, - "TestCertifyBad": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestCertifyBads": {arango: true, memmap: true, redis: true, tikv: true}, - "TestCertifyGood": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestCertifyGoods": {arango: true, memmap: true, redis: true, tikv: true}, - "TestLegal": {arango: true, memmap: true, redis: true, tikv: true}, - "TestLegals": {arango: true, memmap: true, redis: true, tikv: true}, - "TestCertifyScorecard": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestScorecards": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestCertifyVulnerability": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestCertifyVulns": {arango: true, memmap: true, redis: true, tikv: true}, - "TestHasMetadata": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestBulkHasMetadata": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestHasSBOMs": {arango: true, memmap: true, redis: true, tikv: true}, - "TestHasSLSA": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestHasSLSAs": {arango: true, memmap: true, redis: true, tikv: true}, - "TestHasSourceAt": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestHasSourceAts": {arango: true, memmap: true, redis: true, tikv: true}, - "TestHashEqual": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestHashEquals": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIsDependencies": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestOccurrences": {arango: true, memmap: true, redis: true, tikv: true}, - "TestLicenses": {arango: true, memmap: true, redis: true, tikv: true}, - "TestLicensesBulk": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestPkgEquals": {arango: true, memmap: true, redis: true, tikv: true}, - "TestPackages": {arango: true, memmap: true, redis: true, tikv: true}, - "TestPointOfContact": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestPointOfContacts": {arango: true, memmap: true, redis: true, tikv: true}, - "TestSources": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestVulnEquals": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestVulnMetadata": {arango: true, memmap: true, redis: true, tikv: true}, - "TestIngestVulnMetadatas": {arango: true, memmap: true, redis: true, tikv: true}, + "TestArtifacts": {arango: true, redis: true, tikv: true}, + "TestBuilder": {arango: true, redis: true, tikv: true}, + "TestBuilders": {arango: true, redis: true, tikv: true}, + "TestCertifyBad": {arango: true, redis: true, tikv: true}, + "TestIngestCertifyBads": {arango: true, redis: true, tikv: true}, + "TestCertifyGood": {arango: true, redis: true, tikv: true}, + "TestIngestCertifyGoods": {arango: true, redis: true, tikv: true}, + "TestLegal": {arango: true, redis: true, tikv: true}, + "TestLegals": {arango: true, redis: true, tikv: true}, + "TestCertifyScorecard": {arango: true, redis: true, tikv: true}, + "TestIngestScorecards": {arango: true, redis: true, tikv: true}, + "TestIngestCertifyVulnerability": {arango: true, redis: true, tikv: true}, + "TestIngestCertifyVulns": {arango: true, redis: true, tikv: true}, + "TestHasMetadata": {arango: true, redis: true, tikv: true}, + "TestIngestBulkHasMetadata": {arango: true, redis: true, tikv: true}, + "TestIngestHasSBOMs": {arango: true, redis: true, tikv: true}, + "TestHasSLSA": {arango: true, redis: true, tikv: true}, + "TestIngestHasSLSAs": {arango: true, redis: true, tikv: true}, + "TestHasSourceAt": {arango: true, redis: true, tikv: true}, + "TestIngestHasSourceAts": {arango: true, redis: true, tikv: true}, + "TestHashEqual": {arango: true, redis: true, tikv: true}, + "TestIngestHashEquals": {arango: true, redis: true, tikv: true}, + "TestIsDependencies": {arango: true, redis: true, tikv: true}, + "TestIngestOccurrences": {arango: true, redis: true, tikv: true}, + "TestLicenses": {arango: true, redis: true, tikv: true}, + "TestLicensesBulk": {arango: true, redis: true, tikv: true}, + "TestIngestPkgEquals": {arango: true, redis: true, tikv: true}, + "TestPackages": {arango: true, redis: true, tikv: true}, + "TestPointOfContact": {arango: true, redis: true, tikv: true}, + "TestIngestPointOfContacts": {arango: true, redis: true, tikv: true}, + "TestSources": {arango: true, redis: true, tikv: true}, + "TestIngestVulnEquals": {arango: true, redis: true, tikv: true}, + "TestIngestVulnMetadata": {arango: true, redis: true, tikv: true}, + "TestIngestVulnMetadatas": {arango: true, redis: true, tikv: true}, // arango fails IncludedOccurrences_-_Valid_Included_ID and IncludedDependencies_-_Valid_Included_ID "TestHasSBOM": {arango: true}, @@ -87,11 +87,11 @@ var skipMatrix = map[string]map[string]bool{ "TestPkgEqual": {arango: true, memmap: true, redis: true, tikv: true}, // keyvalue: Query_on_OSV_and_novuln_(return_nothing_as_not_valid) fails // arango: errors when ID is not found - "TestVulnEqual": {memmap: true, redis: true, tikv: true, arango: true}, + "TestVulnEqual": {redis: true, memmap: true, tikv: true, arango: true}, // arango: errors when ID is not found - "TestVulnerability": {arango: true}, + "TestVulnerability": {arango: true, redis: true, tikv: true}, // redis order issues - "TestVEX": {arango: true, redis: true}, + "TestVEX": {arango: true, redis: true, tikv: true}, // redis order issues "TestVEXBulkIngest": {arango: true, redis: true}, "TestFindSoftware": {redis: true, arango: true}, @@ -105,11 +105,11 @@ type backend interface { } var testBackends = map[string]backend{ - // memmap: newMemMap(), + memmap: newMemMap(), arango: newArango(), - // redis: newRedis(), - ent: newEnt(), - // tikv: newTikv(), + redis: newRedis(), + ent: newEnt(), + tikv: newTikv(), } var currentBackend string diff --git a/pkg/assembler/backends/keyvalue/artifact.go b/pkg/assembler/backends/keyvalue/artifact.go index 65bbd93b26..da54ad4d54 100644 --- a/pkg/assembler/backends/keyvalue/artifact.go +++ b/pkg/assembler/backends/keyvalue/artifact.go @@ -257,60 +257,73 @@ func (c *demoClient) ArtifactsList(ctx context.Context, artifactSpec model.Artif currentPage = true } hasNextPage := false + totalCount := 0 algorithm := strings.ToLower(nilToEmpty(artifactSpec.Algorithm)) digest := strings.ToLower(nilToEmpty(artifactSpec.Digest)) + var done bool scn := c.kv.Keys(artCol) + var artKeys []string + for !done { artKeys, done, err = scn.Scan(ctx) if err != nil { return nil, err } + sort.Strings(artKeys) + + totalCount = len(artKeys) + for i, ak := range artKeys { a, err := byKeykv[*artStruct](ctx, artCol, ak, c) if err != nil { return nil, err } + convArt := c.convArtifact(a) if after != nil && !currentPage { if convArt.ID == *after { currentPage = true - continue - } else { - continue + totalCount = len(artKeys) - (i + 1) } + continue + } + + if convArt == nil { + continue } + + artEdge := createArtifactEdges(algorithm, a, digest, convArt) + + if artEdge == nil { + continue + } + if first != nil { if currentPage && count < *first { - artEdge := createArtifactEdges(algorithm, a, digest, convArt) - if artEdge != nil { - edges = append(edges, artEdge) - count++ - } + edges = append(edges, artEdge) + count++ } // If there are any elements left after the current page we indicate that in the response if count == *first && i < len(artKeys) { hasNextPage = true } } else { - artEdge := createArtifactEdges(algorithm, a, digest, convArt) - if artEdge != nil { - edges = append(edges, artEdge) - count++ - } + edges = append(edges, artEdge) + count++ } } } if len(edges) > 0 { return &model.ArtifactConnection{ - TotalCount: len(artKeys), + TotalCount: totalCount, PageInfo: &model.PageInfo{ HasNextPage: hasNextPage, StartCursor: ptrfrom.String(edges[0].Node.ID), - EndCursor: ptrfrom.String(edges[count-1].Node.ID), + EndCursor: ptrfrom.String(edges[max(count-1, 0)].Node.ID), }, Edges: edges}, nil } else { diff --git a/pkg/assembler/backends/keyvalue/builder.go b/pkg/assembler/backends/keyvalue/builder.go index d74dd45e51..d79737b426 100644 --- a/pkg/assembler/backends/keyvalue/builder.go +++ b/pkg/assembler/backends/keyvalue/builder.go @@ -146,6 +146,8 @@ func (c *demoClient) BuildersList(ctx context.Context, builderSpec model.Builder currentPage = true } hasNextPage := false + totalCount := 0 + var done bool scn := c.kv.Keys(builderCol) @@ -156,19 +158,26 @@ func (c *demoClient) BuildersList(ctx context.Context, builderSpec model.Builder return nil, err } sort.Strings(bKeys) + + totalCount = len(bKeys) + for i, bk := range bKeys { b, err := byKeykv[*builderStruct](ctx, builderCol, bk, c) if err != nil { return nil, err } convBuild := c.convBuilder(b) + + if convBuild == nil { + continue + } + if after != nil && !currentPage { if convBuild.ID == *after { currentPage = true - continue - } else { - continue + totalCount = len(bKeys) - (i + 1) } + continue } if first != nil { if currentPage && count < *first { @@ -193,16 +202,15 @@ func (c *demoClient) BuildersList(ctx context.Context, builderSpec model.Builder } if len(edges) > 0 { return &model.BuilderConnection{ - TotalCount: len(bKeys), + TotalCount: totalCount, PageInfo: &model.PageInfo{ HasNextPage: hasNextPage, StartCursor: ptrfrom.String(edges[0].Node.ID), - EndCursor: ptrfrom.String(edges[count-1].Node.ID), + EndCursor: ptrfrom.String(edges[max(0, count-1)].Node.ID), }, Edges: edges}, nil - } else { - return nil, nil } + return nil, nil } func (c *demoClient) Builders(ctx context.Context, builderSpec *model.BuilderSpec) ([]*model.Builder, error) { diff --git a/pkg/assembler/backends/keyvalue/certifyBad.go b/pkg/assembler/backends/keyvalue/certifyBad.go index dbb79902eb..60259a8459 100644 --- a/pkg/assembler/backends/keyvalue/certifyBad.go +++ b/pkg/assembler/backends/keyvalue/certifyBad.go @@ -18,7 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "time" @@ -192,7 +193,180 @@ func (c *demoClient) ingestCertifyBad(ctx context.Context, subject model.Package // Query CertifyBad func (c *demoClient) CertifyBadList(ctx context.Context, certifyBadSpec model.CertifyBadSpec, after *string, first *int) (*model.CertifyBadConnection, error) { - return nil, fmt.Errorf("not implemented: CertifyBadList") + c.m.RLock() + defer c.m.RUnlock() + + funcName := "CertifyBad" + + if certifyBadSpec.ID != nil { + link, err := byIDkv[*badLink](ctx, *certifyBadSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + foundCertifyBad, err := c.buildCertifyBad(ctx, link, &certifyBadSpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + return &model.CertifyBadConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(foundCertifyBad.ID), + EndCursor: ptrfrom.String(foundCertifyBad.ID), + }, + Edges: []*model.CertifyBadEdge{ + { + Cursor: foundCertifyBad.ID, + Node: foundCertifyBad, + }, + }, + }, nil + } + + edges := make([]*model.CertifyBadEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + // Cant really search for an exact Pkg, as these can be linked to either + // names or versions, and version could be empty. + var search []string + foundOne := false + if certifyBadSpec.Subject != nil && certifyBadSpec.Subject.Artifact != nil { + exactArtifact, err := c.artifactExact(ctx, certifyBadSpec.Subject.Artifact) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactArtifact != nil { + search = append(search, exactArtifact.BadLinks...) + foundOne = true + } + } + if !foundOne && certifyBadSpec.Subject != nil && certifyBadSpec.Subject.Source != nil { + exactSource, err := c.exactSource(ctx, certifyBadSpec.Subject.Source) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactSource != nil { + search = append(search, exactSource.BadLinks...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*badLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + cb, err := c.CBIfMatch(ctx, &certifyBadSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if cb == nil { + continue + } + + if (after != nil && cb.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.CertifyBadEdge{ + Cursor: cb.ID, + Node: cb, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyBadEdge{ + Cursor: cb.ID, + Node: cb, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(cbCol) + for !done { + var cbKeys []string + var err error + + cbKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(cbKeys) + + totalCount = len(cbKeys) + + for i, cbk := range cbKeys { + link, err := byKeykv[*badLink](ctx, cbCol, cbk, c) + if err != nil { + return nil, err + } + cbOut, err := c.CBIfMatch(ctx, &certifyBadSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if cbOut == nil { + continue + } + + if after != nil && !currentPage { + if cbOut.ID == *after { + totalCount = len(cbKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first && currentPage { + edges = append(edges, &model.CertifyBadEdge{ + Cursor: cbOut.ID, + Node: cbOut, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyBadEdge{ + Cursor: cbOut.ID, + Node: cbOut, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.CertifyBadConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(0, numNodes-1)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) CertifyBad(ctx context.Context, filter *model.CertifyBadSpec) ([]*model.CertifyBad, error) { @@ -246,10 +420,16 @@ func (c *demoClient) CertifyBad(ctx context.Context, filter *model.CertifyBadSpe if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addCBIfMatch(ctx, out, filter, link) + cb, err := c.CBIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if cb == nil { + continue + } + + out = append(out, cb) } } else { var done bool @@ -266,19 +446,19 @@ func (c *demoClient) CertifyBad(ctx context.Context, filter *model.CertifyBadSpe if err != nil { return nil, err } - out, err = c.addCBIfMatch(ctx, out, filter, link) + cb, err := c.CBIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + out = append(out, cb) } } } return out, nil } -func (c *demoClient) addCBIfMatch(ctx context.Context, out []*model.CertifyBad, - filter *model.CertifyBadSpec, link *badLink) ( - []*model.CertifyBad, error) { +func (c *demoClient) CBIfMatch(ctx context.Context, filter *model.CertifyBadSpec, link *badLink) (*model.CertifyBad, error) { if filter != nil { if noMatch(filter.Justification, link.Justification) || @@ -286,7 +466,7 @@ func (c *demoClient) addCBIfMatch(ctx context.Context, out []*model.CertifyBad, noMatch(filter.Origin, link.Origin) || noMatch(filter.DocumentRef, link.DocumentRef) || filter.KnownSince != nil && filter.KnownSince.After(link.KnownSince) { - return out, nil + return nil, nil } } @@ -295,9 +475,9 @@ func (c *demoClient) addCBIfMatch(ctx context.Context, out []*model.CertifyBad, return nil, err } if foundCertifyBad == nil { - return out, nil + return nil, nil } - return append(out, foundCertifyBad), nil + return foundCertifyBad, nil } func (c *demoClient) buildCertifyBad(ctx context.Context, link *badLink, filter *model.CertifyBadSpec, ingestOrIDProvided bool) (*model.CertifyBad, error) { diff --git a/pkg/assembler/backends/keyvalue/certifyGood.go b/pkg/assembler/backends/keyvalue/certifyGood.go index 4179e8e8e6..617185e530 100644 --- a/pkg/assembler/backends/keyvalue/certifyGood.go +++ b/pkg/assembler/backends/keyvalue/certifyGood.go @@ -18,7 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "time" @@ -191,7 +192,181 @@ func (c *demoClient) ingestCertifyGood(ctx context.Context, subject model.Packag // Query CertifyGood func (c *demoClient) CertifyGoodList(ctx context.Context, certifyGoodSpec model.CertifyGoodSpec, after *string, first *int) (*model.CertifyGoodConnection, error) { - return nil, fmt.Errorf("not implemented: CertifyGoodList") + c.m.RLock() + defer c.m.RUnlock() + + funcName := "CertifyGood" + + if certifyGoodSpec.ID != nil { + link, err := byIDkv[*goodLink](ctx, *certifyGoodSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + + foundCertifyGood, err := c.buildCertifyGood(ctx, link, &certifyGoodSpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + return &model.CertifyGoodConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(foundCertifyGood.ID), + EndCursor: ptrfrom.String(foundCertifyGood.ID), + }, + Edges: []*model.CertifyGoodEdge{ + { + Cursor: foundCertifyGood.ID, + Node: foundCertifyGood, + }, + }, + }, nil + } + + edges := make([]*model.CertifyGoodEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + // Cant really search for an exact Pkg, as these can be linked to either + // names or versions, and version could be empty. + var search []string + foundOne := false + if certifyGoodSpec.Subject != nil && certifyGoodSpec.Subject.Artifact != nil { + exactArtifact, err := c.artifactExact(ctx, certifyGoodSpec.Subject.Artifact) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactArtifact != nil { + search = append(search, exactArtifact.GoodLinks...) + foundOne = true + } + } + if !foundOne && certifyGoodSpec.Subject != nil && certifyGoodSpec.Subject.Source != nil { + exactSource, err := c.exactSource(ctx, certifyGoodSpec.Subject.Source) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactSource != nil { + search = append(search, exactSource.GoodLinks...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*goodLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + cg, err := c.CGIfMatch(ctx, &certifyGoodSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if cg == nil { + continue + } + + if (after != nil && cg.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.CertifyGoodEdge{ + Cursor: cg.ID, + Node: cg, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyGoodEdge{ + Cursor: cg.ID, + Node: cg, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(cgCol) + for !done { + var cgKeys []string + var err error + cgKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(cgKeys) + + totalCount = len(cgKeys) + + for i, cgKey := range cgKeys { + link, err := byKeykv[*goodLink](ctx, cgCol, cgKey, c) + if err != nil { + return nil, err + } + cgOut, err := c.CGIfMatch(ctx, &certifyGoodSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if cgOut == nil { + continue + } + + if after != nil && !currentPage { + if cgOut.ID == *after { + totalCount = len(cgKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.CertifyGoodEdge{ + Cursor: cgOut.ID, + Node: cgOut, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyGoodEdge{ + Cursor: cgOut.ID, + Node: cgOut, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.CertifyGoodConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(0, numNodes-1)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) CertifyGood(ctx context.Context, filter *model.CertifyGoodSpec) ([]*model.CertifyGood, error) { @@ -245,10 +420,15 @@ func (c *demoClient) CertifyGood(ctx context.Context, filter *model.CertifyGoodS if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addCGIfMatch(ctx, out, filter, link) + cg, err := c.CGIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if cg == nil { + continue + } + + out = append(out, cg) } } else { var done bool @@ -265,19 +445,19 @@ func (c *demoClient) CertifyGood(ctx context.Context, filter *model.CertifyGoodS if err != nil { return nil, err } - out, err = c.addCGIfMatch(ctx, out, filter, link) + cg, err := c.CGIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + out = append(out, cg) } } } return out, nil } -func (c *demoClient) addCGIfMatch(ctx context.Context, out []*model.CertifyGood, - filter *model.CertifyGoodSpec, link *goodLink) ( - []*model.CertifyGood, error) { +func (c *demoClient) CGIfMatch(ctx context.Context, filter *model.CertifyGoodSpec, link *goodLink) (*model.CertifyGood, error) { if filter != nil { if noMatch(filter.Justification, link.Justification) || @@ -285,7 +465,7 @@ func (c *demoClient) addCGIfMatch(ctx context.Context, out []*model.CertifyGood, noMatch(filter.Origin, link.Origin) || noMatch(filter.DocumentRef, link.DocumentRef) || filter.KnownSince != nil && filter.KnownSince.After(link.KnownSince) { - return out, nil + return nil, nil } } @@ -294,9 +474,9 @@ func (c *demoClient) addCGIfMatch(ctx context.Context, out []*model.CertifyGood, return nil, err } if foundCertifyGood == nil { - return out, nil + return nil, nil } - return append(out, foundCertifyGood), nil + return foundCertifyGood, nil } func (c *demoClient) buildCertifyGood(ctx context.Context, link *goodLink, filter *model.CertifyGoodSpec, ingestOrIDProvided bool) (*model.CertifyGood, error) { diff --git a/pkg/assembler/backends/keyvalue/certifyLegal.go b/pkg/assembler/backends/keyvalue/certifyLegal.go index 8e3811ae05..4d879c86fb 100644 --- a/pkg/assembler/backends/keyvalue/certifyLegal.go +++ b/pkg/assembler/backends/keyvalue/certifyLegal.go @@ -19,7 +19,9 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" "slices" + "sort" "strings" "time" @@ -266,7 +268,190 @@ func (c *demoClient) convLegal(ctx context.Context, in *certifyLegalStruct) (*mo } func (c *demoClient) CertifyLegalList(ctx context.Context, certifyLegalSpec model.CertifyLegalSpec, after *string, first *int) (*model.CertifyLegalConnection, error) { - return nil, fmt.Errorf("not implemented: CertifyLegalList") + c.m.RLock() + defer c.m.RUnlock() + + funcName := "CertifyLegal" + + if certifyLegalSpec.ID != nil { + link, err := byIDkv[*certifyLegalStruct](ctx, *certifyLegalSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + foundCertifyLegal, err := c.convLegal(ctx, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + return &model.CertifyLegalConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(foundCertifyLegal.ID), + EndCursor: ptrfrom.String(foundCertifyLegal.ID), + }, + Edges: []*model.CertifyLegalEdge{ + { + Cursor: foundCertifyLegal.ID, + Node: foundCertifyLegal, + }, + }, + }, nil + } + + edges := make([]*model.CertifyLegalEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + if certifyLegalSpec.Subject != nil && certifyLegalSpec.Subject.Package != nil { + pkgs, err := c.findPackageVersion(ctx, certifyLegalSpec.Subject.Package) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + foundOne = len(pkgs) > 0 + for _, pkg := range pkgs { + search = append(search, pkg.CertifyLegals...) + } + } + if !foundOne && certifyLegalSpec.Subject != nil && certifyLegalSpec.Subject.Source != nil { + exactSource, err := c.exactSource(ctx, certifyLegalSpec.Subject.Source) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactSource != nil { + search = append(search, exactSource.CertifyLegals...) + foundOne = true + } + } + if !foundOne { + for _, lSpec := range certifyLegalSpec.DeclaredLicenses { + exactLicense, err := c.licenseExact(ctx, lSpec) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactLicense != nil { + search = append(search, exactLicense.CertifyLegals...) + foundOne = true + break + } + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*certifyLegalStruct](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + legal, err := c.legalIfMatch(ctx, &certifyLegalSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if legal == nil { + continue + } + + if (after != nil && legal.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.CertifyLegalEdge{ + Cursor: legal.ID, + Node: legal, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyLegalEdge{ + Cursor: legal.ID, + Node: legal, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(clCol) + for !done { + var clKeys []string + var err error + clKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(clKeys) + totalCount = len(clKeys) + + for i, clk := range clKeys { + link, err := byKeykv[*certifyLegalStruct](ctx, clCol, clk, c) + if err != nil { + return nil, err + } + legal, err := c.legalIfMatch(ctx, &certifyLegalSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if legal == nil { + continue + } + + if after != nil && !currentPage { + if legal.ID == *after { + totalCount = len(clKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.CertifyLegalEdge{ + Cursor: legal.ID, + Node: legal, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyLegalEdge{ + Cursor: legal.ID, + Node: legal, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.CertifyLegalConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) CertifyLegal(ctx context.Context, filter *model.CertifyLegalSpec) ([]*model.CertifyLegal, error) { @@ -324,19 +509,6 @@ func (c *demoClient) CertifyLegal(ctx context.Context, filter *model.CertifyLega } } } - if !foundOne && filter != nil { - for _, lSpec := range filter.DiscoveredLicenses { - exactLicense, err := c.licenseExact(ctx, lSpec) - if err != nil { - return nil, gqlerror.Errorf("%v :: %v", funcName, err) - } - if exactLicense != nil { - search = append(search, exactLicense.CertifyLegals...) - foundOne = true - break - } - } - } var out []*model.CertifyLegal if foundOne { @@ -345,10 +517,16 @@ func (c *demoClient) CertifyLegal(ctx context.Context, filter *model.CertifyLega if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addLegalIfMatch(ctx, out, filter, link) + legal, err := c.legalIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if legal == nil { + continue + } + + out = append(out, legal) } } else { var done bool @@ -365,19 +543,19 @@ func (c *demoClient) CertifyLegal(ctx context.Context, filter *model.CertifyLega if err != nil { return nil, err } - out, err = c.addLegalIfMatch(ctx, out, filter, link) + legal, err := c.legalIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + out = append(out, legal) } } } return out, nil } -func (c *demoClient) addLegalIfMatch(ctx context.Context, out []*model.CertifyLegal, - filter *model.CertifyLegalSpec, link *certifyLegalStruct) ( - []*model.CertifyLegal, error, +func (c *demoClient) legalIfMatch(ctx context.Context, filter *model.CertifyLegalSpec, link *certifyLegalStruct) ( + *model.CertifyLegal, error, ) { if noMatch(filter.DeclaredLicense, link.DeclaredLicense) || noMatch(filter.DiscoveredLicense, link.DiscoveredLicense) || @@ -389,30 +567,30 @@ func (c *demoClient) addLegalIfMatch(ctx context.Context, out []*model.CertifyLe (filter.TimeScanned != nil && !link.TimeScanned.Equal(*filter.TimeScanned)) || !c.matchLicenses(ctx, filter.DeclaredLicenses, link.DeclaredLicenses) || !c.matchLicenses(ctx, filter.DiscoveredLicenses, link.DiscoveredLicenses) { - return out, nil + return nil, nil } if filter.Subject != nil { if filter.Subject.Package != nil { if link.Pkg == "" { - return out, nil + return nil, nil } p, err := c.buildPackageResponse(ctx, link.Pkg, filter.Subject.Package) if err != nil { return nil, err } if p == nil { - return out, nil + return nil, nil } } else if filter.Subject.Source != nil { if link.Source == "" { - return out, nil + return nil, nil } s, err := c.buildSourceResponse(ctx, link.Source, filter.Subject.Source) if err != nil { return nil, err } if s == nil { - return out, nil + return nil, nil } } } @@ -420,7 +598,7 @@ func (c *demoClient) addLegalIfMatch(ctx context.Context, out []*model.CertifyLe if err != nil { return nil, err } - return append(out, o), nil + return o, nil } func (c *demoClient) matchLicenses(ctx context.Context, filter []*model.LicenseSpec, value []string) bool { diff --git a/pkg/assembler/backends/keyvalue/certifyScorecard.go b/pkg/assembler/backends/keyvalue/certifyScorecard.go index 8b5c6fb72f..8f717bcbfc 100644 --- a/pkg/assembler/backends/keyvalue/certifyScorecard.go +++ b/pkg/assembler/backends/keyvalue/certifyScorecard.go @@ -19,7 +19,9 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" "reflect" + "sort" "strings" "time" @@ -144,7 +146,172 @@ func (c *demoClient) certifyScorecard(ctx context.Context, source model.IDorSour // Query CertifyScorecard func (c *demoClient) ScorecardsList(ctx context.Context, scorecardSpec model.CertifyScorecardSpec, after *string, first *int) (*model.CertifyScorecardConnection, error) { - return nil, fmt.Errorf("not implemented: ScorecardsList") + c.m.RLock() + defer c.m.RUnlock() + + funcName := "Scorecards" + + if scorecardSpec.ID != nil { + link, err := byIDkv[*scorecardLink](ctx, *scorecardSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + + exactCertifyScorecard, err := c.buildScorecard(ctx, link, &scorecardSpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + return &model.CertifyScorecardConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(exactCertifyScorecard.ID), + EndCursor: ptrfrom.String(exactCertifyScorecard.ID), + }, + Edges: []*model.CertifyScorecardEdge{ + { + Cursor: exactCertifyScorecard.ID, + Node: exactCertifyScorecard, + }, + }, + }, nil + } + + edges := make([]*model.CertifyScorecardEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + if scorecardSpec.Source != nil { + exactSource, err := c.exactSource(ctx, scorecardSpec.Source) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactSource != nil { + search = exactSource.ScorecardLinks + foundOne = true + } + } + + if foundOne { + var out []*model.CertifyScorecard + for _, id := range search { + link, err := byIDkv[*scorecardLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + out, err = c.addSCIfMatch(ctx, out, &scorecardSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + for _, scorecardOut := range out { + if (after != nil && scorecardOut.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.CertifyScorecardEdge{ + Cursor: scorecardOut.ID, + Node: scorecardOut, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyScorecardEdge{ + Cursor: scorecardOut.ID, + Node: scorecardOut, + }) + } + } + } + } + } else { + scn := c.kv.Keys(cscCol) + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + for !done { + var err error + var cscKeys []string + + cscKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(cscKeys) + + totalCount = len(cscKeys) + + for i, cscKey := range cscKeys { + link, err := byKeykv[*scorecardLink](ctx, cscCol, cscKey, c) + if err != nil { + return nil, err + } + + scorecardOut, err := c.SCIfMatch(ctx, &scorecardSpec, link) + + if err != nil { + return nil, err + } + + if scorecardOut == nil { + continue + } + + if after != nil && !currentPage { + if scorecardOut.ID == *after { + totalCount = len(cscKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.CertifyScorecardEdge{ + Cursor: scorecardOut.ID, + Node: scorecardOut, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyScorecardEdge{ + Cursor: scorecardOut.ID, + Node: scorecardOut, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.CertifyScorecardConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) Scorecards(ctx context.Context, filter *model.CertifyScorecardSpec) ([]*model.CertifyScorecard, error) { @@ -216,6 +383,41 @@ func (c *demoClient) Scorecards(ctx context.Context, filter *model.CertifyScorec return out, nil } +func (c *demoClient) SCIfMatch(ctx context.Context, filter *model.CertifyScorecardSpec, link *scorecardLink) ( + *model.CertifyScorecard, error) { + if filter != nil && filter.TimeScanned != nil && !filter.TimeScanned.Equal(link.TimeScanned) { + return nil, nil + } + if filter != nil && noMatchFloat(filter.AggregateScore, link.AggregateScore) { + return nil, nil + } + if filter != nil && noMatchChecks(filter.Checks, link.Checks) { + return nil, nil + } + if filter != nil && noMatch(filter.ScorecardVersion, link.ScorecardVersion) { + return nil, nil + } + if filter != nil && noMatch(filter.ScorecardCommit, link.ScorecardCommit) { + return nil, nil + } + if filter != nil && noMatch(filter.Origin, link.Origin) { + return nil, nil + } + if filter != nil && noMatch(filter.Collector, link.Collector) { + return nil, nil + } + if filter != nil && noMatch(filter.DocumentRef, link.DocumentRef) { + return nil, nil + } + + foundCertifyScorecard, err := c.buildScorecard(ctx, link, filter, false) + if err != nil { + return nil, err + } + + return foundCertifyScorecard, nil +} + func (c *demoClient) addSCIfMatch(ctx context.Context, out []*model.CertifyScorecard, filter *model.CertifyScorecardSpec, link *scorecardLink) ( []*model.CertifyScorecard, error) { diff --git a/pkg/assembler/backends/keyvalue/certifyVEXStatement.go b/pkg/assembler/backends/keyvalue/certifyVEXStatement.go index 41766d96da..71bff0ae78 100644 --- a/pkg/assembler/backends/keyvalue/certifyVEXStatement.go +++ b/pkg/assembler/backends/keyvalue/certifyVEXStatement.go @@ -18,7 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "time" @@ -194,7 +195,189 @@ func (c *demoClient) ingestVEXStatement(ctx context.Context, subject model.Packa // Query CertifyVex func (c *demoClient) CertifyVEXStatementList(ctx context.Context, certifyVEXStatementSpec model.CertifyVEXStatementSpec, after *string, first *int) (*model.VEXConnection, error) { - return nil, fmt.Errorf("not implemented: CertifyVEXStatementList") + c.m.RLock() + defer c.m.RUnlock() + funcName := "CertifyVEXStatement" + + if certifyVEXStatementSpec.ID != nil { + link, err := byIDkv[*vexLink](ctx, *certifyVEXStatementSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + foundCertifyVex, err := c.buildCertifyVEXStatement(ctx, link, &certifyVEXStatementSpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + return &model.VEXConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(foundCertifyVex.ID), + EndCursor: ptrfrom.String(foundCertifyVex.ID), + }, + Edges: []*model.VEXEdge{ + { + Cursor: foundCertifyVex.ID, + Node: foundCertifyVex, + }, + }, + }, nil + } + + edges := make([]*model.VEXEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + + if certifyVEXStatementSpec.Subject != nil && certifyVEXStatementSpec.Subject.Artifact != nil { + exactArtifact, err := c.artifactExact(ctx, certifyVEXStatementSpec.Subject.Artifact) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactArtifact != nil { + search = append(search, exactArtifact.VexLinks...) + foundOne = true + } + } + if !foundOne && certifyVEXStatementSpec.Subject != nil && certifyVEXStatementSpec.Subject.Package != nil { + pkgs, err := c.findPackageVersion(ctx, certifyVEXStatementSpec.Subject.Package) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + foundOne = len(pkgs) > 0 + for _, pkg := range pkgs { + search = append(search, pkg.VexLinks...) + } + } + if !foundOne && certifyVEXStatementSpec.Vulnerability != nil { + exactVuln, err := c.exactVulnerability(ctx, certifyVEXStatementSpec.Vulnerability) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactVuln != nil { + search = append(search, exactVuln.VexLinks...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*vexLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + vex, err := c.vexIfMatch(ctx, &certifyVEXStatementSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if vex == nil { + continue + } + + if (after != nil && vex.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.VEXEdge{ + Cursor: vex.ID, + Node: vex, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.VEXEdge{ + Cursor: vex.ID, + Node: vex, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(cVEXCol) + + for !done { + var keys []string + var err error + keys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(keys) + totalCount = len(keys) + + for i, key := range keys { + link, err := byKeykv[*vexLink](ctx, cVEXCol, key, c) + if err != nil { + return nil, err + } + vex, err := c.vexIfMatch(ctx, &certifyVEXStatementSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%vex :: %vex", funcName, err) + } + + if vex == nil { + continue + } + + if after != nil && !currentPage { + if vex.ID == *after { + totalCount = len(keys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.VEXEdge{ + Cursor: vex.ID, + Node: vex, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.VEXEdge{ + Cursor: vex.ID, + Node: vex, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.VEXConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) CertifyVEXStatement(ctx context.Context, filter *model.CertifyVEXStatementSpec) ([]*model.CertifyVEXStatement, error) { @@ -257,10 +440,16 @@ func (c *demoClient) CertifyVEXStatement(ctx context.Context, filter *model.Cert if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addVexIfMatch(ctx, out, filter, link) + v, err := c.vexIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if v == nil { + continue + } + + out = append(out, v) } } else { var done bool @@ -277,43 +466,48 @@ func (c *demoClient) CertifyVEXStatement(ctx context.Context, filter *model.Cert if err != nil { return nil, err } - out, err = c.addVexIfMatch(ctx, out, filter, link) + v, err := c.vexIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if v == nil { + continue + } + + out = append(out, v) } } } return out, nil } -func (c *demoClient) addVexIfMatch(ctx context.Context, out []*model.CertifyVEXStatement, - filter *model.CertifyVEXStatementSpec, link *vexLink) ( - []*model.CertifyVEXStatement, error) { +func (c *demoClient) vexIfMatch(ctx context.Context, filter *model.CertifyVEXStatementSpec, link *vexLink) ( + *model.CertifyVEXStatement, error) { if filter != nil && filter.KnownSince != nil && !filter.KnownSince.Equal(link.KnownSince) { - return out, nil + return nil, nil } if filter != nil && filter.VexJustification != nil && *filter.VexJustification != link.Justification { - return out, nil + return nil, nil } if filter != nil && filter.Status != nil && *filter.Status != link.Status { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Statement, link.Statement) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.StatusNotes, link.StatusNotes) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Collector, link.Collector) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Origin, link.Origin) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.DocumentRef, link.DocumentRef) { - return out, nil + return nil, nil } foundCertifyVex, err := c.buildCertifyVEXStatement(ctx, link, filter, false) @@ -321,9 +515,9 @@ func (c *demoClient) addVexIfMatch(ctx context.Context, out []*model.CertifyVEXS return nil, err } if foundCertifyVex == nil { - return out, nil + return nil, nil } - return append(out, foundCertifyVex), nil + return foundCertifyVex, nil } diff --git a/pkg/assembler/backends/keyvalue/certifyVuln.go b/pkg/assembler/backends/keyvalue/certifyVuln.go index 2ad332b842..3307755897 100644 --- a/pkg/assembler/backends/keyvalue/certifyVuln.go +++ b/pkg/assembler/backends/keyvalue/certifyVuln.go @@ -18,8 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" "reflect" + "sort" "strings" "time" @@ -158,7 +158,196 @@ func (c *demoClient) ingestVulnerability(ctx context.Context, packageArg model.I // Query CertifyVuln func (c *demoClient) CertifyVulnList(ctx context.Context, certifyVulnSpec model.CertifyVulnSpec, after *string, first *int) (*model.CertifyVulnConnection, error) { - return nil, fmt.Errorf("not implemented: CertifyVulnList") + c.m.RLock() + defer c.m.RUnlock() + funcName := "CertifyVuln" + + if certifyVulnSpec.ID != nil { + link, err := byIDkv[*certifyVulnerabilityLink](ctx, *certifyVulnSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + foundCertifyVuln, err := c.buildCertifyVulnerability(ctx, link, &certifyVulnSpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + return &model.CertifyVulnConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(foundCertifyVuln.ID), + EndCursor: ptrfrom.String(foundCertifyVuln.ID), + }, + Edges: []*model.CertifyVulnEdge{ + { + Cursor: foundCertifyVuln.ID, + Node: foundCertifyVuln, + }, + }, + }, nil + } + + edges := make([]*model.CertifyVulnEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + + if certifyVulnSpec.Package != nil { + pkgs, err := c.findPackageVersion(ctx, certifyVulnSpec.Package) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + foundOne = len(pkgs) > 0 + for _, pkg := range pkgs { + search = append(search, pkg.CertifyVulnLinks...) + } + } + + if !foundOne && certifyVulnSpec.Vulnerability != nil && + certifyVulnSpec.Vulnerability.NoVuln != nil && *certifyVulnSpec.Vulnerability.NoVuln { + exactVuln, err := c.exactVulnerability(ctx, &model.VulnerabilitySpec{ + Type: ptrfrom.String(noVulnType), + VulnerabilityID: ptrfrom.String(""), + }) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactVuln != nil { + search = append(search, exactVuln.CertifyVulnLinks...) + foundOne = true + } + } else if !foundOne && certifyVulnSpec.Vulnerability != nil { + if certifyVulnSpec.Vulnerability.NoVuln != nil && !*certifyVulnSpec.Vulnerability.NoVuln { + if certifyVulnSpec.Vulnerability.Type != nil && *certifyVulnSpec.Vulnerability.Type == noVulnType { + return nil, gqlerror.Errorf("novuln boolean set to false, cannot specify vulnerability type to be novuln") + } + } + exactVuln, err := c.exactVulnerability(ctx, certifyVulnSpec.Vulnerability) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactVuln != nil { + search = append(search, exactVuln.CertifyVulnLinks...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*certifyVulnerabilityLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + cv, err := c.certifyVulnIfMatch(ctx, &certifyVulnSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if cv == nil { + continue + } + + if (after != nil && cv.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.CertifyVulnEdge{ + Cursor: cv.ID, + Node: cv, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyVulnEdge{ + Cursor: cv.ID, + Node: cv, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + var done bool + scn := c.kv.Keys(cVulnCol) + for !done { + var keys []string + var err error + keys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(keys) + totalCount = len(keys) + + for i, key := range keys { + link, err := byKeykv[*certifyVulnerabilityLink](ctx, cVulnCol, key, c) + if err != nil { + return nil, err + } + cv, err := c.certifyVulnIfMatch(ctx, &certifyVulnSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if cv == nil { + continue + } + + if after != nil && !currentPage { + if cv.ID == *after { + totalCount = len(keys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.CertifyVulnEdge{ + Cursor: cv.ID, + Node: cv, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.CertifyVulnEdge{ + Cursor: cv.ID, + Node: cv, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.CertifyVulnConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) CertifyVuln(ctx context.Context, filter *model.CertifyVulnSpec) ([]*model.CertifyVuln, error) { @@ -210,7 +399,7 @@ func (c *demoClient) CertifyVuln(ctx context.Context, filter *model.CertifyVulnS } else if !foundOne && filter != nil && filter.Vulnerability != nil { if filter.Vulnerability.NoVuln != nil && !*filter.Vulnerability.NoVuln { if filter.Vulnerability.Type != nil && *filter.Vulnerability.Type == noVulnType { - return []*model.CertifyVuln{}, gqlerror.Errorf("novuln boolean set to false, cannot specify vulnerability type to be novuln") + return nil, gqlerror.Errorf("novuln boolean set to false, cannot specify vulnerability type to be novuln") } } exactVuln, err := c.exactVulnerability(ctx, filter.Vulnerability) @@ -230,10 +419,16 @@ func (c *demoClient) CertifyVuln(ctx context.Context, filter *model.CertifyVulnS if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addCVIfMatch(ctx, out, filter, link) + cv, err := c.certifyVulnIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if cv == nil { + continue + } + + out = append(out, cv) } } else { var done bool @@ -250,10 +445,16 @@ func (c *demoClient) CertifyVuln(ctx context.Context, filter *model.CertifyVulnS if err != nil { return nil, err } - out, err = c.addCVIfMatch(ctx, out, filter, link) + cv, err := c.certifyVulnIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if cv == nil { + continue + } + + out = append(out, cv) } } } @@ -261,32 +462,31 @@ func (c *demoClient) CertifyVuln(ctx context.Context, filter *model.CertifyVulnS return out, nil } -func (c *demoClient) addCVIfMatch(ctx context.Context, out []*model.CertifyVuln, - filter *model.CertifyVulnSpec, - link *certifyVulnerabilityLink) ([]*model.CertifyVuln, error) { +func (c *demoClient) certifyVulnIfMatch(ctx context.Context, filter *model.CertifyVulnSpec, link *certifyVulnerabilityLink) ( + *model.CertifyVuln, error) { if filter != nil && filter.TimeScanned != nil && !filter.TimeScanned.Equal(link.TimeScanned) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.DbURI, link.DBURI) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.DbVersion, link.DBVersion) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.ScannerURI, link.ScannerURI) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.ScannerVersion, link.ScannerVersion) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Collector, link.Collector) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Origin, link.Origin) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.DocumentRef, link.DocumentRef) { - return out, nil + return nil, nil } foundCertifyVuln, err := c.buildCertifyVulnerability(ctx, link, filter, false) @@ -294,9 +494,9 @@ func (c *demoClient) addCVIfMatch(ctx context.Context, out []*model.CertifyVuln, return nil, err } if foundCertifyVuln == nil || reflect.ValueOf(foundCertifyVuln.Vulnerability).IsNil() { - return out, nil + return nil, nil } - return append(out, foundCertifyVuln), nil + return foundCertifyVuln, nil } func (c *demoClient) buildCertifyVulnerability(ctx context.Context, link *certifyVulnerabilityLink, filter *model.CertifyVulnSpec, ingestOrIDProvided bool) (*model.CertifyVuln, error) { diff --git a/pkg/assembler/backends/keyvalue/hasMetadata.go b/pkg/assembler/backends/keyvalue/hasMetadata.go index bf4c623219..6d0e4d9af7 100644 --- a/pkg/assembler/backends/keyvalue/hasMetadata.go +++ b/pkg/assembler/backends/keyvalue/hasMetadata.go @@ -18,7 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "time" @@ -201,7 +202,178 @@ func (c *demoClient) ingestHasMetadata(ctx context.Context, subject model.Packag // Query HasMetadata func (c *demoClient) HasMetadataList(ctx context.Context, hasMetadataSpec model.HasMetadataSpec, after *string, first *int) (*model.HasMetadataConnection, error) { - return nil, fmt.Errorf("not implemented: HasMetadataList") + funcName := "HasMetadata" + + c.m.RLock() + defer c.m.RUnlock() + + if hasMetadataSpec.ID != nil { + link, err := byIDkv[*hasMetadataLink](ctx, *hasMetadataSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + found, err := c.buildHasMetadata(ctx, link, &hasMetadataSpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + return &model.HasMetadataConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(found.ID), + EndCursor: ptrfrom.String(found.ID), + }, + Edges: []*model.HasMetadataEdge{ + { + Cursor: found.ID, + Node: found, + }, + }, + }, nil + } + + edges := make([]*model.HasMetadataEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + // Cant really search for an exact Pkg, as these can be linked to either + // names or versions, and version could be empty. + var search []string + foundOne := false + if hasMetadataSpec.Subject != nil && hasMetadataSpec.Subject.Artifact != nil { + exactArtifact, err := c.artifactExact(ctx, hasMetadataSpec.Subject.Artifact) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactArtifact != nil { + search = append(search, exactArtifact.HasMetadataLinks...) + foundOne = true + } + } + if !foundOne && hasMetadataSpec.Subject != nil && hasMetadataSpec.Subject.Source != nil { + exactSource, err := c.exactSource(ctx, hasMetadataSpec.Subject.Source) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactSource != nil { + search = append(search, exactSource.HasMetadataLinks...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*hasMetadataLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + hm, err := c.hasMetadataIfMatch(ctx, &hasMetadataSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if hm == nil { + continue + } + + if (after != nil && hm.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HasMetadataEdge{ + Cursor: hm.ID, + Node: hm, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HasMetadataEdge{ + Cursor: hm.ID, + Node: hm, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(hasMDCol) + for !done { + var hmKeys []string + var err error + hmKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(hmKeys) + totalCount = len(hmKeys) + + for i, hmKey := range hmKeys { + link, err := byKeykv[*hasMetadataLink](ctx, hasMDCol, hmKey, c) + if err != nil { + return nil, err + } + hm, err := c.hasMetadataIfMatch(ctx, &hasMetadataSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if hm == nil { + continue + } + + if after != nil && !currentPage { + if hm.ID == *after { + totalCount = len(hmKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HasMetadataEdge{ + Cursor: hm.ID, + Node: hm, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HasMetadataEdge{ + Cursor: hm.ID, + Node: hm, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.HasMetadataConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) HasMetadata(ctx context.Context, filter *model.HasMetadataSpec) ([]*model.HasMetadata, error) { @@ -255,10 +427,16 @@ func (c *demoClient) HasMetadata(ctx context.Context, filter *model.HasMetadataS if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addHMIfMatch(ctx, out, filter, link) + hm, err := c.hasMetadataIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if hm == nil { + continue + } + + out = append(out, hm) } } else { var done bool @@ -275,40 +453,46 @@ func (c *demoClient) HasMetadata(ctx context.Context, filter *model.HasMetadataS if err != nil { return nil, err } - out, err = c.addHMIfMatch(ctx, out, filter, link) + hm, err := c.hasMetadataIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if hm == nil { + continue + } + + out = append(out, hm) } } } return out, nil } -func (c *demoClient) addHMIfMatch(ctx context.Context, out []*model.HasMetadata, filter *model.HasMetadataSpec, link *hasMetadataLink) ( - []*model.HasMetadata, error) { +func (c *demoClient) hasMetadataIfMatch(ctx context.Context, filter *model.HasMetadataSpec, link *hasMetadataLink) ( + *model.HasMetadata, error) { if filter != nil && noMatch(filter.Justification, link.Justification) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Collector, link.Collector) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Origin, link.Origin) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Key, link.MDKey) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Value, link.Value) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.DocumentRef, link.DocumentRef) { - return out, nil + return nil, nil } // no match if filter time since is after the timestamp if filter != nil && filter.Since != nil && filter.Since.After(link.Timestamp) { - return out, nil + return nil, nil } found, err := c.buildHasMetadata(ctx, link, filter, false) @@ -316,9 +500,9 @@ func (c *demoClient) addHMIfMatch(ctx context.Context, out []*model.HasMetadata, return nil, err } if found == nil { - return out, nil + return nil, nil } - return append(out, found), nil + return found, nil } func (c *demoClient) buildHasMetadata(ctx context.Context, link *hasMetadataLink, filter *model.HasMetadataSpec, ingestOrIDProvided bool) (*model.HasMetadata, error) { diff --git a/pkg/assembler/backends/keyvalue/hasSBOM.go b/pkg/assembler/backends/keyvalue/hasSBOM.go index 7228c6ef49..b7caac0e91 100644 --- a/pkg/assembler/backends/keyvalue/hasSBOM.go +++ b/pkg/assembler/backends/keyvalue/hasSBOM.go @@ -19,6 +19,8 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "time" @@ -322,7 +324,176 @@ func (c *demoClient) convHasSBOM(ctx context.Context, in *hasSBOMStruct) (*model // Query HasSBOM func (c *demoClient) HasSBOMList(ctx context.Context, hasSBOMSpec model.HasSBOMSpec, after *string, first *int) (*model.HasSBOMConnection, error) { - return nil, fmt.Errorf("not implemented: HasSBOMList") + funcName := "HasSBOM" + c.m.RLock() + defer c.m.RUnlock() + + if hasSBOMSpec.ID != nil { + link, err := byIDkv[*hasSBOMStruct](ctx, *hasSBOMSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + hs, err := c.convHasSBOM(ctx, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + return &model.HasSBOMConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(hs.ID), + EndCursor: ptrfrom.String(hs.ID), + }, + Edges: []*model.HasSBOMEdge{ + { + Cursor: hs.ID, + Node: hs, + }, + }, + }, nil + } + + edges := make([]*model.HasSBOMEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + if hasSBOMSpec.Subject != nil && hasSBOMSpec.Subject.Package != nil { + pkgs, err := c.findPackageVersion(ctx, hasSBOMSpec.Subject.Package) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + foundOne = len(pkgs) > 0 + for _, pkg := range pkgs { + search = append(search, pkg.HasSBOMs...) + } + } + if !foundOne && hasSBOMSpec.Subject != nil && hasSBOMSpec.Subject.Artifact != nil { + exactArt, err := c.artifactExact(ctx, hasSBOMSpec.Subject.Artifact) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactArt != nil { + search = exactArt.HasSBOMs + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*hasSBOMStruct](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + hs, err := c.hasSBOMIfMatch(ctx, &hasSBOMSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if hs == nil { + continue + } + + if (after != nil && hs.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HasSBOMEdge{ + Cursor: hs.ID, + Node: hs, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HasSBOMEdge{ + Cursor: hs.ID, + Node: hs, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(hasSBOMCol) + for !done { + var hsKeys []string + var err error + hsKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(hsKeys) + totalCount = len(hsKeys) + + for i, hsKey := range hsKeys { + link, err := byKeykv[*hasSBOMStruct](ctx, hasSBOMCol, hsKey, c) + if err != nil { + return nil, err + } + hs, err := c.hasSBOMIfMatch(ctx, &hasSBOMSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if hs == nil { + continue + } + + if after != nil && !currentPage { + if hs.ID == *after { + totalCount = len(hsKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HasSBOMEdge{ + Cursor: hs.ID, + Node: hs, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HasSBOMEdge{ + Cursor: hs.ID, + Node: hs, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.HasSBOMConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) HasSBOM(ctx context.Context, filter *model.HasSBOMSpec) ([]*model.HasSbom, error) { @@ -374,10 +545,16 @@ func (c *demoClient) HasSBOM(ctx context.Context, filter *model.HasSBOMSpec) ([] if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addHasSBOMIfMatch(ctx, out, filter, link) + hs, err := c.hasSBOMIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if hs == nil { + continue + } + + out = append(out, hs) } } else { var done bool @@ -394,10 +571,16 @@ func (c *demoClient) HasSBOM(ctx context.Context, filter *model.HasSBOMSpec) ([] if err != nil { return nil, err } - out, err = c.addHasSBOMIfMatch(ctx, out, filter, link) + hs, err := c.hasSBOMIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if hs == nil { + continue + } + + out = append(out, hs) } } } @@ -405,9 +588,8 @@ func (c *demoClient) HasSBOM(ctx context.Context, filter *model.HasSBOMSpec) ([] return out, nil } -func (c *demoClient) addHasSBOMIfMatch(ctx context.Context, out []*model.HasSbom, - filter *model.HasSBOMSpec, link *hasSBOMStruct) ( - []*model.HasSbom, error) { +func (c *demoClient) hasSBOMIfMatch(ctx context.Context, filter *model.HasSBOMSpec, link *hasSBOMStruct) ( + *model.HasSbom, error) { if filter != nil { if noMatch(filter.URI, link.URI) || @@ -418,39 +600,39 @@ func (c *demoClient) addHasSBOMIfMatch(ctx context.Context, out []*model.HasSbom noMatch(filter.Collector, link.Collector) || noMatch(filter.DocumentRef, link.DocumentRef) || (filter.KnownSince != nil && filter.KnownSince.After(link.KnownSince)) { - return out, nil + return nil, nil } // collect packages and artifacts from included software pkgs, artifacts, err := c.getPackageVersionAndArtifacts(ctx, link.IncludedSoftware) if err != nil { - return out, err + return nil, err } pkgFilters, artFilters := helper.GetPackageAndArtifactFilters(filter.IncludedSoftware) if !c.matchPackages(ctx, pkgFilters, pkgs) || !c.matchArtifacts(ctx, artFilters, artifacts) || !c.matchDependencies(ctx, filter.IncludedDependencies, link.IncludedDependencies) || !c.matchOccurrences(ctx, filter.IncludedOccurrences, link.IncludedOccurrences) { - return out, nil + return nil, nil } if filter.Subject != nil { if filter.Subject.Package != nil { if link.Pkg == "" { - return out, nil + return nil, nil } p, err := c.buildPackageResponse(ctx, link.Pkg, filter.Subject.Package) if err != nil { return nil, err } if p == nil { - return out, nil + return nil, nil } } else if filter.Subject.Artifact != nil { if link.Artifact == "" { - return out, nil + return nil, nil } if !c.artifactMatch(ctx, link.Artifact, filter.Subject.Artifact) { - return out, nil + return nil, nil } } } @@ -459,5 +641,5 @@ func (c *demoClient) addHasSBOMIfMatch(ctx context.Context, out []*model.HasSbom if err != nil { return nil, err } - return append(out, sb), nil + return sb, nil } diff --git a/pkg/assembler/backends/keyvalue/hasSLSA.go b/pkg/assembler/backends/keyvalue/hasSLSA.go index b7e6a679f6..c9ed00831f 100644 --- a/pkg/assembler/backends/keyvalue/hasSLSA.go +++ b/pkg/assembler/backends/keyvalue/hasSLSA.go @@ -19,6 +19,7 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" "slices" "sort" "strings" @@ -92,7 +93,183 @@ func (n *hasSLSAStruct) BuildModelNode(ctx context.Context, c *demoClient) (mode // Query HasSlsa func (c *demoClient) HasSLSAList(ctx context.Context, hasSLSASpec model.HasSLSASpec, after *string, first *int) (*model.HasSLSAConnection, error) { - return nil, fmt.Errorf("not implemented: HasSLSAList") + funcName := "HasSlsa" + c.m.RLock() + defer c.m.RUnlock() + if hasSLSASpec.ID != nil { + link, err := byIDkv[*hasSLSAStruct](ctx, *hasSLSASpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + hs, err := c.convSLSA(ctx, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + return &model.HasSLSAConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(hs.ID), + EndCursor: ptrfrom.String(hs.ID), + }, + Edges: []*model.HasSLSAEdge{ + { + Cursor: hs.ID, + Node: hs, + }, + }, + }, nil + } + + edges := make([]*model.HasSLSAEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + var arts []*model.ArtifactSpec + arts = append(arts, hasSLSASpec.Subject) + arts = append(arts, hasSLSASpec.BuiltFrom...) + + for _, a := range arts { + if !foundOne && a != nil { + exactArtifact, err := c.artifactExact(ctx, a) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactArtifact != nil { + search = append(search, exactArtifact.HasSLSAs...) + foundOne = true + break + } + } + } + if !foundOne && hasSLSASpec.BuiltBy != nil { + exactBuilder, err := c.exactBuilder(ctx, hasSLSASpec.BuiltBy) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactBuilder != nil { + search = append(search, exactBuilder.HasSLSAs...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*hasSLSAStruct](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + hs, err := c.addSLSAIfMatch(ctx, &hasSLSASpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if hs == nil { + continue + } + + if (after != nil && hs.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HasSLSAEdge{ + Cursor: hs.ID, + Node: hs, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HasSLSAEdge{ + Cursor: hs.ID, + Node: hs, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(slsaCol) + for !done { + var slsaKeys []string + var err error + slsaKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(slsaKeys) + totalCount = len(slsaKeys) + + for i, slsaKey := range slsaKeys { + link, err := byKeykv[*hasSLSAStruct](ctx, slsaCol, slsaKey, c) + if err != nil { + return nil, err + } + hs, err := c.addSLSAIfMatch(ctx, &hasSLSASpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if hs == nil { + continue + } + + if after != nil && !currentPage { + if hs.ID == *after { + totalCount = len(slsaKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HasSLSAEdge{ + Cursor: hs.ID, + Node: hs, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HasSLSAEdge{ + Cursor: hs.ID, + Node: hs, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.HasSLSAConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) HasSlsa(ctx context.Context, filter *model.HasSLSASpec) ([]*model.HasSlsa, error) { @@ -151,10 +328,15 @@ func (c *demoClient) HasSlsa(ctx context.Context, filter *model.HasSLSASpec) ([] if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addSLSAIfMatch(ctx, out, filter, link) + hs, err := c.addSLSAIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if hs == nil { + continue + } + + out = append(out, hs) } } else { var done bool @@ -171,10 +353,15 @@ func (c *demoClient) HasSlsa(ctx context.Context, filter *model.HasSLSASpec) ([] if err != nil { return nil, err } - out, err = c.addSLSAIfMatch(ctx, out, filter, link) + hs, err := c.addSLSAIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if hs == nil { + continue + } + + out = append(out, hs) } } } @@ -351,9 +538,8 @@ func (c *demoClient) convSLSA(ctx context.Context, in *hasSLSAStruct) (*model.Ha }, nil } -func (c *demoClient) addSLSAIfMatch(ctx context.Context, out []*model.HasSlsa, - filter *model.HasSLSASpec, link *hasSLSAStruct) ( - []*model.HasSlsa, error, +func (c *demoClient) addSLSAIfMatch(ctx context.Context, filter *model.HasSLSASpec, link *hasSLSAStruct) ( + *model.HasSlsa, error, ) { bb, err := byIDkv[*builderStruct](ctx, link.BuiltBy, c) if err != nil { @@ -371,11 +557,11 @@ func (c *demoClient) addSLSAIfMatch(ctx context.Context, out []*model.HasSlsa, !matchSLSAPreds(link.Predicates, filter.Predicate) || !c.matchArtifacts(ctx, []*model.ArtifactSpec{filter.Subject}, []string{link.Subject}) || !c.matchArtifacts(ctx, filter.BuiltFrom, link.BuiltFrom) { - return out, nil + return nil, nil } hs, err := c.convSLSA(ctx, link) if err != nil { return nil, err } - return append(out, hs), nil + return hs, nil } diff --git a/pkg/assembler/backends/keyvalue/hasSourceAt.go b/pkg/assembler/backends/keyvalue/hasSourceAt.go index bc687da60e..b5ddc8e5ba 100644 --- a/pkg/assembler/backends/keyvalue/hasSourceAt.go +++ b/pkg/assembler/backends/keyvalue/hasSourceAt.go @@ -18,7 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "time" @@ -151,7 +152,165 @@ func (c *demoClient) ingestHasSourceAt(ctx context.Context, packageArg model.IDo // Query HasSourceAt func (c *demoClient) HasSourceAtList(ctx context.Context, hasSourceAtSpec model.HasSourceAtSpec, after *string, first *int) (*model.HasSourceAtConnection, error) { - return nil, fmt.Errorf("not implemented: HasSourceAtList") + c.m.RLock() + defer c.m.RUnlock() + funcName := "HasSourceAt" + if hasSourceAtSpec.ID != nil { + link, err := byIDkv[*srcMapLink](ctx, *hasSourceAtSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + foundHasSourceAt, err := c.buildHasSourceAt(ctx, link, &hasSourceAtSpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + return &model.HasSourceAtConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(foundHasSourceAt.ID), + EndCursor: ptrfrom.String(foundHasSourceAt.ID), + }, + Edges: []*model.HasSourceAtEdge{ + { + Cursor: foundHasSourceAt.ID, + Node: foundHasSourceAt, + }, + }, + }, nil + } + + edges := make([]*model.HasSourceAtEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + // Cant really search for an exact Pkg, as these can be linked to either + // names or versions, only search Source backedges. + var search []string + foundOne := false + if hasSourceAtSpec.Source != nil { + exactSource, err := c.exactSource(ctx, hasSourceAtSpec.Source) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactSource != nil { + search = append(search, exactSource.SrcMapLinks...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*srcMapLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + src, err := c.srcIfMatch(ctx, &hasSourceAtSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if src == nil { + continue + } + + if (after != nil && src.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HasSourceAtEdge{ + Cursor: src.ID, + Node: src, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HasSourceAtEdge{ + Cursor: src.ID, + Node: src, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + var done bool + scn := c.kv.Keys(hsaCol) + for !done { + var hsaKeys []string + var err error + hsaKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(hsaKeys) + totalCount = len(hsaKeys) + + for i, hsak := range hsaKeys { + link, err := byKeykv[*srcMapLink](ctx, hsaCol, hsak, c) + if err != nil { + return nil, err + } + src, err := c.srcIfMatch(ctx, &hasSourceAtSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if src == nil { + continue + } + + if after != nil && !currentPage { + if src.ID == *after { + totalCount = len(hsaKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HasSourceAtEdge{ + Cursor: src.ID, + Node: src, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HasSourceAtEdge{ + Cursor: src.ID, + Node: src, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.HasSourceAtConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) HasSourceAt(ctx context.Context, filter *model.HasSourceAtSpec) ([]*model.HasSourceAt, error) { @@ -193,10 +352,15 @@ func (c *demoClient) HasSourceAt(ctx context.Context, filter *model.HasSourceAtS if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addSrcIfMatch(ctx, out, filter, link) + src, err := c.srcIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if src == nil { + continue + } + + out = append(out, src) } } else { var done bool @@ -213,10 +377,15 @@ func (c *demoClient) HasSourceAt(ctx context.Context, filter *model.HasSourceAtS if err != nil { return nil, err } - out, err = c.addSrcIfMatch(ctx, out, filter, link) + src, err := c.srcIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if src == nil { + continue + } + + out = append(out, src) } } } @@ -273,30 +442,29 @@ func (c *demoClient) buildHasSourceAt(ctx context.Context, link *srcMapLink, fil return &newHSA, nil } -func (c *demoClient) addSrcIfMatch(ctx context.Context, out []*model.HasSourceAt, - filter *model.HasSourceAtSpec, link *srcMapLink) ( - []*model.HasSourceAt, error) { +func (c *demoClient) srcIfMatch(ctx context.Context, filter *model.HasSourceAtSpec, link *srcMapLink) ( + *model.HasSourceAt, error) { if filter != nil && noMatch(filter.Justification, link.Justification) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Origin, link.Origin) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Collector, link.Collector) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.DocumentRef, link.DocumentRef) { - return out, nil + return nil, nil } if filter != nil && filter.KnownSince != nil && !filter.KnownSince.Equal(link.KnownSince) { - return out, nil + return nil, nil } foundHasSourceAt, err := c.buildHasSourceAt(ctx, link, filter, false) if err != nil { return nil, err } if foundHasSourceAt == nil { - return out, nil + return nil, nil } - return append(out, foundHasSourceAt), nil + return foundHasSourceAt, nil } diff --git a/pkg/assembler/backends/keyvalue/hashEqual.go b/pkg/assembler/backends/keyvalue/hashEqual.go index 76a09a97d6..99ff4f3885 100644 --- a/pkg/assembler/backends/keyvalue/hashEqual.go +++ b/pkg/assembler/backends/keyvalue/hashEqual.go @@ -19,7 +19,9 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" "slices" + "sort" "strings" "github.com/guacsec/guac/pkg/assembler/graphql/model" @@ -184,7 +186,168 @@ func (c *demoClient) matchArtifacts(ctx context.Context, filter []*model.Artifac // Query HashEqual func (c *demoClient) HashEqualList(ctx context.Context, hashEqualSpec model.HashEqualSpec, after *string, first *int) (*model.HashEqualConnection, error) { - return nil, fmt.Errorf("not implemented: HashEqualList") + funcName := "HashEqual" + c.m.RLock() + defer c.m.RUnlock() + if hashEqualSpec.ID != nil { + link, err := byIDkv[*hashEqualStruct](ctx, *hashEqualSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + he, err := c.convHashEqual(ctx, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + return &model.HashEqualConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(he.ID), + EndCursor: ptrfrom.String(he.ID), + }, + Edges: []*model.HashEqualEdge{ + { + Cursor: he.ID, + Node: he, + }, + }, + }, nil + } + + edges := make([]*model.HashEqualEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + for _, a := range hashEqualSpec.Artifacts { + if !foundOne && a != nil { + exactArtifact, err := c.artifactExact(ctx, a) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactArtifact != nil { + search = append(search, exactArtifact.HashEquals...) + foundOne = true + break + } + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*hashEqualStruct](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + he, err := c.hashEqualsIfMatch(ctx, &hashEqualSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if he == nil { + continue + } + + if (after != nil && he.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HashEqualEdge{ + Cursor: he.ID, + Node: he, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HashEqualEdge{ + Cursor: he.ID, + Node: he, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(hashEqCol) + for !done { + var heKeys []string + var err error + heKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(heKeys) + totalCount = len(heKeys) + + for i, hek := range heKeys { + link, err := byKeykv[*hashEqualStruct](ctx, hashEqCol, hek, c) + if err != nil { + return nil, err + } + he, err := c.hashEqualsIfMatch(ctx, &hashEqualSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if he == nil { + continue + } + + if after != nil && !currentPage { + if he.ID == *after { + totalCount = len(heKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.HashEqualEdge{ + Cursor: he.ID, + Node: he, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.HashEqualEdge{ + Cursor: he.ID, + Node: he, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.HashEqualConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) HashEqual(ctx context.Context, filter *model.HashEqualSpec) ([]*model.HashEqual, error) { @@ -228,10 +391,16 @@ func (c *demoClient) HashEqual(ctx context.Context, filter *model.HashEqualSpec) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addHEIfMatch(ctx, out, filter, link) + he, err := c.hashEqualsIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if he == nil { + continue + } + + out = append(out, he) } } else { var done bool @@ -248,10 +417,16 @@ func (c *demoClient) HashEqual(ctx context.Context, filter *model.HashEqualSpec) if err != nil { return nil, err } - out, err = c.addHEIfMatch(ctx, out, filter, link) + he, err := c.hashEqualsIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + + if he == nil { + continue + } + + out = append(out, he) } } } @@ -277,20 +452,19 @@ func (c *demoClient) convHashEqual(ctx context.Context, h *hashEqualStruct) (*mo }, nil } -func (c *demoClient) addHEIfMatch(ctx context.Context, out []*model.HashEqual, - filter *model.HashEqualSpec, link *hashEqualStruct) ( - []*model.HashEqual, error, +func (c *demoClient) hashEqualsIfMatch(ctx context.Context, filter *model.HashEqualSpec, link *hashEqualStruct) ( + *model.HashEqual, error, ) { if noMatch(filter.Justification, link.Justification) || noMatch(filter.Origin, link.Origin) || noMatch(filter.Collector, link.Collector) || noMatch(filter.DocumentRef, link.DocumentRef) || !c.matchArtifacts(ctx, filter.Artifacts, link.Artifacts) { - return out, nil + return nil, nil } he, err := c.convHashEqual(ctx, link) if err != nil { return nil, err } - return append(out, he), nil + return he, nil } diff --git a/pkg/assembler/backends/keyvalue/isDependency.go b/pkg/assembler/backends/keyvalue/isDependency.go index e7f5909ae1..68a9862d26 100644 --- a/pkg/assembler/backends/keyvalue/isDependency.go +++ b/pkg/assembler/backends/keyvalue/isDependency.go @@ -18,7 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "github.com/vektah/gqlparser/v2/gqlerror" @@ -152,7 +153,167 @@ func (c *demoClient) ingestDependency(ctx context.Context, packageArg model.IDor // Query IsDependency func (c *demoClient) IsDependencyList(ctx context.Context, isDependencySpec model.IsDependencySpec, after *string, first *int) (*model.IsDependencyConnection, error) { - return nil, fmt.Errorf("not implemented: IsDependencyList") + c.m.RLock() + defer c.m.RUnlock() + funcName := "IsDependency" + + if isDependencySpec.ID != nil { + link, err := byIDkv[*isDependencyLink](ctx, *isDependencySpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + foundIsDependency, err := c.buildIsDependency(ctx, link, &isDependencySpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + return &model.IsDependencyConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(foundIsDependency.ID), + EndCursor: ptrfrom.String(foundIsDependency.ID), + }, + Edges: []*model.IsDependencyEdge{ + { + Cursor: foundIsDependency.ID, + Node: foundIsDependency, + }, + }, + }, nil + } + + edges := make([]*model.IsDependencyEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + if isDependencySpec.Package != nil { + pkgs, err := c.findPackageVersion(ctx, isDependencySpec.Package) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + foundOne = len(pkgs) > 0 + for _, pkg := range pkgs { + search = append(search, pkg.IsDependencyLinks...) + } + } + // Dont search on DependencyPackage as it can be either package-name or package-version + + if foundOne { + for _, id := range search { + link, err := byIDkv[*isDependencyLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + dep, err := c.depIfMatch(ctx, &isDependencySpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if dep == nil { + continue + } + + if (after != nil && dep.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.IsDependencyEdge{ + Cursor: dep.ID, + Node: dep, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.IsDependencyEdge{ + Cursor: dep.ID, + Node: dep, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(isDepCol) + for !done { + var depKeys []string + var err error + depKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(depKeys) + totalCount = len(depKeys) + + for i, depKey := range depKeys { + link, err := byKeykv[*isDependencyLink](ctx, isDepCol, depKey, c) + if err != nil { + return nil, err + } + dep, err := c.depIfMatch(ctx, &isDependencySpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if dep == nil { + continue + } + + if after != nil && !currentPage { + if dep.ID == *after { + totalCount = len(depKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.IsDependencyEdge{ + Cursor: dep.ID, + Node: dep, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.IsDependencyEdge{ + Cursor: dep.ID, + Node: dep, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.IsDependencyConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) IsDependency(ctx context.Context, filter *model.IsDependencySpec) ([]*model.IsDependency, error) { @@ -194,10 +355,15 @@ func (c *demoClient) IsDependency(ctx context.Context, filter *model.IsDependenc if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addDepIfMatch(ctx, out, filter, link) + dep, err := c.depIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if dep == nil { + continue + } + + out = append(out, dep) } } else { var done bool @@ -214,10 +380,15 @@ func (c *demoClient) IsDependency(ctx context.Context, filter *model.IsDependenc if err != nil { return nil, err } - out, err = c.addDepIfMatch(ctx, out, filter, link) + dep, err := c.depIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if dep == nil { + continue + } + + out = append(out, dep) } } } @@ -273,11 +444,10 @@ func (c *demoClient) buildIsDependency(ctx context.Context, link *isDependencyLi return &foundIsDependency, nil } -func (c *demoClient) addDepIfMatch(ctx context.Context, out []*model.IsDependency, - filter *model.IsDependencySpec, link *isDependencyLink) ( - []*model.IsDependency, error) { +func (c *demoClient) depIfMatch(ctx context.Context, filter *model.IsDependencySpec, link *isDependencyLink) ( + *model.IsDependency, error) { if noMatchIsDep(filter, link) { - return out, nil + return nil, nil } foundIsDependency, err := c.buildIsDependency(ctx, link, filter, false) @@ -285,9 +455,9 @@ func (c *demoClient) addDepIfMatch(ctx context.Context, out []*model.IsDependenc return nil, err } if foundIsDependency == nil { - return out, nil + return nil, nil } - return append(out, foundIsDependency), nil + return foundIsDependency, nil } func noMatchIsDep(filter *model.IsDependencySpec, link *isDependencyLink) bool { diff --git a/pkg/assembler/backends/keyvalue/isOccurrence.go b/pkg/assembler/backends/keyvalue/isOccurrence.go index 551f1e04d7..9fb9148d19 100644 --- a/pkg/assembler/backends/keyvalue/isOccurrence.go +++ b/pkg/assembler/backends/keyvalue/isOccurrence.go @@ -18,7 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "github.com/vektah/gqlparser/v2/gqlerror" @@ -237,7 +238,188 @@ func (c *demoClient) artifactMatch(ctx context.Context, aID string, artifactSpec // Query IsOccurrence func (c *demoClient) IsOccurrenceList(ctx context.Context, isOccurrenceSpec model.IsOccurrenceSpec, after *string, first *int) (*model.IsOccurrenceConnection, error) { - return nil, fmt.Errorf("not implemented: IsOccurrenceList") + funcName := "IsOccurrence" + + c.m.RLock() + defer c.m.RUnlock() + + if isOccurrenceSpec.ID != nil { + link, err := byIDkv[*isOccurrenceStruct](ctx, *isOccurrenceSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + o, err := c.convOccurrence(ctx, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + return &model.IsOccurrenceConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(o.ID), + EndCursor: ptrfrom.String(o.ID), + }, + Edges: []*model.IsOccurrenceEdge{ + { + Cursor: o.ID, + Node: o, + }, + }, + }, nil + } + + edges := make([]*model.IsOccurrenceEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + if isOccurrenceSpec.Artifact != nil { + exactArtifact, err := c.artifactExact(ctx, isOccurrenceSpec.Artifact) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactArtifact != nil { + search = append(search, exactArtifact.Occurrences...) + foundOne = true + } + } + if !foundOne && isOccurrenceSpec.Subject != nil && isOccurrenceSpec.Subject.Package != nil { + pkgs, err := c.findPackageVersion(ctx, isOccurrenceSpec.Subject.Package) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + foundOne = len(pkgs) > 0 + for _, pkg := range pkgs { + search = append(search, pkg.Occurrences...) + } + } + if !foundOne && isOccurrenceSpec.Subject != nil && isOccurrenceSpec.Subject.Source != nil { + exactSource, err := c.exactSource(ctx, isOccurrenceSpec.Subject.Source) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactSource != nil { + search = append(search, exactSource.Occurrences...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*isOccurrenceStruct](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + occ, err := c.occIfMatch(ctx, &isOccurrenceSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if occ == nil { + continue + } + + if (after != nil && occ.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.IsOccurrenceEdge{ + Cursor: occ.ID, + Node: occ, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.IsOccurrenceEdge{ + Cursor: occ.ID, + Node: occ, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(occCol) + for !done { + var occKeys []string + var err error + occKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(occKeys) + totalCount = len(occKeys) + + for i, ok := range occKeys { + link, err := byKeykv[*isOccurrenceStruct](ctx, occCol, ok, c) + if err != nil { + return nil, err + } + occ, err := c.occIfMatch(ctx, &isOccurrenceSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if occ == nil { + continue + } + + if after != nil && !currentPage { + if occ.ID == *after { + totalCount = len(occKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.IsOccurrenceEdge{ + Cursor: occ.ID, + Node: occ, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.IsOccurrenceEdge{ + Cursor: occ.ID, + Node: occ, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.IsOccurrenceConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) IsOccurrence(ctx context.Context, filter *model.IsOccurrenceSpec) ([]*model.IsOccurrence, error) { @@ -300,10 +482,15 @@ func (c *demoClient) IsOccurrence(ctx context.Context, filter *model.IsOccurrenc if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addOccIfMatch(ctx, out, filter, link) + occ, err := c.occIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if occ == nil { + continue + } + + out = append(out, occ) } } else { var done bool @@ -320,51 +507,55 @@ func (c *demoClient) IsOccurrence(ctx context.Context, filter *model.IsOccurrenc if err != nil { return nil, err } - out, err = c.addOccIfMatch(ctx, out, filter, link) + occ, err := c.occIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if occ == nil { + continue + } + + out = append(out, occ) } } } return out, nil } -func (c *demoClient) addOccIfMatch(ctx context.Context, out []*model.IsOccurrence, - filter *model.IsOccurrenceSpec, link *isOccurrenceStruct) ( - []*model.IsOccurrence, error) { +func (c *demoClient) occIfMatch(ctx context.Context, filter *model.IsOccurrenceSpec, link *isOccurrenceStruct) ( + *model.IsOccurrence, error) { if noMatch(filter.Justification, link.Justification) || noMatch(filter.Origin, link.Origin) || noMatch(filter.Collector, link.Collector) || noMatch(filter.DocumentRef, link.DocumentRef) { - return out, nil + return nil, nil } if filter.Artifact != nil && !c.artifactMatch(ctx, link.Artifact, filter.Artifact) { - return out, nil + return nil, nil } if filter.Subject != nil { if filter.Subject.Package != nil { if link.Pkg == "" { - return out, nil + return nil, nil } p, err := c.buildPackageResponse(ctx, link.Pkg, filter.Subject.Package) if err != nil { return nil, err } if p == nil { - return out, nil + return nil, nil } } else if filter.Subject.Source != nil { if link.Source == "" { - return out, nil + return nil, nil } s, err := c.buildSourceResponse(ctx, link.Source, filter.Subject.Source) if err != nil { return nil, err } if s == nil { - return out, nil + return nil, nil } } } @@ -372,7 +563,7 @@ func (c *demoClient) addOccIfMatch(ctx context.Context, out []*model.IsOccurrenc if err != nil { return nil, err } - return append(out, o), nil + return o, nil } func (c *demoClient) matchOccurrences(ctx context.Context, filters []*model.IsOccurrenceSpec, occLinkIDs []string) bool { diff --git a/pkg/assembler/backends/keyvalue/license.go b/pkg/assembler/backends/keyvalue/license.go index 8d6b197cf1..490f0d03c8 100644 --- a/pkg/assembler/backends/keyvalue/license.go +++ b/pkg/assembler/backends/keyvalue/license.go @@ -18,7 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "github.com/vektah/gqlparser/v2/gqlerror" @@ -159,7 +160,110 @@ func (c *demoClient) licenseExact(ctx context.Context, licenseSpec *model.Licens // Query Licenses func (c *demoClient) LicenseList(ctx context.Context, licenseSpec model.LicenseSpec, after *string, first *int) (*model.LicenseConnection, error) { - return nil, fmt.Errorf("not implemented: LicenseList") + c.m.RLock() + defer c.m.RUnlock() + a, err := c.licenseExact(ctx, &licenseSpec) + if err != nil { + return nil, gqlerror.Errorf("Licenses :: invalid spec %s", err) + } + if a != nil { + license := c.convLicense(a) + return &model.LicenseConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(license.ID), + EndCursor: ptrfrom.String(license.ID), + }, + Edges: []*model.LicenseEdge{ + { + Cursor: license.ID, + Node: license, + }, + }, + }, nil + } + + edges := make([]*model.LicenseEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + + var done bool + scn := c.kv.Keys(licenseCol) + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + for !done { + var lKeys []string + lKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(lKeys) + totalCount = len(lKeys) + + for i, lk := range lKeys { + l, err := byKeykv[*licStruct](ctx, licenseCol, lk, c) + if err != nil { + return nil, err + } + if noMatch(licenseSpec.Name, l.Name) || + noMatch(licenseSpec.ListVersion, l.ListVersion) || + noMatch(licenseSpec.Inline, l.Inline) { + continue + } + + license := c.convLicense(l) + + if license == nil { + continue + } + + if after != nil && !currentPage { + if license.ID == *after { + totalCount = len(lKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.LicenseEdge{ + Cursor: license.ID, + Node: license, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.LicenseEdge{ + Cursor: license.ID, + Node: license, + }) + } + } + } + + if len(edges) != 0 { + return &model.LicenseConnection{ + TotalCount: totalCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) Licenses(ctx context.Context, licenseSpec *model.LicenseSpec) ([]*model.License, error) { diff --git a/pkg/assembler/backends/keyvalue/pkg.go b/pkg/assembler/backends/keyvalue/pkg.go index ea126fe06f..b80b261fce 100644 --- a/pkg/assembler/backends/keyvalue/pkg.go +++ b/pkg/assembler/backends/keyvalue/pkg.go @@ -19,8 +19,10 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" "reflect" "slices" + "sort" "strings" "github.com/vektah/gqlparser/v2/gqlerror" @@ -523,7 +525,191 @@ func hashVersionHelper(version string, subpath string, qualifiers map[string]str // Query Package func (c *demoClient) PackagesList(ctx context.Context, pkgSpec model.PkgSpec, after *string, first *int) (*model.PackageConnection, error) { - return nil, fmt.Errorf("not implemented: PackagesList") + c.m.RLock() + defer c.m.RUnlock() + if pkgSpec.ID != nil { + p, err := c.buildPackageResponse(ctx, *pkgSpec.ID, &pkgSpec) + if err != nil { + if errors.Is(err, errNotFound) { + // not found + return nil, nil + } + return nil, err + } + + return &model.PackageConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(p.ID), + EndCursor: ptrfrom.String(p.ID), + }, + Edges: []*model.PackageEdge{ + { + Cursor: p.ID, + Node: p, + }, + }, + }, nil + } + + edges := make([]*model.PackageEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + if pkgSpec.Type != nil { + inType := &pkgType{ + Type: *pkgSpec.Type, + } + pkgTypeNode, err := byKeykv[*pkgType](ctx, pkgTypeCol, inType.Key(), c) + if err == nil { + pNamespaces := c.buildPkgNamespace(ctx, pkgTypeNode, &pkgSpec) + for _, namespace := range pNamespaces { + for _, name := range namespace.Names { + for _, version := range name.Versions { + p := &model.Package{ + ID: pkgTypeNode.ThisID, + Type: pkgTypeNode.Type, + Namespaces: []*model.PackageNamespace{ + { + ID: namespace.ID, + Namespace: namespace.Namespace, + Names: []*model.PackageName{ + { + ID: name.ID, + Name: name.Name, + Versions: []*model.PackageVersion{ + version, + }, + }, + }, + }, + }, + } + + if (after != nil && pNamespaces[0].Names[0].Versions[0].ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.PackageEdge{ + Cursor: pNamespaces[0].Names[0].Versions[0].ID, + Node: p, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.PackageEdge{ + Cursor: pNamespaces[0].Names[0].Versions[0].ID, + Node: p, + }) + } + } + } + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(pkgTypeCol) + for !done { + var typeKeys []string + var err error + typeKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(typeKeys) + totalCount = len(typeKeys) + + for i, tk := range typeKeys { + pkgTypeNode, err := byKeykv[*pkgType](ctx, pkgTypeCol, tk, c) + if err != nil { + return nil, err + } + pNamespaces := c.buildPkgNamespace(ctx, pkgTypeNode, &pkgSpec) + if len(pNamespaces) == 0 { + continue + } + + for _, namespace := range pNamespaces { + for _, name := range namespace.Names { + for _, version := range name.Versions { + p := &model.Package{ + ID: pkgTypeNode.ThisID, + Type: pkgTypeNode.Type, + Namespaces: []*model.PackageNamespace{ + { + ID: namespace.ID, + Namespace: namespace.Namespace, + Names: []*model.PackageName{ + { + ID: name.ID, + Name: name.Name, + Versions: []*model.PackageVersion{ + version, + }, + }, + }, + }, + }, + } + + if after != nil && !currentPage { + if p.Namespaces[0].Names[0].Versions[0].ID == *after { + totalCount = len(typeKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.PackageEdge{ + Cursor: p.Namespaces[0].Names[0].Versions[0].ID, + Node: p, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.PackageEdge{ + Cursor: p.Namespaces[0].Names[0].Versions[0].ID, + Node: p, + }) + } + } + } + } + } + } + } + + if len(edges) != 0 { + return &model.PackageConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) Packages(ctx context.Context, filter *model.PkgSpec) ([]*model.Package, error) { diff --git a/pkg/assembler/backends/keyvalue/pkgEqual.go b/pkg/assembler/backends/keyvalue/pkgEqual.go index 404cc2be82..905ca4a984 100644 --- a/pkg/assembler/backends/keyvalue/pkgEqual.go +++ b/pkg/assembler/backends/keyvalue/pkgEqual.go @@ -19,7 +19,9 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" "slices" + "sort" "strings" "github.com/guacsec/guac/pkg/assembler/graphql/model" @@ -154,7 +156,163 @@ func (c *demoClient) ingestPkgEqual(ctx context.Context, pkg model.IDorPkgInput, // Query PkgEqual func (c *demoClient) PkgEqualList(ctx context.Context, pkgEqualSpec model.PkgEqualSpec, after *string, first *int) (*model.PkgEqualConnection, error) { - return nil, fmt.Errorf("not implemented: PkgEqualList") + funcName := "PkgEqual" + c.m.RLock() + defer c.m.RUnlock() + if pkgEqualSpec.ID != nil { + link, err := byIDkv[*pkgEqualStruct](ctx, *pkgEqualSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + pe, err := c.convPkgEqual(ctx, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + return &model.PkgEqualConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(pe.ID), + EndCursor: ptrfrom.String(pe.ID), + }, + Edges: []*model.PkgEqualEdge{ + { + Cursor: pe.ID, + Node: pe, + }, + }, + }, nil + } + + edges := make([]*model.PkgEqualEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + for _, p := range pkgEqualSpec.Packages { + pkgs, err := c.findPackageVersion(ctx, p) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + for _, pkg := range pkgs { + search = append(search, pkg.PkgEquals...) + } + } + + if len(search) > 0 { + for _, id := range search { + link, err := byIDkv[*pkgEqualStruct](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + pe, err := c.peIfMatch(ctx, &pkgEqualSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if pe == nil { + continue + } + + if (after != nil && pe.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.PkgEqualEdge{ + Cursor: pe.ID, + Node: pe, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.PkgEqualEdge{ + Cursor: pe.ID, + Node: pe, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(pkgEqCol) + for !done { + var peKeys []string + var err error + peKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(peKeys) + totalCount = len(peKeys) + + for i, pek := range peKeys { + link, err := byKeykv[*pkgEqualStruct](ctx, pkgEqCol, pek, c) + if err != nil { + return nil, err + } + pe, err := c.peIfMatch(ctx, &pkgEqualSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if pe == nil { + continue + } + + if after != nil && !currentPage { + if pe.ID == *after { + totalCount = len(peKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.PkgEqualEdge{ + Cursor: pe.ID, + Node: pe, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.PkgEqualEdge{ + Cursor: pe.ID, + Node: pe, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.PkgEqualConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) PkgEqual(ctx context.Context, filter *model.PkgEqualSpec) ([]*model.PkgEqual, error) { @@ -193,10 +351,15 @@ func (c *demoClient) PkgEqual(ctx context.Context, filter *model.PkgEqualSpec) ( if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addCPIfMatch(ctx, out, filter, link) + pe, err := c.peIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if pe == nil { + continue + } + + out = append(out, pe) } } else { var done bool @@ -213,25 +376,29 @@ func (c *demoClient) PkgEqual(ctx context.Context, filter *model.PkgEqualSpec) ( if err != nil { return nil, err } - out, err = c.addCPIfMatch(ctx, out, filter, link) + pe, err := c.peIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if pe == nil { + continue + } + + out = append(out, pe) } } } return out, nil } -func (c *demoClient) addCPIfMatch(ctx context.Context, out []*model.PkgEqual, - filter *model.PkgEqualSpec, link *pkgEqualStruct) ( - []*model.PkgEqual, error, +func (c *demoClient) peIfMatch(ctx context.Context, filter *model.PkgEqualSpec, link *pkgEqualStruct) ( + *model.PkgEqual, error, ) { if noMatch(filter.Justification, link.Justification) || noMatch(filter.Origin, link.Origin) || noMatch(filter.Collector, link.Collector) || noMatch(filter.DocumentRef, link.DocumentRef) { - return out, nil + return nil, nil } for _, ps := range filter.Packages { if ps == nil { @@ -250,12 +417,12 @@ func (c *demoClient) addCPIfMatch(ctx context.Context, out []*model.PkgEqual, } } if !found { - return out, nil + return nil, nil } } pe, err := c.convPkgEqual(ctx, link) if err != nil { return nil, err } - return append(out, pe), nil + return pe, nil } diff --git a/pkg/assembler/backends/keyvalue/pointOfContact.go b/pkg/assembler/backends/keyvalue/pointOfContact.go index 1dda7e3e4a..c32fbeff80 100644 --- a/pkg/assembler/backends/keyvalue/pointOfContact.go +++ b/pkg/assembler/backends/keyvalue/pointOfContact.go @@ -18,7 +18,8 @@ package keyvalue import ( "context" "errors" - "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "time" @@ -199,7 +200,179 @@ func (c *demoClient) ingestPointOfContact(ctx context.Context, subject model.Pac // Query PointOfContact func (c *demoClient) PointOfContactList(ctx context.Context, pointOfContactSpec model.PointOfContactSpec, after *string, first *int) (*model.PointOfContactConnection, error) { - return nil, fmt.Errorf("not implemented: PointOfContactList") + funcName := "PointOfContact" + + c.m.RLock() + defer c.m.RUnlock() + + if pointOfContactSpec.ID != nil { + link, err := byIDkv[*pointOfContactLink](ctx, *pointOfContactSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + found, err := c.buildPointOfContact(ctx, link, &pointOfContactSpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + return &model.PointOfContactConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(found.ID), + EndCursor: ptrfrom.String(found.ID), + }, + Edges: []*model.PointOfContactEdge{ + { + Cursor: found.ID, + Node: found, + }, + }, + }, nil + } + + edges := make([]*model.PointOfContactEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + // Cant really search for an exact Pkg, as these can be linked to either + // names or versions, and version could be empty. + var search []string + foundOne := false + if pointOfContactSpec.Subject != nil && pointOfContactSpec.Subject.Artifact != nil { + exactArtifact, err := c.artifactExact(ctx, pointOfContactSpec.Subject.Artifact) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactArtifact != nil { + search = append(search, exactArtifact.PointOfContactLinks...) + foundOne = true + } + } + if !foundOne && pointOfContactSpec.Subject != nil && pointOfContactSpec.Subject.Source != nil { + exactSource, err := c.exactSource(ctx, pointOfContactSpec.Subject.Source) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactSource != nil { + search = append(search, exactSource.PointOfContactLinks...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*pointOfContactLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + poc, err := c.pointOfContactIfMatch(ctx, &pointOfContactSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if poc == nil { + continue + } + + if (after != nil && poc.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.PointOfContactEdge{ + Cursor: poc.ID, + Node: poc, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.PointOfContactEdge{ + Cursor: poc.ID, + Node: poc, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(pocCol) + for !done { + var pocKeys []string + var err error + pocKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(pocKeys) + totalCount = len(pocKeys) + + for i, pk := range pocKeys { + link, err := byKeykv[*pointOfContactLink](ctx, pocCol, pk, c) + if err != nil { + return nil, err + } + poc, err := c.pointOfContactIfMatch(ctx, &pointOfContactSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if poc == nil { + continue + } + + if after != nil && !currentPage { + if poc.ID == *after { + totalCount = len(pocKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.PointOfContactEdge{ + Cursor: poc.ID, + Node: poc, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.PointOfContactEdge{ + Cursor: poc.ID, + Node: poc, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.PointOfContactConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) PointOfContact(ctx context.Context, filter *model.PointOfContactSpec) ([]*model.PointOfContact, error) { @@ -253,10 +426,15 @@ func (c *demoClient) PointOfContact(ctx context.Context, filter *model.PointOfCo if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addPOCIfMatch(ctx, out, filter, link) + poc, err := c.pointOfContactIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if poc == nil { + continue + } + + out = append(out, poc) } } else { var done bool @@ -273,40 +451,45 @@ func (c *demoClient) PointOfContact(ctx context.Context, filter *model.PointOfCo if err != nil { return nil, err } - out, err = c.addPOCIfMatch(ctx, out, filter, link) + poc, err := c.pointOfContactIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if poc == nil { + continue + } + + out = append(out, poc) } } } return out, nil } -func (c *demoClient) addPOCIfMatch(ctx context.Context, out []*model.PointOfContact, filter *model.PointOfContactSpec, link *pointOfContactLink) ( - []*model.PointOfContact, error) { +func (c *demoClient) pointOfContactIfMatch(ctx context.Context, filter *model.PointOfContactSpec, link *pointOfContactLink) ( + *model.PointOfContact, error) { if filter != nil && noMatch(filter.Justification, link.Justification) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Collector, link.Collector) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Origin, link.Origin) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Email, link.Email) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Info, link.Info) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.DocumentRef, link.DocumentRef) { - return out, nil + return nil, nil } // no match if filter time since is after the timestamp if filter != nil && filter.Since != nil && filter.Since.After(link.Since) { - return out, nil + return nil, nil } found, err := c.buildPointOfContact(ctx, link, filter, false) @@ -314,9 +497,9 @@ func (c *demoClient) addPOCIfMatch(ctx context.Context, out []*model.PointOfCont return nil, err } if found == nil { - return out, nil + return nil, nil } - return append(out, found), nil + return found, nil } func (c *demoClient) buildPointOfContact(ctx context.Context, link *pointOfContactLink, filter *model.PointOfContactSpec, ingestOrIDProvided bool) (*model.PointOfContact, error) { diff --git a/pkg/assembler/backends/keyvalue/src.go b/pkg/assembler/backends/keyvalue/src.go index caa2d011d5..5a8c40b111 100644 --- a/pkg/assembler/backends/keyvalue/src.go +++ b/pkg/assembler/backends/keyvalue/src.go @@ -19,6 +19,8 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" + "sort" "strings" "github.com/vektah/gqlparser/v2/gqlerror" @@ -312,7 +314,171 @@ func (c *demoClient) IngestSource(ctx context.Context, input model.IDorSourceInp // Query Source func (c *demoClient) SourcesList(ctx context.Context, sourceSpec model.SourceSpec, after *string, first *int) (*model.SourceConnection, error) { - return nil, fmt.Errorf("not implemented: SourcesList") + c.m.RLock() + defer c.m.RUnlock() + if sourceSpec.ID != nil { + s, err := c.buildSourceResponse(ctx, *sourceSpec.ID, &sourceSpec) + if err != nil { + if errors.Is(err, errNotFound) { + // not found + return nil, nil + } + return nil, err + } + + return &model.SourceConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(s.ID), + EndCursor: ptrfrom.String(s.ID), + }, + Edges: []*model.SourceEdge{ + { + Cursor: s.ID, + Node: s, + }, + }, + }, nil + } + + edges := make([]*model.SourceEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + if sourceSpec.Type != nil { + inType := &srcType{ + Type: *sourceSpec.Type, + } + srcTypeNode, err := byKeykv[*srcType](ctx, srcTypeCol, inType.Key(), c) + if err == nil { + sNamespaces := c.buildSourceNamespace(ctx, srcTypeNode, &sourceSpec) + for _, namespace := range sNamespaces { + for _, name := range namespace.Names { + s := &model.Source{ + ID: srcTypeNode.ThisID, + Type: srcTypeNode.Type, + Namespaces: []*model.SourceNamespace{ + { + ID: namespace.ID, + Namespace: namespace.Namespace, + Names: []*model.SourceName{ + name, + }, + }, + }, + } + + if (after != nil && sNamespaces[0].Names[0].ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.SourceEdge{ + Cursor: sNamespaces[0].Names[0].ID, + Node: s, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.SourceEdge{ + Cursor: sNamespaces[0].Names[0].ID, + Node: s, + }) + } + } + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(srcTypeCol) + for !done { + var typeKeys []string + var err error + typeKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(typeKeys) + totalCount = len(typeKeys) + + for i, tk := range typeKeys { + srcTypeNode, err := byKeykv[*srcType](ctx, srcTypeCol, tk, c) + if err != nil { + return nil, err + } + sNamespaces := c.buildSourceNamespace(ctx, srcTypeNode, &sourceSpec) + for _, namespace := range sNamespaces { + for _, name := range namespace.Names { + s := &model.Source{ + ID: srcTypeNode.ThisID, + Type: srcTypeNode.Type, + Namespaces: []*model.SourceNamespace{ + { + ID: namespace.ID, + Namespace: namespace.Namespace, + Names: []*model.SourceName{ + name, + }, + }, + }, + } + + if after != nil && !currentPage { + if s.Namespaces[0].Names[0].ID == *after { + totalCount = len(typeKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.SourceEdge{ + Cursor: s.Namespaces[0].Names[0].ID, + Node: s, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.SourceEdge{ + Cursor: s.Namespaces[0].Names[0].ID, + Node: s, + }) + } + } + } + } + } + } + + if len(edges) != 0 { + return &model.SourceConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) Sources(ctx context.Context, filter *model.SourceSpec) ([]*model.Source, error) { diff --git a/pkg/assembler/backends/keyvalue/vulnEqual.go b/pkg/assembler/backends/keyvalue/vulnEqual.go index 683639ea6b..4a74fb616d 100644 --- a/pkg/assembler/backends/keyvalue/vulnEqual.go +++ b/pkg/assembler/backends/keyvalue/vulnEqual.go @@ -19,7 +19,9 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" "slices" + "sort" "strings" "github.com/guacsec/guac/pkg/assembler/graphql/model" @@ -155,7 +157,168 @@ func (c *demoClient) convVulnEqual(ctx context.Context, in *vulnerabilityEqualLi // Query VulnEqual func (c *demoClient) VulnEqualList(ctx context.Context, vulnEqualSpec model.VulnEqualSpec, after *string, first *int) (*model.VulnEqualConnection, error) { - return nil, fmt.Errorf("not implemented: VulnEqualList") + funcName := "VulnEqual" + c.m.RLock() + defer c.m.RUnlock() + if vulnEqualSpec.ID != nil { + link, err := byIDkv[*vulnerabilityEqualLink](ctx, *vulnEqualSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + ve, err := c.convVulnEqual(ctx, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + return &model.VulnEqualConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(ve.ID), + EndCursor: ptrfrom.String(ve.ID), + }, + Edges: []*model.VulnEqualEdge{ + { + Cursor: ve.ID, + Node: ve, + }, + }, + }, nil + } + + edges := make([]*model.VulnEqualEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + for _, v := range vulnEqualSpec.Vulnerabilities { + if !foundOne { + exactVuln, err := c.exactVulnerability(ctx, v) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactVuln != nil { + search = append(search, exactVuln.VulnEqualLinks...) + foundOne = true + break + } + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*vulnerabilityEqualLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + ve, err := c.vulnIfMatch(ctx, &vulnEqualSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if ve == nil { + continue + } + + if (after != nil && ve.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.VulnEqualEdge{ + Cursor: ve.ID, + Node: ve, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.VulnEqualEdge{ + Cursor: ve.ID, + Node: ve, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(vulnEqCol) + for !done { + var veKeys []string + var err error + veKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(veKeys) + totalCount = len(veKeys) + + for i, vek := range veKeys { + link, err := byKeykv[*vulnerabilityEqualLink](ctx, vulnEqCol, vek, c) + if err != nil { + return nil, err + } + ve, err := c.vulnIfMatch(ctx, &vulnEqualSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if ve == nil { + continue + } + + if after != nil && !currentPage { + if ve.ID == *after { + totalCount = len(veKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.VulnEqualEdge{ + Cursor: ve.ID, + Node: ve, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.VulnEqualEdge{ + Cursor: ve.ID, + Node: ve, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.VulnEqualConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) VulnEqual(ctx context.Context, filter *model.VulnEqualSpec) ([]*model.VulnEqual, error) { @@ -199,10 +362,15 @@ func (c *demoClient) VulnEqual(ctx context.Context, filter *model.VulnEqualSpec) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addVulnIfMatch(ctx, out, filter, link) + ve, err := c.vulnIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if ve == nil { + continue + } + + out = append(out, ve) } } else { var done bool @@ -219,25 +387,29 @@ func (c *demoClient) VulnEqual(ctx context.Context, filter *model.VulnEqualSpec) if err != nil { return nil, err } - out, err = c.addVulnIfMatch(ctx, out, filter, link) + ve, err := c.vulnIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if ve == nil { + continue + } + + out = append(out, ve) } } } return out, nil } -func (c *demoClient) addVulnIfMatch(ctx context.Context, out []*model.VulnEqual, - filter *model.VulnEqualSpec, link *vulnerabilityEqualLink) ( - []*model.VulnEqual, error, +func (c *demoClient) vulnIfMatch(ctx context.Context, filter *model.VulnEqualSpec, link *vulnerabilityEqualLink) ( + *model.VulnEqual, error, ) { if noMatch(filter.Justification, link.Justification) || noMatch(filter.Origin, link.Origin) || noMatch(filter.Collector, link.Collector) || noMatch(filter.DocumentRef, link.DocumentRef) { - return out, nil + return nil, nil } for _, vs := range filter.Vulnerabilities { if vs == nil { @@ -254,12 +426,12 @@ func (c *demoClient) addVulnIfMatch(ctx context.Context, out []*model.VulnEqual, } } if !found { - return out, nil + return nil, nil } } ve, err := c.convVulnEqual(ctx, link) if err != nil { return nil, err } - return append(out, ve), nil + return ve, nil } diff --git a/pkg/assembler/backends/keyvalue/vulnMetadata.go b/pkg/assembler/backends/keyvalue/vulnMetadata.go index 7145ce1a04..ed63f2b667 100644 --- a/pkg/assembler/backends/keyvalue/vulnMetadata.go +++ b/pkg/assembler/backends/keyvalue/vulnMetadata.go @@ -19,7 +19,9 @@ import ( "context" "errors" "fmt" + "github.com/guacsec/guac/internal/testing/ptrfrom" "reflect" + "sort" "strings" "time" @@ -133,7 +135,167 @@ func (c *demoClient) ingestVulnerabilityMetadata(ctx context.Context, vulnerabil // Query VulnerabilityMetadata func (c *demoClient) VulnerabilityMetadataList(ctx context.Context, vulnerabilityMetadataSpec model.VulnerabilityMetadataSpec, after *string, first *int) (*model.VulnerabilityMetadataConnection, error) { - return nil, fmt.Errorf("not implemented: CertifyBadList") + c.m.RLock() + defer c.m.RUnlock() + funcName := "VulnerabilityMetadata" + + if vulnerabilityMetadataSpec.ID != nil { + link, err := byIDkv[*vulnerabilityMetadataLink](ctx, *vulnerabilityMetadataSpec.ID, c) + if err != nil { + // Not found + return nil, nil + } + // If found by id, ignore rest of fields in spec and return as a match + foundVulnMetadata, err := c.buildVulnerabilityMetadata(ctx, link, &vulnerabilityMetadataSpec, true) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + return &model.VulnerabilityMetadataConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(foundVulnMetadata.ID), + EndCursor: ptrfrom.String(foundVulnMetadata.ID), + }, + Edges: []*model.VulnerabilityMetadataEdge{ + { + Cursor: foundVulnMetadata.ID, + Node: foundVulnMetadata, + }, + }, + }, nil + } + + edges := make([]*model.VulnerabilityMetadataEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + var search []string + foundOne := false + if !foundOne && vulnerabilityMetadataSpec.Vulnerability != nil { + exactVuln, err := c.exactVulnerability(ctx, vulnerabilityMetadataSpec.Vulnerability) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if exactVuln != nil { + search = append(search, exactVuln.VulnMetadataLinks...) + foundOne = true + } + } + + if foundOne { + for _, id := range search { + link, err := byIDkv[*vulnerabilityMetadataLink](ctx, id, c) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + vmd, err := c.vulnMetadataIfMatch(ctx, &vulnerabilityMetadataSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + if vmd == nil { + continue + } + + if (after != nil && vmd.ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.VulnerabilityMetadataEdge{ + Cursor: vmd.ID, + Node: vmd, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.VulnerabilityMetadataEdge{ + Cursor: vmd.ID, + Node: vmd, + }) + } + } + } + } else { + currentPage := false + + // If no cursor present start from the top + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(vulnMDCol) + for !done { + var vmdKeys []string + var err error + vmdKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(vmdKeys) + totalCount = len(vmdKeys) + + for i, vmdk := range vmdKeys { + link, err := byKeykv[*vulnerabilityMetadataLink](ctx, vulnMDCol, vmdk, c) + if err != nil { + return nil, err + } + vmd, err := c.vulnMetadataIfMatch(ctx, &vulnerabilityMetadataSpec, link) + if err != nil { + return nil, gqlerror.Errorf("%v :: %v", funcName, err) + } + + if vmd == nil { + continue + } + + if after != nil && !currentPage { + if vmd.ID == *after { + totalCount = len(vmdKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.VulnerabilityMetadataEdge{ + Cursor: vmd.ID, + Node: vmd, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.VulnerabilityMetadataEdge{ + Cursor: vmd.ID, + Node: vmd, + }) + } + } + } + } + + if len(edges) != 0 { + return &model.VulnerabilityMetadataConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) VulnerabilityMetadata(ctx context.Context, filter *model.VulnerabilityMetadataSpec) ([]*model.VulnerabilityMetadata, error) { @@ -176,10 +338,15 @@ func (c *demoClient) VulnerabilityMetadata(ctx context.Context, filter *model.Vu if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } - out, err = c.addVulnMetadataMatch(ctx, out, filter, link) + vmd, err := c.vulnMetadataIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if vmd == nil { + continue + } + + out = append(out, vmd) } } else { var done bool @@ -196,10 +363,15 @@ func (c *demoClient) VulnerabilityMetadata(ctx context.Context, filter *model.Vu if err != nil { return nil, err } - out, err = c.addVulnMetadataMatch(ctx, out, filter, link) + vmd, err := c.vulnMetadataIfMatch(ctx, filter, link) if err != nil { return nil, gqlerror.Errorf("%v :: %v", funcName, err) } + if vmd == nil { + continue + } + + out = append(out, vmd) } } } @@ -207,44 +379,43 @@ func (c *demoClient) VulnerabilityMetadata(ctx context.Context, filter *model.Vu return out, nil } -func (c *demoClient) addVulnMetadataMatch(ctx context.Context, out []*model.VulnerabilityMetadata, - filter *model.VulnerabilityMetadataSpec, - link *vulnerabilityMetadataLink) ([]*model.VulnerabilityMetadata, error) { +func (c *demoClient) vulnMetadataIfMatch(ctx context.Context, filter *model.VulnerabilityMetadataSpec, + link *vulnerabilityMetadataLink) (*model.VulnerabilityMetadata, error) { if filter != nil && filter.Comparator != nil { if filter.ScoreValue == nil { - return out, gqlerror.Errorf("comparator set without a vulnerability score being specified") + return nil, gqlerror.Errorf("comparator set without a vulnerability score being specified") } switch *filter.Comparator { case model.ComparatorEqual: if link.ScoreValue != *filter.ScoreValue { - return out, nil + return nil, nil } case model.ComparatorGreater, model.ComparatorGreaterEqual: if link.ScoreValue < *filter.ScoreValue { - return out, nil + return nil, nil } case model.ComparatorLess, model.ComparatorLessEqual: if link.ScoreValue > *filter.ScoreValue { - return out, nil + return nil, nil } } } else { if filter != nil && noMatchFloat(filter.ScoreValue, link.ScoreValue) { - return out, nil + return nil, nil } } if filter != nil && filter.ScoreType != nil && *filter.ScoreType != link.ScoreType { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Collector, link.Collector) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.Origin, link.Origin) { - return out, nil + return nil, nil } if filter != nil && noMatch(filter.DocumentRef, link.DocumentRef) { - return out, nil + return nil, nil } foundVulnMetadata, err := c.buildVulnerabilityMetadata(ctx, link, filter, false) @@ -252,9 +423,9 @@ func (c *demoClient) addVulnMetadataMatch(ctx context.Context, out []*model.Vuln return nil, fmt.Errorf("failed to build vuln metadata node from link") } if foundVulnMetadata == nil || reflect.ValueOf(foundVulnMetadata.Vulnerability).IsNil() { - return out, nil + return nil, nil } - return append(out, foundVulnMetadata), nil + return foundVulnMetadata, nil } func (c *demoClient) buildVulnerabilityMetadata(ctx context.Context, link *vulnerabilityMetadataLink, filter *model.VulnerabilityMetadataSpec, ingestOrIDProvided bool) (*model.VulnerabilityMetadata, error) { diff --git a/pkg/assembler/backends/keyvalue/vulnerability.go b/pkg/assembler/backends/keyvalue/vulnerability.go index 91b5ae7a13..0800126310 100644 --- a/pkg/assembler/backends/keyvalue/vulnerability.go +++ b/pkg/assembler/backends/keyvalue/vulnerability.go @@ -19,6 +19,7 @@ import ( "context" "errors" "fmt" + "sort" "strings" "github.com/vektah/gqlparser/v2/gqlerror" @@ -215,7 +216,168 @@ func (c *demoClient) IngestVulnerability(ctx context.Context, input model.IDorVu // Query Vulnerabilities func (c *demoClient) VulnerabilityList(ctx context.Context, vulnSpec model.VulnerabilitySpec, after *string, first *int) (*model.VulnerabilityConnection, error) { - return nil, fmt.Errorf("not implemented: VulnerabilityList") + c.m.RLock() + defer c.m.RUnlock() + if vulnSpec.ID != nil { + v, err := c.buildVulnResponse(ctx, *vulnSpec.ID, &vulnSpec) + if err != nil { + if errors.Is(err, errNotFound) { + // not found + return nil, nil + } + return nil, err + } + + return &model.VulnerabilityConnection{ + TotalCount: 1, + PageInfo: &model.PageInfo{ + HasNextPage: false, + StartCursor: ptrfrom.String(v.ID), + EndCursor: ptrfrom.String(v.ID), + }, + Edges: []*model.VulnerabilityEdge{ + { + Cursor: v.ID, + Node: v, + }, + }, + }, nil + } + + edges := make([]*model.VulnerabilityEdge, 0) + hasNextPage := false + numNodes := 0 + totalCount := 0 + addToCount := 0 + + if vulnSpec.NoVuln != nil && !*vulnSpec.NoVuln { + if vulnSpec.Type != nil && *vulnSpec.Type == noVulnType { + return nil, gqlerror.Errorf("novuln boolean set to false, cannot specify vulnerability type to be novuln") + } + } + + // if novuln is specified, retrieve all "novuln" type nodes + if vulnSpec.NoVuln != nil && *vulnSpec.NoVuln { + vulnSpec.Type = ptrfrom.String(noVulnType) + vulnSpec.VulnerabilityID = ptrfrom.String("") + } + + if vulnSpec.Type != nil { + inType := &vulnTypeStruct{ + Type: strings.ToLower(*vulnSpec.Type), + } + typeStruct, err := byKeykv[*vulnTypeStruct](ctx, vulnTypeCol, inType.Key(), c) + if err == nil { + vulnIDs := c.buildVulnID(ctx, typeStruct, &vulnSpec) + for _, id := range vulnIDs { + v := &model.Vulnerability{ + ID: typeStruct.ThisID, + Type: typeStruct.Type, + VulnerabilityIDs: []*model.VulnerabilityID{ + id, + }, + } + + if (after != nil && vulnIDs[0].ID > *after) || after == nil { + addToCount += 1 + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.VulnerabilityEdge{ + Cursor: vulnIDs[0].ID, + Node: v, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.VulnerabilityEdge{ + Cursor: vulnIDs[0].ID, + Node: v, + }) + } + } + } + } + } else { + currentPage := false + + if after == nil { + currentPage = true + } + + var done bool + scn := c.kv.Keys(vulnTypeCol) + for !done { + var typeKeys []string + var err error + typeKeys, done, err = scn.Scan(ctx) + if err != nil { + return nil, err + } + + sort.Strings(typeKeys) + totalCount = len(typeKeys) + + for i, tk := range typeKeys { + typeStruct, err := byKeykv[*vulnTypeStruct](ctx, vulnTypeCol, tk, c) + if err != nil { + return nil, err + } + vulnIDs := c.buildVulnID(ctx, typeStruct, &vulnSpec) + if len(vulnIDs) > 0 { + for _, id := range vulnIDs { + v := &model.Vulnerability{ + ID: typeStruct.ThisID, + Type: typeStruct.Type, + VulnerabilityIDs: []*model.VulnerabilityID{ + id, + }, + } + + if after != nil && !currentPage { + if id.ID == *after { + totalCount = len(typeKeys) - (i + 1) + currentPage = true + } + continue + } + + if first != nil { + if numNodes < *first { + edges = append(edges, &model.VulnerabilityEdge{ + Cursor: id.ID, + Node: v, + }) + numNodes++ + } else if numNodes == *first { + hasNextPage = true + } + } else { + edges = append(edges, &model.VulnerabilityEdge{ + Cursor: id.ID, + Node: v, + }) + } + } + } + } + } + } + + if len(edges) != 0 { + return &model.VulnerabilityConnection{ + TotalCount: totalCount + addToCount, + PageInfo: &model.PageInfo{ + HasNextPage: hasNextPage, + StartCursor: ptrfrom.String(edges[0].Node.ID), + EndCursor: ptrfrom.String(edges[max(numNodes-1, 0)].Node.ID), + }, + Edges: edges, + }, nil + } + return nil, nil } func (c *demoClient) Vulnerabilities(ctx context.Context, filter *model.VulnerabilitySpec) ([]*model.Vulnerability, error) { diff --git a/pkg/assembler/graphql/examples/artifact_builder.gql b/pkg/assembler/graphql/examples/artifact_builder.gql index 93af0d88e6..770bc3dc79 100644 --- a/pkg/assembler/graphql/examples/artifact_builder.gql +++ b/pkg/assembler/graphql/examples/artifact_builder.gql @@ -4,6 +4,21 @@ fragment allArtifactTree on Artifact { digest } +fragment allArtifactPaginationTree on ArtifactConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + query ArtifactQ1 { artifacts(artifactSpec: {}) { ...allArtifactTree @@ -13,18 +28,7 @@ query ArtifactQ1 { query ArtifactPagination { artifactsList(artifactSpec: {}, first: 10) { - totalCount - edges { - cursor - node { - ...allArtifactTree - } - } - pageInfo { - startCursor - endCursor - hasNextPage - } + ...allArtifactPaginationTree } } diff --git a/pkg/assembler/graphql/examples/certify_bad.gql b/pkg/assembler/graphql/examples/certify_bad.gql index d394a49d40..1ecea96aa2 100644 --- a/pkg/assembler/graphql/examples/certify_bad.gql +++ b/pkg/assembler/graphql/examples/certify_bad.gql @@ -50,6 +50,27 @@ fragment allCertifyBadTree on CertifyBad { documentRef } +fragment allCertifyBadPaginationTree on CertifyBadConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query certifyBadPagination { + CertifyBadList(certifyBadSpec: {}, first: 10) { + ...allCertifyBadPaginationTree + } +} + query CertifactBadQ1 { CertifyBad(certifyBadSpec: {}) { ...allCertifyBadTree diff --git a/pkg/assembler/graphql/examples/certify_good.gql b/pkg/assembler/graphql/examples/certify_good.gql index b83148ede2..3ef5f2403f 100644 --- a/pkg/assembler/graphql/examples/certify_good.gql +++ b/pkg/assembler/graphql/examples/certify_good.gql @@ -50,6 +50,27 @@ fragment allCertifyGoodTree on CertifyGood { documentRef } +fragment allCertifyGoodPaginationTree on CertifyGoodConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query CertifyGoodPagination { + C(scorecardSpec: {}, first: 10) { + ...allCertifyScorecardPaginationTree + } +} + query CertifactGoodQ1 { CertifyGood(certifyGoodSpec: {}) { ...allCertifyGoodTree diff --git a/pkg/assembler/graphql/examples/certify_legal.gql b/pkg/assembler/graphql/examples/certify_legal.gql new file mode 100644 index 0000000000..c7a8386575 --- /dev/null +++ b/pkg/assembler/graphql/examples/certify_legal.gql @@ -0,0 +1,20 @@ +fragment AllCertifyLegalPaginationTree on CertifyLegalConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query CertifyLegalPagination { + CertifyLegalList(certifyLegalSpec: {}, first: 10) { + ...AllCertifyLegalPaginationTree + } +} \ No newline at end of file diff --git a/pkg/assembler/graphql/examples/certify_scorecard.gql b/pkg/assembler/graphql/examples/certify_scorecard.gql index beb4b52ba3..82686f67c5 100644 --- a/pkg/assembler/graphql/examples/certify_scorecard.gql +++ b/pkg/assembler/graphql/examples/certify_scorecard.gql @@ -33,6 +33,27 @@ fragment allCertifyScorecardTree on CertifyScorecard { } } +fragment allCertifyScorecardPaginationTree on CertifyScorecardConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query scorecardPagination { + scorecardsList(scorecardSpec: {}, first: 10) { + ...allCertifyScorecardPaginationTree + } +} + query ScorecardQ1 { scorecards(scorecardSpec: {}) { ...allCertifyScorecardTree diff --git a/pkg/assembler/graphql/examples/certify_vex.gql b/pkg/assembler/graphql/examples/certify_vex.gql index c77413f026..ed20262d07 100644 --- a/pkg/assembler/graphql/examples/certify_vex.gql +++ b/pkg/assembler/graphql/examples/certify_vex.gql @@ -47,6 +47,27 @@ fragment allCertifyVEXStatementTree on CertifyVEXStatement { documentRef } +fragment AllVexPaginationTree on VEXConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query VexPagination { + CertifyVEXStatementList(certifyVEXStatementSpec: {}, first: 10) { + ...AllVexPaginationTree + } +} + query CertifyVEXStatementQ1 { CertifyVEXStatement(certifyVEXStatementSpec: {}) { ...allCertifyVEXStatementTree diff --git a/pkg/assembler/graphql/examples/certify_vuln.gql b/pkg/assembler/graphql/examples/certify_vuln.gql index 3b5cbe3e53..7cea3b0a0d 100644 --- a/pkg/assembler/graphql/examples/certify_vuln.gql +++ b/pkg/assembler/graphql/examples/certify_vuln.gql @@ -41,6 +41,27 @@ fragment allCertifyVulnTree on CertifyVuln { } } +fragment AllCertifyVulnPaginationTree on CertifyVulnConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query CertifyVulnPagination { + CertifyVulnList(certifyVulnSpec: {}, first: 10) { + ...AllCertifyVulnPaginationTree + } +} + query CertifyVulnQ1 { CertifyVuln(certifyVulnSpec: {}) { ...allCertifyVulnTree diff --git a/pkg/assembler/graphql/examples/has_metadata.gql b/pkg/assembler/graphql/examples/has_metadata.gql new file mode 100644 index 0000000000..ae4d48c6e3 --- /dev/null +++ b/pkg/assembler/graphql/examples/has_metadata.gql @@ -0,0 +1,20 @@ +fragment AllHasMetadataPaginationTree on HasMetadataConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query HasMetadataPagination { + HasMetadataList(hasMetadataSpec: {}, first: 10) { + ...AllHasMetadataPaginationTree + } +} \ No newline at end of file diff --git a/pkg/assembler/graphql/examples/has_sbom.gql b/pkg/assembler/graphql/examples/has_sbom.gql index 053782fc28..a7b1614533 100644 --- a/pkg/assembler/graphql/examples/has_sbom.gql +++ b/pkg/assembler/graphql/examples/has_sbom.gql @@ -39,6 +39,27 @@ fragment allHasSBOMTree on HasSBOM { documentRef } +fragment AllHasSBOMPaginationTree on HasSBOMConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query HasSBOMPagination { + HasSBOMList(hasSBOMSpec: {}, first: 10) { + ...AllHasSBOMPaginationTree + } +} + query HasSBOMQ1 { HasSBOM(hasSBOMSpec: {}) { ...allHasSBOMTree diff --git a/pkg/assembler/graphql/examples/has_slsa.gql b/pkg/assembler/graphql/examples/has_slsa.gql index 4767b06597..fb6c9e03c7 100644 --- a/pkg/assembler/graphql/examples/has_slsa.gql +++ b/pkg/assembler/graphql/examples/has_slsa.gql @@ -29,6 +29,27 @@ fragment allHasSLSATree on HasSLSA { } } +fragment AllHasSLSAPaginationTree on HasSLSAConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query HasSLSAPagination { + HasSLSAList(hasSLSASpec: {}, first: 10) { + ...AllHasSLSAPaginationTree + } +} + query SLSAQ1 { HasSLSA(hasSLSASpec: {}) { ...allHasSLSATree diff --git a/pkg/assembler/graphql/examples/has_source_at.gql b/pkg/assembler/graphql/examples/has_source_at.gql index 0859502426..22bfa73926 100644 --- a/pkg/assembler/graphql/examples/has_source_at.gql +++ b/pkg/assembler/graphql/examples/has_source_at.gql @@ -42,6 +42,27 @@ fragment allHasSourceAtTree on HasSourceAt { documentRef } +fragment AllHasSourceAtPaginationTree on HasSourceAtConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query HasSourceAtPagination { + HasSourceAtList(hasSourceAtSpec: {}, first: 10) { + ...AllHasSourceAtPaginationTree + } +} + query HasSourceAtQ1 { HasSourceAt(hasSourceAtSpec: {}) { ...allHasSourceAtTree diff --git a/pkg/assembler/graphql/examples/hash_equal.gql b/pkg/assembler/graphql/examples/hash_equal.gql index 07d90f8dd7..ddf87d9074 100644 --- a/pkg/assembler/graphql/examples/hash_equal.gql +++ b/pkg/assembler/graphql/examples/hash_equal.gql @@ -11,6 +11,27 @@ fragment allHashEqualTree on HashEqual { documentRef } +fragment AllHashEqualsPaginationTree on HashEqualConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query HashEqualsPagination { + HashEqualList(hashEqualSpec: {}, first: 10) { + ...AllHashEqualsPaginationTree + } +} + query HashEqualQ1 { HashEqual(hashEqualSpec: {}) { ...allHashEqualTree diff --git a/pkg/assembler/graphql/examples/is_dependency.gql b/pkg/assembler/graphql/examples/is_dependency.gql index 4f2671c278..b91199bb5d 100644 --- a/pkg/assembler/graphql/examples/is_dependency.gql +++ b/pkg/assembler/graphql/examples/is_dependency.gql @@ -50,6 +50,27 @@ fragment allIsDependencyTree on IsDependency { documentRef } +fragment AllIsDependencyPaginationTree on IsDependencyConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query IsDependencyPagination { + IsDependencyList(isDependencySpec: {}, first: 10) { + ...AllIsDependencyPaginationTree + } +} + query IsDependencyQ1 { IsDependency(isDependencySpec: {}) { ...allIsDependencyTree diff --git a/pkg/assembler/graphql/examples/is_occurence.gql b/pkg/assembler/graphql/examples/is_occurence.gql index 9c67d2e290..dc36f95c30 100644 --- a/pkg/assembler/graphql/examples/is_occurence.gql +++ b/pkg/assembler/graphql/examples/is_occurence.gql @@ -49,6 +49,27 @@ fragment allIsOccurrencesTree on IsOccurrence { documentRef } +fragment AllIsOccurrencePaginationTree on IsOccurrenceConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query IsOccurrencePagination { + IsOccurrenceList(isOccurrenceSpec: {}, first: 10) { + ...AllIsOccurrencePaginationTree + } +} + query IsOccurrenceQ1 { IsOccurrence(isOccurrenceSpec: {}) { ...allIsOccurrencesTree diff --git a/pkg/assembler/graphql/examples/license.gql b/pkg/assembler/graphql/examples/license.gql new file mode 100644 index 0000000000..69d2810f7b --- /dev/null +++ b/pkg/assembler/graphql/examples/license.gql @@ -0,0 +1,20 @@ +fragment AllLicensePaginationTree on LicenseConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query LicensePagination { + licenseList(licenseSpec: {}, first: 10) { + ...AllLicensePaginationTree + } +} \ No newline at end of file diff --git a/pkg/assembler/graphql/examples/packages.gql b/pkg/assembler/graphql/examples/packages.gql index 5edee3b954..bc0aa0f662 100644 --- a/pkg/assembler/graphql/examples/packages.gql +++ b/pkg/assembler/graphql/examples/packages.gql @@ -20,6 +20,27 @@ fragment allPkgTree on Package { } } +fragment AllPkgPaginationTree on PackageConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query PkgPagination { + packagesList(pkgSpec: {}, first: 10) { + ...AllPkgPaginationTree + } +} + query PkgQ1 { packages(pkgSpec: {}) { type diff --git a/pkg/assembler/graphql/examples/pkg_equal.gql b/pkg/assembler/graphql/examples/pkg_equal.gql index 375b88780a..1a32f9d544 100644 --- a/pkg/assembler/graphql/examples/pkg_equal.gql +++ b/pkg/assembler/graphql/examples/pkg_equal.gql @@ -27,6 +27,27 @@ fragment allPkgEqualTree on PkgEqual { documentRef } +fragment AllPkgEqualPaginationTree on PkgEqualConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query PkgEqualPagination { + PkgEqualList(pkgEqualSpec: {}, first: 10) { + ...AllPkgEqualPaginationTree + } +} + query PkgEqualQ1 { PkgEqual(pkgEqualSpec: {}) { ...allPkgEqualTree diff --git a/pkg/assembler/graphql/examples/point_of_contact.gql b/pkg/assembler/graphql/examples/point_of_contact.gql new file mode 100644 index 0000000000..ac32fc6693 --- /dev/null +++ b/pkg/assembler/graphql/examples/point_of_contact.gql @@ -0,0 +1,20 @@ +fragment AllPointOfContactPaginationTree on PointOfContactConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query PointOfContactPagination { + PointOfContactList(pointOfContactSpec: {}, first: 10) { + ...AllPointOfContactPaginationTree + } +} \ No newline at end of file diff --git a/pkg/assembler/graphql/examples/source.gql b/pkg/assembler/graphql/examples/source.gql index 77cf7ef4c7..e9fcc9bc5e 100644 --- a/pkg/assembler/graphql/examples/source.gql +++ b/pkg/assembler/graphql/examples/source.gql @@ -13,6 +13,27 @@ fragment allSrcTree on Source { } } +fragment AllSourcePaginationTree on SourceConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query SourcePagination { + sourcesList(sourceSpec: {}, first: 10) { + ...AllSourcePaginationTree + } +} + query SrcQ1 { sources(sourceSpec: {}) { namespaces { diff --git a/pkg/assembler/graphql/examples/vulnEqual.gql b/pkg/assembler/graphql/examples/vulnEqual.gql index 14fec5c018..3ac66f8a10 100644 --- a/pkg/assembler/graphql/examples/vulnEqual.gql +++ b/pkg/assembler/graphql/examples/vulnEqual.gql @@ -14,6 +14,27 @@ fragment allVulnEqualTree on VulnEqual { documentRef } +fragment AllVulnEqualPaginationTree on VulnEqualConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query VulnEqualPagination { + vulnEqualList(vulnEqualSpec: {}, first: 10) { + ...AllVulnEqualPaginationTree + } +} + query IsVulnerabilityQ1 { vulnEqual(vulnEqualSpec: {}) { ...allVulnEqualTree diff --git a/pkg/assembler/graphql/examples/vuln_metadata.gql b/pkg/assembler/graphql/examples/vuln_metadata.gql index d045580cf5..c9944936b9 100644 --- a/pkg/assembler/graphql/examples/vuln_metadata.gql +++ b/pkg/assembler/graphql/examples/vuln_metadata.gql @@ -16,6 +16,27 @@ fragment allVulnMetadataTree on VulnerabilityMetadata { documentRef } +fragment AllVulnerabilityMetadataPaginationTree on VulnerabilityMetadataConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query VulnerabilityMetadataPagination { + vulnerabilityMetadataList(vulnerabilityMetadataSpec: {}, first: 10) { + ...AllVulnerabilityMetadataPaginationTree + } +} + query VulnMetadataQ1 { vulnerabilityMetadata(vulnerabilityMetadataSpec: {}) { ...allVulnMetadataTree diff --git a/pkg/assembler/graphql/examples/vulnerability.gql b/pkg/assembler/graphql/examples/vulnerability.gql index fad82c70b5..dd9c7cc2b3 100644 --- a/pkg/assembler/graphql/examples/vulnerability.gql +++ b/pkg/assembler/graphql/examples/vulnerability.gql @@ -7,6 +7,27 @@ fragment allVulnerabilityTree on Vulnerability { } } +fragment AllVulnerabilityPaginationTree on VulnerabilityConnection { + totalCount + edges { + cursor + node { + id + } + } + pageInfo { + startCursor + endCursor + hasNextPage + } +} + +query VulnerabilityPagination { + vulnerabilityList(vulnSpec: {}, first: 10) { + ...AllVulnerabilityPaginationTree + } +} + query CVEQ1 { vulnerabilities(vulnSpec: {}) { ...allVulnerabilityTree