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

Add functionality to inspect nodes #104

Merged
merged 1 commit into from
Oct 26, 2024
Merged
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
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,36 +59,36 @@
_Redis must be running at `localhost:6379`. If not, please use `make docker-up` to start Redis._

1. **Start the API server:**
```shell
```sh
minefield start-service
```

2. **Ingest the `test` SBOM directory:**
2. **Ingest the `testdata/small` SBOM directory:**
```sh
minefield ingest sbom testdata
minefield ingest sbom testdata/small
```
3. **Cache the data:**
```sh
minefield cache
```
4. **Run the leaderboard custom with "dependents PACKAGE":**
4. **Run the leaderboard custom with "dependents library":**
- This command generates a ranked list of packages, ordered by the number of other packages that depend on them.
```sh
minefield leaderboard custom "dependents PACKAGE"
minefield leaderboard custom "dependents library"
```
5. **Run a query on the top value from the leaderboard:**
- This command queries the dependents for a specific package, in this case `dep2`.
```sh
minefield query "dependents PACKAGE dep2"
minefield query custom "dependents library pkg:dep2@1.0.0"
```
6. **Run queries to see the shared dependencies of `lib-A` and `dep1`, and `lib-A` and `lib-B`:**
- These queries output the intersection of two queries, finding package dependencies shared between each pair.
```sh
minefield query "dependencies PACKAGE pkg:generic/lib-B@1.0.0 and dependencies PACKAGE pkg:generic/lib-A@1.0.0"
minefield query custom "dependencies library pkg:lib-B@1.0.0 and dependencies library pkg:lib-A@1.0.0"
```
7. **Run queries with the visualizer:**
```sh
minefield query "dependents PACKAGE pkg:generic/dep2@1.0.0" --visualize
minefield query custom "dependents library pkg:dep2@1.0.0" --visualize
```

## To Start Using Minefield
Expand Down
33 changes: 1 addition & 32 deletions cmd/query/custom/custom.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package query
package custom

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

Expand All @@ -14,22 +13,18 @@ import (
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")
Expand Down Expand Up @@ -73,32 +68,6 @@ func (o *options) Run(cmd *cobra.Command, args []string) error {
}

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); os.IsNotExist(err) {
if err := os.MkdirAll(o.outputdir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
} else if err != nil {
return fmt.Errorf("failed to check output directory: %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++
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/query/globsearch/globsearch.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package query
package globsearch

import (
"fmt"
Expand Down
101 changes: 101 additions & 0 deletions cmd/query/output/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package output

import (
"encoding/json"
"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/spf13/cobra"
)

type options struct {
storage graph.Storage
outputFile string
}
neilnaveen marked this conversation as resolved.
Show resolved Hide resolved

func (o *options) AddFlags(cmd *cobra.Command) {

cmd.Flags().StringVar(&o.outputFile, "output-file", "", "output file")
}

func (o *options) Run(cmd *cobra.Command, args []string) error {
httpClient := &http.Client{}
addr := os.Getenv("BITBOMDEV_ADDR")
neilnaveen marked this conversation as resolved.
Show resolved Hide resolved
if addr == "" {
addr = "http://localhost:8089"
}
neilnaveen marked this conversation as resolved.
Show resolved Hide resolved
client := apiv1connect.NewGraphServiceClient(httpClient, addr)

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

// Create a new QueryRequest
req := connect.NewRequest(&apiv1.GetNodeByNameRequest{
Name: args[0],
})

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

node := res.Msg.Node
if node == nil {
return fmt.Errorf("node not found: %s", args[0])
}

// Unmarshal the metadata JSON string into a map
var metadata map[string]interface{}
if err := json.Unmarshal([]byte(node.Metadata), &metadata); err != nil {
return fmt.Errorf("failed to unmarshal metadata: %v", err)
}

output := struct {
Name string `json:"name"`
Type string `json:"type"`
ID string `json:"id"`
Metadata map[string]interface{} `json:"metadata"` // Change type to map
}{
Name: node.Name,
Type: node.Type,
ID: strconv.Itoa(int(node.Id)),
naveensrinivasan marked this conversation as resolved.
Show resolved Hide resolved
Metadata: metadata, // Use the unmarshaled map
}

jsonOutput, err := json.MarshalIndent(output, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal json: %v", err)
}
if o.outputFile != "" {
err = os.WriteFile(o.outputFile, jsonOutput, 0644)
if err != nil {
return fmt.Errorf("failed to write output file: %v", err)
}
} else {
fmt.Println(string(jsonOutput))
}
naveensrinivasan marked this conversation as resolved.
Show resolved Hide resolved

return nil
}

func New(storage graph.Storage) *cobra.Command {
o := &options{
storage: storage,
}
cmd := &cobra.Command{
Use: "output [node name]",
Short: "Output the node, with its metadata",
Args: cobra.ExactArgs(1),
RunE: o.Run,
DisableAutoGenTag: true,
}
o.AddFlags(cmd)

return cmd
}
3 changes: 2 additions & 1 deletion cmd/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package query
import (
custom "github.com/bit-bom/minefield/cmd/query/custom"
globsearch "github.com/bit-bom/minefield/cmd/query/globsearch"
output "github.com/bit-bom/minefield/cmd/query/output"
"github.com/bit-bom/minefield/pkg/graph"
"github.com/spf13/cobra"
)
Expand All @@ -16,6 +17,6 @@ func New(storage graph.Storage) *cobra.Command {

cmd.AddCommand(custom.New(storage))
cmd.AddCommand(globsearch.New(storage))

cmd.AddCommand(output.New(storage))
return cmd
}
8 changes: 2 additions & 6 deletions pkg/tools/ingest/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@ func processSBOMFile(filePath string, storage graph.Storage) error {
return fmt.Errorf("file path is empty")
}

file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", filePath, err)
}

// Create a new protobom reader
r := reader.New()

Expand All @@ -88,7 +83,8 @@ func processSBOMFile(filePath string, storage graph.Storage) error {
if purl == "" {
purl = fmt.Sprintf("pkg:%s@%s", node.GetName(), node.GetVersion())
}
graphNode, err := graph.AddNode(storage, "library", file, purl)

graphNode, err := graph.AddNode(storage, "library", node, purl)
neilnaveen marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
if errors.Is(err, graph.ErrNodeAlreadyExists) {
// log.Printf("Skipping node %s: %s\n", node.GetName(), err)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading