Skip to content

Commit

Permalink
Added Glob searching for nodes
Browse files Browse the repository at this point in the history
- Added glob searching for nodes, allowing searchs for nodes like
  "*minefield*" and getting back two outputs
"pkg:github/bitbomdev/minefield@v2" and "pkg:github/bitbomdev/minefield@v2"

Signed-off-by: neilnaveen <42328488+neilnaveen@users.noreply.github.com>
  • Loading branch information
neilnaveen committed Oct 23, 2024
1 parent 5143cb1 commit 2cea103
Show file tree
Hide file tree
Showing 12 changed files with 561 additions and 210 deletions.
16 changes: 16 additions & 0 deletions api/v1/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ func (s *Service) GetNodeByName(ctx context.Context, req *connect.Request[servic
return connect.NewResponse(&service.GetNodeByNameResponse{Node: serviceNode}), nil
}

func (s *Service) GetNodesByGlob(ctx context.Context, req *connect.Request[service.GetNodesByGlobRequest]) (*connect.Response[service.GetNodesByGlobResponse], error) {
nodes, err := s.storage.GetNodesByGlob(req.Msg.Pattern)
if err != nil {
return nil, err
}

Check warning on line 88 in api/v1/service.go

View check run for this annotation

Codecov / codecov/patch

api/v1/service.go#L87-L88

Added lines #L87 - L88 were not covered by tests
serviceNodes := make([]*service.Node, 0, len(nodes))
for _, node := range nodes {
serviceNode, err := NodeToServiceNode(node)
if err != nil {
return nil, err
}
serviceNodes = append(serviceNodes, serviceNode)

Check warning on line 95 in api/v1/service.go

View check run for this annotation

Codecov / codecov/patch

api/v1/service.go#L91-L95

Added lines #L91 - L95 were not covered by tests
}
return connect.NewResponse(&service.GetNodesByGlobResponse{Nodes: serviceNodes}), nil
}

func (s *Service) Cache(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[emptypb.Empty], error) {
err := graph.Cache(s.storage)
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions api/v1/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ message GetNodeByNameResponse {
Node node = 1;
}

message GetNodesByGlobRequest {
string pattern = 1;
}

message GetNodesByGlobResponse {
repeated Node nodes = 1;
}

service QueryService {
rpc Query(QueryRequest) returns (QueryResponse) {}
}
Expand All @@ -72,5 +80,6 @@ service LeaderboardService {

service GraphService {
rpc GetNode(GetNodeRequest) returns (GetNodeResponse) {}
rpc GetNodesByGlob(GetNodesByGlobRequest) returns (GetNodesByGlobResponse) {}
rpc GetNodeByName(GetNodeByNameRequest) returns (GetNodeByNameResponse) {}
}
14 changes: 14 additions & 0 deletions api/v1/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ func TestGetNodeByName(t *testing.T) {
assert.Equal(t, node.Name, resp.Msg.Node.Name)
}

func TestGetNodesByGlob(t *testing.T) {
s := setupService()
node, err := graph.AddNode(s.storage, "type1", "metadata1", "name1")
require.NoError(t, err)
node2, err := graph.AddNode(s.storage, "type1", "metadata1", "name2")
require.NoError(t, err)
req := connect.NewRequest(&service.GetNodesByGlobRequest{Pattern: "name"})
resp, err := s.GetNodesByGlob(context.Background(), req)
require.NoError(t, err)
for _, respNode := range resp.Msg.Nodes {
assert.Contains(t, []string{node.Name, node2.Name}, respNode.Name)
}
}

func TestQueriesAndCache(t *testing.T) {
s := setupService()
_, err := ingest.SBOM("../../testdata/osv-sboms/google_agi.sbom.json", s.storage, nil)
Expand Down
139 changes: 139 additions & 0 deletions cmd/query/custom/custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package query

import (
"bufio"
"fmt"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"

"connectrpc.com/connect"
"github.com/RoaringBitmap/roaring"
apiv1 "github.com/bit-bom/minefield/gen/api/v1"
"github.com/bit-bom/minefield/gen/api/v1/apiv1connect"
"github.com/bit-bom/minefield/pkg/graph"
"github.com/bit-bom/minefield/pkg/tools"
"github.com/goccy/go-json"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
)

type options struct {
storage graph.Storage
outputdir string
visualizerAddr string
maxOutput int
visualize bool
}

func (o *options) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.outputdir, "output-dir", "", "specify dir to write the output to")
cmd.Flags().IntVar(&o.maxOutput, "max-output", 10, "max output length")
cmd.Flags().BoolVar(&o.visualize, "visualize", false, "visualize the query")
cmd.Flags().StringVar(&o.visualizerAddr, "addr", "8081", "address to run the visualizer on")
}

func (o *options) Run(cmd *cobra.Command, args []string) error {
script := strings.Join(args, " ")
httpClient := &http.Client{}
addr := os.Getenv("BITBOMDEV_ADDR")
if addr == "" {
addr = "http://localhost:8089"
}
client := apiv1connect.NewQueryServiceClient(httpClient, addr)

// Create a new context
ctx := cmd.Context()

// Create a new QueryRequest
req := connect.NewRequest(&apiv1.QueryRequest{
Script: script,
})

// Make the Query request
res, err := client.Query(ctx, req)
if err != nil {
return fmt.Errorf("query failed: %v", err)
}

// Print dependencies
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Name", "Type", "ID"})

count := 0
for _, node := range res.Msg.Nodes {
if count > o.maxOutput {
break
}

table.Append([]string{node.Name, node.Type, strconv.Itoa(int(node.Id))})

if o.outputdir != "" {
data, err := json.MarshalIndent(node.Metadata, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal node metadata: %w", err)
}
if _, err := os.Stat(o.outputdir); err != nil {
return fmt.Errorf("output directory does not exist: %w", err)
}

filePath := filepath.Join(o.outputdir, tools.SanitizeFilename(node.Name)+".json")
file, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer file.Close()

_, err = file.Write(data)
if err != nil {
return fmt.Errorf("failed to write data to file: %w", err)
}
}
count++
}

if o.visualize {
server := &http.Server{
Addr: ":" + o.visualizerAddr,
}

ids := roaring.New()

for _, node := range res.Msg.Nodes {
ids.Add(node.Id)
}

shutdown, err := graph.RunGraphVisualizer(o.storage, ids, script, server)
if err != nil {
return err
}
defer shutdown()

fmt.Println("Press Enter to stop the server and continue...")
if _, err := bufio.NewReader(os.Stdin).ReadBytes('\n'); err != nil {
return err
}
}

table.Render()

return nil
}

func New(storage graph.Storage) *cobra.Command {
o := &options{
storage: storage,
}
cmd := &cobra.Command{
Use: "custom [script]",
Short: "Quer dependencies and dependents of a project",
Args: cobra.ExactArgs(1),
RunE: o.Run,
DisableAutoGenTag: true,
}
o.AddFlags(cmd)

return cmd
}
82 changes: 82 additions & 0 deletions cmd/query/globsearch/globsearch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package query

import (
"fmt"
"net/http"
"os"
"strconv"

"connectrpc.com/connect"
apiv1 "github.com/bit-bom/minefield/gen/api/v1"
"github.com/bit-bom/minefield/gen/api/v1/apiv1connect"
"github.com/bit-bom/minefield/pkg/graph"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
)

type options struct {
storage graph.Storage
maxOutput int
}

func (o *options) AddFlags(cmd *cobra.Command) {
cmd.Flags().IntVar(&o.maxOutput, "max-output", 10, "max output length")
}

func (o *options) Run(cmd *cobra.Command, args []string) error {
pattern := args[0]
httpClient := &http.Client{}
addr := os.Getenv("BITBOMDEV_ADDR")
if addr == "" {
addr = "http://localhost:8089"
}
client := apiv1connect.NewGraphServiceClient(httpClient, addr)

// Create a new context
ctx := cmd.Context()

// Create a new QueryRequest
req := connect.NewRequest(&apiv1.GetNodesByGlobRequest{
Pattern: pattern,
})

// Make the Query request
res, err := client.GetNodesByGlob(ctx, req)
if err != nil {
return fmt.Errorf("query failed: %v", err)
}

// Print dependencies
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Name", "Type", "ID"})

count := 0
for _, node := range res.Msg.Nodes {
if count > o.maxOutput {
break
}

table.Append([]string{node.Name, node.Type, strconv.Itoa(int(node.Id))})
count++
}

table.Render()

return nil
}

func New(storage graph.Storage) *cobra.Command {
o := &options{
storage: storage,
}
cmd := &cobra.Command{
Use: "globsearch [pattern]",
Short: "Search for nodes by glob pattern",
Args: cobra.ExactArgs(1),
RunE: o.Run,
DisableAutoGenTag: true,
}
o.AddFlags(cmd)

return cmd
}
Loading

0 comments on commit 2cea103

Please sign in to comment.