Skip to content

Commit 2e09d50

Browse files
committed
stable alpha - scanner scaffolding
1 parent 940174e commit 2e09d50

12 files changed

Lines changed: 3049 additions & 4 deletions

File tree

Makefile

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Makefile for nwx CLI
2+
3+
# Build variables
4+
BINARY_NAME=nwx
5+
BINARY_PATH=./$(BINARY_NAME)
6+
INSTALL_PATH=/usr/local/bin/$(BINARY_NAME)
7+
8+
# Go parameters
9+
GOCMD=go
10+
GOBUILD=$(GOCMD) build
11+
GOCLEAN=$(GOCMD) clean
12+
GOTEST=$(GOCMD) test
13+
GOGET=$(GOCMD) get
14+
GOMOD=$(GOCMD) mod
15+
16+
# Build the binary
17+
build:
18+
$(GOBUILD) -o $(BINARY_PATH) -v .
19+
20+
# Clean build artifacts
21+
clean:
22+
$(GOCLEAN)
23+
rm -f $(BINARY_PATH)
24+
25+
# Test the application
26+
test:
27+
$(GOTEST) -v ./...
28+
29+
# Install dependencies
30+
deps:
31+
$(GOMOD) tidy
32+
$(GOMOD) download
33+
34+
# Install globally (requires sudo)
35+
install: build
36+
sudo cp $(BINARY_PATH) $(INSTALL_PATH)
37+
sudo chmod +x $(INSTALL_PATH)
38+
@echo "✅ nwx installed globally to $(INSTALL_PATH)"
39+
@echo "You can now run 'nwx' from anywhere!"
40+
41+
# Install to user bin directory (no sudo required)
42+
install-user: build
43+
mkdir -p ~/bin
44+
cp $(BINARY_PATH) ~/bin/$(BINARY_NAME)
45+
chmod +x ~/bin/$(BINARY_NAME)
46+
@echo "✅ nwx installed to ~/bin/$(BINARY_NAME)"
47+
@echo "Make sure ~/bin is in your PATH:"
48+
@echo " echo 'export PATH=\"\$$HOME/bin:\$$PATH\"' >> ~/.zshrc"
49+
@echo " source ~/.zshrc"
50+
51+
# Uninstall from system
52+
uninstall:
53+
sudo rm -f $(INSTALL_PATH)
54+
@echo "✅ nwx removed from system"
55+
56+
# Uninstall from user bin
57+
uninstall-user:
58+
rm -f ~/bin/$(BINARY_NAME)
59+
@echo "✅ nwx removed from ~/bin"
60+
61+
# Run the application
62+
run:
63+
$(GOBUILD) -o $(BINARY_PATH) -v .
64+
$(BINARY_PATH)
65+
66+
# Help
67+
help:
68+
@echo "Available commands:"
69+
@echo " make build - Build the binary"
70+
@echo " make install - Install globally (requires sudo)"
71+
@echo " make install-user - Install to ~/bin (no sudo)"
72+
@echo " make uninstall - Remove from system"
73+
@echo " make uninstall-user - Remove from ~/bin"
74+
@echo " make test - Run tests"
75+
@echo " make clean - Clean build artifacts"
76+
@echo " make deps - Install dependencies"
77+
@echo " make run - Build and run"
78+
79+
.PHONY: build clean test deps install install-user uninstall uninstall-user run help

cmd/aa_config.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var aaConfigCmd = &cobra.Command{
12+
Use: "config",
13+
Short: "Access Analyzer configuration",
14+
Long: "Manage Access Analyzer configuration settings",
15+
Run: func(cmd *cobra.Command, args []string) {
16+
fmt.Println("Access Analyzer Configuration")
17+
fmt.Println("Available options:")
18+
fmt.Println(" --endpoint Set the Access Analyzer API endpoint")
19+
fmt.Println(" --show Show current configuration")
20+
fmt.Println()
21+
fmt.Println("Examples:")
22+
fmt.Println(" nwx aa config --endpoint=\"http://localhost:3020\"")
23+
fmt.Println(" nwx aa config --show")
24+
},
25+
}
26+
27+
var (
28+
endpointFlag string
29+
showFlag bool
30+
)
31+
32+
func init() {
33+
aaConfigCmd.Flags().StringVar(&endpointFlag, "endpoint", "", "Set the Access Analyzer API endpoint")
34+
aaConfigCmd.Flags().BoolVar(&showFlag, "show", false, "Show current configuration")
35+
36+
aaConfigCmd.PreRun = func(cmd *cobra.Command, args []string) {
37+
if endpointFlag != "" {
38+
if err := setAAEndpoint(endpointFlag); err != nil {
39+
fmt.Fprintf(os.Stderr, "Error setting endpoint: %v\n", err)
40+
os.Exit(1)
41+
}
42+
fmt.Printf("✅ Access Analyzer endpoint set to: %s\n", endpointFlag)
43+
// TODO: Test connection
44+
fmt.Println("⚠️ Connection test not implemented yet")
45+
}
46+
47+
if showFlag {
48+
showAAConfig()
49+
}
50+
51+
// If no flags provided, show help
52+
if endpointFlag == "" && !showFlag {
53+
cmd.Help()
54+
}
55+
}
56+
57+
accessAnalyzerCmd.AddCommand(aaConfigCmd)
58+
}
59+
60+
func setAAEndpoint(endpoint string) error {
61+
configDir, err := getAAConfigDir()
62+
if err != nil {
63+
return err
64+
}
65+
66+
// Create config directory if it doesn't exist
67+
if err := os.MkdirAll(configDir, 0755); err != nil {
68+
return err
69+
}
70+
71+
configFile := filepath.Join(configDir, "endpoint")
72+
return os.WriteFile(configFile, []byte(endpoint), 0644)
73+
}
74+
75+
func getAAEndpoint() (string, error) {
76+
configDir, err := getAAConfigDir()
77+
if err != nil {
78+
return "", err
79+
}
80+
81+
configFile := filepath.Join(configDir, "endpoint")
82+
data, err := os.ReadFile(configFile)
83+
if os.IsNotExist(err) {
84+
return "", nil // No config file exists yet
85+
}
86+
if err != nil {
87+
return "", err
88+
}
89+
90+
return string(data), nil
91+
}
92+
93+
func getAAConfigDir() (string, error) {
94+
homeDir, err := os.UserHomeDir()
95+
if err != nil {
96+
return "", err
97+
}
98+
return filepath.Join(homeDir, ".nwx", "access-analyzer"), nil
99+
}
100+
101+
func showAAConfig() {
102+
fmt.Println("Access Analyzer Configuration:")
103+
104+
endpoint, err := getAAEndpoint()
105+
if err != nil {
106+
fmt.Printf(" endpoint: <error: %v>\n", err)
107+
} else if endpoint == "" {
108+
fmt.Println(" endpoint: <not configured>")
109+
} else {
110+
fmt.Printf(" endpoint: %s\n", endpoint)
111+
}
112+
}

cmd/access_analyzer.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
var accessAnalyzerCmd = &cobra.Command{
10+
Use: "access-analyzer",
11+
Aliases: []string{"aa"},
12+
Short: "Access Analyzer commands",
13+
Long: "Commands for managing Access Analyzer scanners, sources, and scans",
14+
Run: func(cmd *cobra.Command, args []string) {
15+
fmt.Println("Access Analyzer CLI")
16+
fmt.Println("Available commands:")
17+
fmt.Println(" nwx aa config - Configuration management")
18+
fmt.Println(" nwx aa scanner - Scanner management")
19+
fmt.Println(" nwx aa source - Source management")
20+
fmt.Println(" nwx aa scan - Scan management")
21+
fmt.Println()
22+
fmt.Println("Use 'nwx aa <command> --help' for more information about a command.")
23+
},
24+
}
25+
26+
27+
func init() {
28+
rootCmd.AddCommand(accessAnalyzerCmd)
29+
}

cmd/api_client.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"net/url"
9+
"time"
10+
)
11+
12+
// API client for Access Analyzer
13+
type APIClient struct {
14+
BaseURL string
15+
Client *http.Client
16+
}
17+
18+
// SourceType represents a scanner/source type from the API
19+
type SourceType struct {
20+
SourceTypeID string `json:"sourceTypeId"`
21+
TypeName string `json:"typeName"`
22+
DisplayName string `json:"displayName"`
23+
Description string `json:"description"`
24+
Version string `json:"version"`
25+
ScannerImage string `json:"scannerImage"`
26+
IsActive bool `json:"isActive"`
27+
IsBuiltIn bool `json:"isBuiltIn"`
28+
CreatedAt string `json:"createdAt"`
29+
UpdatedAt string `json:"updatedAt"`
30+
SupportedScans []string `json:"supportedScanTypes,omitempty"`
31+
Icon string `json:"icon,omitempty"`
32+
}
33+
34+
// SourceTypeListResponse represents the API response for listing source types
35+
type SourceTypeListResponse struct {
36+
Data []SourceType `json:"data"`
37+
Pagination PaginationMetadata `json:"pagination"`
38+
}
39+
40+
// PaginationMetadata represents pagination information
41+
type PaginationMetadata struct {
42+
Page int `json:"page"`
43+
PageSize int `json:"pageSize"`
44+
TotalItems int `json:"totalItems"`
45+
TotalPages int `json:"totalPages"`
46+
}
47+
48+
// NewAPIClient creates a new API client
49+
func NewAPIClient(baseURL string) *APIClient {
50+
return &APIClient{
51+
BaseURL: baseURL,
52+
Client: &http.Client{
53+
Timeout: 30 * time.Second,
54+
},
55+
}
56+
}
57+
58+
// GetSourceTypes fetches all source types from the API
59+
func (c *APIClient) GetSourceTypes() (*SourceTypeListResponse, error) {
60+
// Build URL with pagination
61+
u, err := url.Parse(c.BaseURL + "/source-types")
62+
if err != nil {
63+
return nil, fmt.Errorf("invalid base URL: %w", err)
64+
}
65+
66+
params := url.Values{}
67+
params.Set("page", "1")
68+
params.Set("pageSize", "100") // Get all scanners in one request
69+
u.RawQuery = params.Encode()
70+
71+
// Make HTTP request
72+
resp, err := c.Client.Get(u.String())
73+
if err != nil {
74+
return nil, fmt.Errorf("failed to make API request: %w", err)
75+
}
76+
defer resp.Body.Close()
77+
78+
// Check status code
79+
if resp.StatusCode != http.StatusOK {
80+
body, _ := io.ReadAll(resp.Body)
81+
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
82+
}
83+
84+
// Parse response
85+
var result SourceTypeListResponse
86+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
87+
return nil, fmt.Errorf("failed to parse API response: %w", err)
88+
}
89+
90+
return &result, nil
91+
}
92+
93+
// TestConnection tests the connection to the API
94+
func (c *APIClient) TestConnection() error {
95+
// Try to get source types as a health check
96+
resp, err := c.Client.Get(c.BaseURL + "/source-types?page=1&pageSize=1")
97+
if err != nil {
98+
return fmt.Errorf("connection failed: %w", err)
99+
}
100+
defer resp.Body.Close()
101+
102+
if resp.StatusCode != http.StatusOK {
103+
body, _ := io.ReadAll(resp.Body)
104+
return fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body))
105+
}
106+
107+
return nil
108+
}
109+
110+
// Helper function to get API client with configured endpoint
111+
func getAPIClient() (*APIClient, error) {
112+
endpoint, err := getAAEndpoint()
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
if endpoint == "" {
118+
return nil, fmt.Errorf("no endpoint configured - use 'nwx aa config --endpoint=\"<url>\"'")
119+
}
120+
121+
return NewAPIClient(endpoint), nil
122+
}

0 commit comments

Comments
 (0)