Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented a Delete functionality for KeyValue #2033

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions internal/testing/backend/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ var skipMatrix = map[string]map[string]bool{
// redis order issues
"TestVEXBulkIngest": {arango: true, redis: true},
"TestFindSoftware": {redis: true, arango: true},
// remove these once its implemented for the other backends
"TestDeleteCertifyVuln": {arango: true, memmap: true, redis: true, tikv: true},
nathannaveen marked this conversation as resolved.
Show resolved Hide resolved
"TestDeleteHasSBOM": {arango: true, memmap: true, redis: true, tikv: true},
"TestDeleteHasSLSAs": {arango: true, memmap: true, redis: true, tikv: true},
// remove these once its implemented for the arango backend
"TestDeleteCertifyVuln": {arango: true},
"TestDeleteHasSBOM": {arango: true},
"TestDeleteHasSLSAs": {arango: true},
}

type backend interface {
Expand Down
4 changes: 4 additions & 0 deletions internal/testing/stablememmap/stablememmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func (s *store) Set(ctx context.Context, c, k string, v any) error {
return s.mm.Set(ctx, c, k, v)
}

func (s *store) Remove(ctx context.Context, c, k string) error {
nathannaveen marked this conversation as resolved.
Show resolved Hide resolved
return s.mm.Remove(ctx, c, k)
}

func (s *store) Keys(c string) kv.Scanner {
return &scanner{mms: s.mm.Keys(c)}
}
Expand Down
55 changes: 55 additions & 0 deletions pkg/assembler/backends/keyvalue/certifyVuln.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,61 @@ func (n *certifyVulnerabilityLink) Key() string {
}, ":"))
}

// removeLinkFromList is a helper function to remove a link from an array of links. This works by setting all the links except the specified linkID.
func removeLinkFromList(linkID string, links []string) []string {
var newLinks []string
for _, link := range links {
if link != linkID {
newLinks = append(newLinks, link)
}
}
return newLinks
}

// DeleteCertifyVuln deletes a specified certifyVuln node along with all associated relationships.
func (c *demoClient) DeleteCertifyVuln(ctx context.Context, id string) (bool, error) {
funcName := "DeleteCertifyVuln"

// Retrieve the certifyVulnerabilityLink by ID
link, err := byIDkv[*certifyVulnerabilityLink](ctx, id, c)
if err != nil {
if errors.Is(err, kv.NotFoundError) {
return false, nil // Not found, nothing to delete
}
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

// Remove backlinks from associated package and vulnerability
foundPkgNode, err := c.returnFoundPkgVersion(ctx, &model.IDorPkgInput{PackageVersionID: &link.PackageID})
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

foundPkgNode.CertifyVulnLinks = removeLinkFromList(link.ThisID, foundPkgNode.CertifyVulnLinks)
err = setkv(ctx, pkgVerCol, foundPkgNode, c)
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

foundVulnNode, err := c.returnFoundVulnerability(ctx, &model.IDorVulnerabilityInput{VulnerabilityNodeID: &link.VulnerabilityID})
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

foundVulnNode.CertifyVulnLinks = removeLinkFromList(link.ThisID, foundPkgNode.CertifyVulnLinks)
err = setkv(ctx, vulnIDCol, foundPkgNode, c)
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

// Delete the link from the KeyValue store
if err := c.kv.Remove(ctx, cVulnCol, link.Key()); err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

return true, nil
}

func (n *certifyVulnerabilityLink) Neighbors(allowedEdges edgeMap) []string {
out := make([]string, 0, 2)
if allowedEdges[model.EdgeCertifyVulnPackage] {
Expand Down
60 changes: 60 additions & 0 deletions pkg/assembler/backends/keyvalue/hasSBOM.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,66 @@ func (n *hasSBOMStruct) Key() string {
}, ":"))
}

// DeleteHasSBOM deletes a specified hasSBOM node along with all associated relationships.
func (c *demoClient) DeleteHasSBOM(ctx context.Context, id string) (bool, error) {
funcName := "DeleteHasSBOM"

// Retrieve the hasSBOM link by ID
link, err := byIDkv[*hasSBOMStruct](ctx, id, c)
if err != nil {
if errors.Is(err, kv.NotFoundError) {
return false, nil // Not found, nothing to delete
}
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

// Delete associated isDependency nodes
for _, depID := range link.IncludedDependencies {
if err := c.kv.Remove(ctx, "dependencies", depID); !errors.Is(err, kv.NotFoundError) {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}
}

// Delete associated isOccurrence nodes
for _, occurID := range link.IncludedOccurrences {
if err := c.kv.Remove(ctx, "occurrences", occurID); !errors.Is(err, kv.NotFoundError) {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}
}

// Remove backlinks from associated package or artifact
if link.Pkg != "" {
foundPkg, err := c.returnFoundPkgVersion(ctx, &model.IDorPkgInput{PackageVersionID: &link.Pkg})
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

foundPkg.CertifyVulnLinks = removeLinkFromList(link.ThisID, foundPkg.HasSBOMs)
err = setkv(ctx, pkgVerCol, foundPkg, c)
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}
} else if link.Artifact != "" {
foundArtifact, err := c.returnFoundArtifact(ctx, &model.IDorArtifactInput{ArtifactID: &link.Artifact})
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

foundArtifact.HasSBOMs = removeLinkFromList(link.ThisID, foundArtifact.HasSBOMs)
err = setkv(ctx, artCol, foundArtifact, c)
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}
}

// Delete the hasSBOM link from the KeyValue store
if err := c.kv.Remove(ctx, hasSBOMCol, link.Key()); err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

return true, nil
}

func (n *hasSBOMStruct) Neighbors(allowedEdges edgeMap) []string {
var out []string
if n.Pkg != "" && allowedEdges[model.EdgeHasSbomPackage] {
Expand Down
59 changes: 59 additions & 0 deletions pkg/assembler/backends/keyvalue/hasSLSA.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,65 @@ func (n *hasSLSAStruct) Key() string {
}, ":"))
}

// DeleteHasSLSA deletes a specified SLSA node along with all associated relationships.
func (c *demoClient) DeleteHasSLSA(ctx context.Context, id string) (bool, error) {
funcName := "DeleteSLSA"

// Retrieve the SLSA link by ID
link, err := byIDkv[*hasSLSAStruct](ctx, id, c)
if err != nil {
if errors.Is(err, kv.NotFoundError) {
return false, nil // Not found, nothing to delete
}
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

// Remove backlinks from associated subject
foundSubject, err := c.returnFoundArtifact(ctx, &model.IDorArtifactInput{ArtifactID: &link.Subject})
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

foundSubject.HasSLSAs = removeLinkFromList(link.ThisID, foundSubject.HasSLSAs)
err = setkv(ctx, artCol, foundSubject, c)
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

// Remove backlinks from associated builtBy
foundBuiltBy, err := c.returnFoundBuilder(ctx, &model.IDorBuilderInput{BuilderID: &link.BuiltBy})
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

foundBuiltBy.HasSLSAs = removeLinkFromList(link.ThisID, foundBuiltBy.HasSLSAs)
err = setkv(ctx, builderCol, foundBuiltBy, c)
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

// Remove backlinks from associated builtFrom
for _, builtFromID := range link.BuiltFrom {
foundBuiltFrom, err := c.returnFoundArtifact(ctx, &model.IDorArtifactInput{ArtifactID: &builtFromID})
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

foundBuiltFrom.HasSLSAs = removeLinkFromList(link.ThisID, foundBuiltFrom.HasSLSAs)
err = setkv(ctx, artCol, foundBuiltFrom, c)
if err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}
}

// Delete the link from the KeyValue store
if err := c.kv.Remove(ctx, slsaCol, link.Key()); err != nil {
return false, gqlerror.Errorf("%v :: %s", funcName, err)
}

return true, nil
}

func (n *hasSLSAStruct) Neighbors(allowedEdges edgeMap) []string {
out := make([]string, 0, 2+len(n.BuiltFrom))
if allowedEdges[model.EdgeHasSlsaSubject] {
Expand Down
42 changes: 40 additions & 2 deletions pkg/assembler/backends/keyvalue/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package keyvalue
import (
"context"
"fmt"
"github.com/guacsec/guac/pkg/logging"
"strings"

"github.com/vektah/gqlparser/v2/gqlerror"
Expand Down Expand Up @@ -192,6 +193,43 @@ func (c *demoClient) Nodes(ctx context.Context, ids []string) ([]model.Node, err

// Delete node and all associated relationships. This functionality is only implemented for
// certifyVuln, HasSBOM and HasSLSA.
func (c *demoClient) Delete(ctx context.Context, node string) (bool, error) {
panic(fmt.Errorf("not implemented: Delete"))
func (c *demoClient) Delete(ctx context.Context, nodeID string) (bool, error) {
logger := logging.FromContext(ctx)

// Retrieve the node type based on the node ID
var k string
if err := c.kv.Get(ctx, indexCol, nodeID, &k); err != nil {
return false, fmt.Errorf("%w : id not found in index %q", err, nodeID)
}

sub := strings.SplitN(k, ":", 2)
if len(sub) != 2 {
return false, fmt.Errorf("Bad value was stored in index map: %v", k)
}

nodeType := sub[0]

switch nodeType {
case "certifyVulns":
deleted, err := c.DeleteCertifyVuln(ctx, nodeID)
if err != nil {
return false, fmt.Errorf("failed to delete CertifyVuln via ID: %s, with error: %w", nodeID, err)
}
return deleted, nil
case "hasSBOMs":
deleted, err := c.DeleteHasSBOM(ctx, nodeID)
if err != nil {
return false, fmt.Errorf("failed to delete HasSBOM via ID: %s, with error: %w", nodeID, err)
}
return deleted, nil
case "hasSLSAs":
deleted, err := c.DeleteHasSLSA(ctx, nodeID)
if err != nil {
return false, fmt.Errorf("failed to delete HasSLSA via ID: %s, with error: %w", nodeID, err)
}
return deleted, nil
default:
logger.Debugf("Delete attempted for node id %s which was of type %s, and that type not supported for delete", nodeID, nodeType)
}
return false, nil
}
4 changes: 3 additions & 1 deletion pkg/assembler/kv/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ import (

// Store is an interface to define to serve as a keyvalue store
type Store interface {

// Retrieve value from store. If not found, returns NotFoundError. Ptr must
// be a pointer to the type of value stored.
Get(ctx context.Context, collection, key string, ptr any) error

// Sets a value, creates collection if necessary
Set(ctx context.Context, collection, key string, value any) error

// Delete a value from the store. If not found, returns NotFoundError.
Remove(ctx context.Context, collection, key string) error

// Create a scanner that will be used to get all the keys in a collection.
Keys(collection string) Scanner
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/assembler/kv/memmap/memmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ func (s *store) Set(_ context.Context, c, k string, v any) error {
return nil
}

func (s *store) Remove(_ context.Context, c, k string) error {
col, ok := s.m[c]
if !ok {
return fmt.Errorf("%w : Collection %q", kv.NotFoundError, c)
}
if _, ok := col[k]; !ok {
return fmt.Errorf("%w : Key %q", kv.NotFoundError, k)
}
delete(col, k)
return nil
}

func (s *store) Keys(c string) kv.Scanner {
return &scanner{
collection: c,
Expand Down
11 changes: 11 additions & 0 deletions pkg/assembler/kv/redis/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ func (s *store) Set(ctx context.Context, c, k string, v any) error {
return s.c.HSet(ctx, c, k, string(b)).Err()
}

func (s *store) Remove(ctx context.Context, c, k string) error {
res, err := s.c.HDel(ctx, c, k).Result()
if err != nil {
return err
}
if res == 0 {
return kv.NotFoundError
}
return nil
}

func (s *store) Keys(c string) kv.Scanner {
return &scanner{
collection: c,
Expand Down
18 changes: 18 additions & 0 deletions pkg/assembler/kv/tikv/tikv.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ func (s *store) Set(ctx context.Context, c, k string, v any) error {
return s.c.Put(ctx, []byte(ck), bts)
}

func (s *store) Remove(ctx context.Context, c, k string) error {
ck := strings.Join([]string{c, k}, ":")
// Check if the key exists
bts, err := s.c.Get(ctx, []byte(ck))
if err != nil {
return err
}
if len(bts) == 0 {
return kv.NotFoundError
}
// Proceed to delete the key
err = s.c.Delete(ctx, []byte(ck))
if err != nil {
return err
}
return nil
}

func (s *store) Keys(c string) kv.Scanner {
return &scanner{
c: s.c,
Expand Down
Loading