Skip to content

Commit

Permalink
Refactor getmetadata
Browse files Browse the repository at this point in the history
- refactor getmetadata
Signed-off-by: naveensrinivasan <[email protected]>
  • Loading branch information
naveensrinivasan committed Nov 10, 2024
1 parent 0364319 commit 3365e64
Show file tree
Hide file tree
Showing 7 changed files with 465 additions and 163 deletions.
178 changes: 128 additions & 50 deletions cmd/query/getMetadata/getMetadata.go
Original file line number Diff line number Diff line change
@@ -1,100 +1,178 @@
package getMetadata

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"

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

type options struct {
storage graph.Storage
outputFile string
type (
options struct {
outputFile string
addr string
output string
graphServiceClient apiv1connect.GraphServiceClient
}

nodeOutput struct {
Name string `json:"name"`
Type string `json:"type"`
ID string `json:"id"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
)

// Constants for output formats and default values
const (
outputFormatJSON = "json"
outputFormatTable = "table"
defaultAddr = "http://localhost:8089"
)

// New creates and returns a new cobra command for the get-metadata functionality.
// The command allows users to retrieve and display node metadata in different formats.
func New() *cobra.Command {
o := &options{}
cmd := &cobra.Command{
Use: "get-metadata [node name]",
Short: "Outputs the node with its metadata",
Args: cobra.ExactArgs(1),
RunE: o.Run,
DisableAutoGenTag: true,
}
o.AddFlags(cmd)
return cmd
}

// AddFlags adds the command-line flags to the provided cobra command.
// It configures flags for output file, server address, and output format.
func (o *options) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.outputFile, "output-file", "", "output file")
cmd.Flags().StringVar(&o.addr, "addr", defaultAddr, "address of the minefield server")
cmd.Flags().StringVar(&o.output, "output", outputFormatJSON, "output format (json or table)")
}

// Run executes the get-metadata command with the provided arguments.
// It fetches the node data and formats the output according to the specified format.
func (o *options) Run(cmd *cobra.Command, args []string) error {
httpClient := &http.Client{}
addr := os.Getenv("BITBOMDEV_ADDR")
if addr == "" {
addr = "http://localhost:8089"
node, err := o.fetchNode(cmd.Context(), args[0])
if err != nil {
return err
}

return o.formatAndDisplayOutput(cmd, node)
}

// fetchNode retrieves node information from the server using the provided node name.
// It returns the node data or an error if the fetch operation fails.
func (o *options) fetchNode(ctx context.Context, nodeName string) (*apiv1.Node, error) {
if nodeName == "" {
return nil, fmt.Errorf("node name is required")
}
client := apiv1connect.NewGraphServiceClient(httpClient, addr)

// Create a new context
ctx := cmd.Context()
if o.graphServiceClient == nil {
o.graphServiceClient = apiv1connect.NewGraphServiceClient(
http.DefaultClient,
o.addr,
)
}

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

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

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

// 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)
}
return res.Msg.Node, nil
}

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)),
Metadata: metadata, // Use the unmarshaled map
// formatAndDisplayOutput formats the node data according to the specified output format
// and displays or saves the result. It supports JSON and table formats.
func (o *options) formatAndDisplayOutput(cmd *cobra.Command, node *apiv1.Node) error {
switch o.output {
case outputFormatJSON:
return o.handleJSONOutput(cmd, node)
case outputFormatTable:
return formatTable(cmd.OutOrStdout(), node)
default:
return fmt.Errorf("unknown output format: %s", o.output)
}
}

jsonOutput, err := json.MarshalIndent(output, "", " ")
// handleJSONOutput processes the node data into JSON format and either writes it
// to a file or displays it to the command output.
func (o *options) handleJSONOutput(cmd *cobra.Command, node *apiv1.Node) error {
jsonOutput, err := formatNodeJSON(node)
if err != nil {
return fmt.Errorf("failed to marshal json: %v", err)
return fmt.Errorf("failed to format node as JSON: %v", err)
}

if o.outputFile != "" {
err = os.WriteFile(o.outputFile, jsonOutput, 0644)
if err != nil {
if err := os.WriteFile(o.outputFile, jsonOutput, 0644); err != nil {
return fmt.Errorf("failed to write output file: %v", err)
}
} else {
fmt.Println(string(jsonOutput))
return nil
}

cmd.Println(string(jsonOutput))
return nil
}

func New(storage graph.Storage) *cobra.Command {
o := &options{
storage: storage,
// formatNodeJSON converts a node into a JSON formatted byte slice.
// It handles the conversion of metadata and ensures proper formatting of all fields.
func formatNodeJSON(node *apiv1.Node) ([]byte, error) {
if node == nil {
return nil, fmt.Errorf("node cannot be nil")
}
cmd := &cobra.Command{
Use: "get-metadata [node name]",
Short: "Outputs the node with its metadata",
Args: cobra.ExactArgs(1),
RunE: o.Run,
DisableAutoGenTag: true,

var metadata map[string]interface{}
if len(node.Metadata) > 0 {
if err := json.Unmarshal(node.Metadata, &metadata); err != nil {
return nil, fmt.Errorf("failed to unmarshal metadata for node %s: %w", node.Name, err)
}
}
o.AddFlags(cmd)

return cmd
output := nodeOutput{
Name: node.Name,
Type: node.Type,
ID: strconv.FormatUint(uint64(node.Id), 10),
Metadata: metadata,
}

return json.MarshalIndent(output, "", " ")
}

// formatTable writes the node information in a tabular format to the provided writer.
// It creates a table with columns for Name, Type, and ID.
func formatTable(w io.Writer, node *apiv1.Node) error {
table := tablewriter.NewWriter(w)
table.SetHeader([]string{"Name", "Type", "ID"})
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)

table.Append([]string{
node.Name,
node.Type,
strconv.FormatUint(uint64(node.Id), 10),
})

table.Render()
return nil
}
Loading

0 comments on commit 3365e64

Please sign in to comment.