Files
igodwin eda033ff9b
CI / Lint (push) Successful in 2m29s
Build and Publish Container / build-and-publish (push) Successful in 2m58s
CI / Vulnerability scan (push) Successful in 44s
CI / Test (push) Successful in 1m45s
fix: clear golangci-lint backlog and make lint job blocking
Addresses errcheck, gosec, revive, staticcheck, and unused findings
across the codebase (unchecked error returns, unsafe file inclusion
warnings on operator/test-controlled paths, missing package comments,
unused parameters, deprecated API usage). Also fixes two suppression
comments that were silently no-ops due to wrong syntax (#nosec needs
a leading '#', nolint reasons need '//' not '--').

With the backlog clear, drop continue-on-error from the CI lint job
per the plan left in b4b4806.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 10:32:51 -07:00

405 lines
9.1 KiB
Go

// Command client is a CLI for sending and managing notifications through
// the notifier service's REST API.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"time"
"github.com/igodwin/notifier/pkg/client"
)
func main() {
// Command
if len(os.Args) < 2 {
printUsage()
os.Exit(1)
}
command := os.Args[1]
switch command {
case "send":
cmdSend(os.Args[2:])
case "status":
cmdStatus(os.Args[2:])
case "list":
cmdList(os.Args[2:])
case "stats":
cmdStats(os.Args[2:])
case "notifiers":
cmdNotifiers(os.Args[2:])
case "health":
cmdHealth(os.Args[2:])
default:
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
printUsage()
os.Exit(1)
}
}
func printUsage() {
fmt.Print(`Notifier Client - CLI for sending notifications
Usage:
client <command> [options]
Commands:
send Send a notification
status Get notification status
list List notifications
stats Get notification statistics
notifiers List available notifiers
health Check service health
Global Options:
--url Service URL (default: http://localhost:8080)
--key API key for authentication (optional)
--timeout Request timeout (default: 30s)
Examples:
# Send email notification
client send --type email --subject "Alert" --body "System down" --recipients user@example.com
# Check notification status
client status --id <notification-id>
# List recent notifications
client list --limit 10
# Get service stats
client stats
# Check health
client health --url http://localhost:8080
`)
}
func cmdSend(args []string) {
fs := flag.NewFlagSet("send", flag.ExitOnError)
fs.Usage = func() {
fmt.Print(`Send a notification
Usage:
client send [options]
Options:
--url Service URL (default: http://localhost:8080)
--key API key (optional)
--type Notification type (stdout, email, slack, ntfy) - required
--subject Subject line
--body Message body - required
--account Account name (optional, uses default)
--recipients Comma-separated recipients
--timeout Request timeout (default: 30s)
`)
}
baseURL := fs.String("url", "http://localhost:8080", "")
apiKey := fs.String("key", "", "")
timeout := fs.Duration("timeout", 30*time.Second, "")
notifType := fs.String("type", "", "")
subject := fs.String("subject", "", "")
body := fs.String("body", "", "")
account := fs.String("account", "", "")
recipients := fs.String("recipients", "", "")
_ = fs.Parse(args) // flag.ExitOnError means Parse never returns a non-nil error
if *notifType == "" || *body == "" {
fmt.Fprintf(os.Stderr, "Error: --type and --body are required\n")
fs.Usage()
os.Exit(1)
}
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
cfg := client.Config{
BaseURL: *baseURL,
APIKey: *apiKey,
Timeout: *timeout,
TLSInsecure: false,
}
c := client.NewRESTClient(cfg)
recipientList := []string{}
if *recipients != "" {
recipientList = strings.Split(*recipients, ",")
for i := range recipientList {
recipientList[i] = strings.TrimSpace(recipientList[i])
}
}
req := client.NotificationRequest{
Type: *notifType,
Subject: *subject,
Body: *body,
Account: *account,
Recipients: recipientList,
}
resp, err := c.Send(ctx, req)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
data, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(data))
}
func cmdStatus(args []string) {
fs := flag.NewFlagSet("status", flag.ExitOnError)
fs.Usage = func() {
fmt.Print(`Get notification status
Usage:
client status [options]
Options:
--url Service URL (default: http://localhost:8080)
--key API key (optional)
--id Notification ID - required
--timeout Request timeout (default: 30s)
`)
}
baseURL := fs.String("url", "http://localhost:8080", "")
apiKey := fs.String("key", "", "")
timeout := fs.Duration("timeout", 30*time.Second, "")
id := fs.String("id", "", "")
_ = fs.Parse(args) // flag.ExitOnError means Parse never returns a non-nil error
if *id == "" {
fmt.Fprintf(os.Stderr, "Error: --id is required\n")
fs.Usage()
os.Exit(1)
}
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
cfg := client.Config{
BaseURL: *baseURL,
APIKey: *apiKey,
Timeout: *timeout,
TLSInsecure: false,
}
c := client.NewRESTClient(cfg)
notif, err := c.GetNotification(ctx, *id)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
data, _ := json.MarshalIndent(notif, "", " ")
fmt.Println(string(data))
}
func cmdList(args []string) {
fs := flag.NewFlagSet("list", flag.ExitOnError)
fs.Usage = func() {
fmt.Print(`List notifications
Usage:
client list [options]
Options:
--url Service URL (default: http://localhost:8080)
--key API key (optional)
--type Filter by type (comma-separated)
--status Filter by status (comma-separated)
--limit Limit results (default: 10)
--offset Offset (default: 0)
--timeout Request timeout (default: 30s)
`)
}
baseURL := fs.String("url", "http://localhost:8080", "")
apiKey := fs.String("key", "", "")
timeout := fs.Duration("timeout", 30*time.Second, "")
filterType := fs.String("type", "", "")
filterStatus := fs.String("status", "", "")
limit := fs.Int("limit", 10, "")
offset := fs.Int("offset", 0, "")
_ = fs.Parse(args) // flag.ExitOnError means Parse never returns a non-nil error
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
cfg := client.Config{
BaseURL: *baseURL,
APIKey: *apiKey,
Timeout: *timeout,
TLSInsecure: false,
}
c := client.NewRESTClient(cfg)
filter := client.ListNotificationsRequest{
Limit: *limit,
Offset: *offset,
}
if *filterType != "" {
filter.Types = strings.Split(*filterType, ",")
}
if *filterStatus != "" {
statuses := strings.Split(*filterStatus, ",")
for _, s := range statuses {
filter.Statuses = append(filter.Statuses, client.NotificationStatus(strings.TrimSpace(s)))
}
}
resp, err := c.ListNotifications(ctx, filter)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
data, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(data))
}
func cmdStats(args []string) {
fs := flag.NewFlagSet("stats", flag.ExitOnError)
fs.Usage = func() {
fmt.Print(`Get notification statistics
Usage:
client stats [options]
Options:
--url Service URL (default: http://localhost:8080)
--key API key (optional)
--timeout Request timeout (default: 30s)
`)
}
baseURL := fs.String("url", "http://localhost:8080", "")
apiKey := fs.String("key", "", "")
timeout := fs.Duration("timeout", 30*time.Second, "")
_ = fs.Parse(args) // flag.ExitOnError means Parse never returns a non-nil error
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
cfg := client.Config{
BaseURL: *baseURL,
APIKey: *apiKey,
Timeout: *timeout,
TLSInsecure: false,
}
c := client.NewRESTClient(cfg)
stats, err := c.GetStats(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
data, _ := json.MarshalIndent(stats, "", " ")
fmt.Println(string(data))
}
func cmdNotifiers(args []string) {
fs := flag.NewFlagSet("notifiers", flag.ExitOnError)
fs.Usage = func() {
fmt.Print(`List available notifiers
Usage:
client notifiers [options]
Options:
--url Service URL (default: http://localhost:8080)
--key API key (optional)
--timeout Request timeout (default: 30s)
`)
}
baseURL := fs.String("url", "http://localhost:8080", "")
apiKey := fs.String("key", "", "")
timeout := fs.Duration("timeout", 30*time.Second, "")
_ = fs.Parse(args) // flag.ExitOnError means Parse never returns a non-nil error
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
cfg := client.Config{
BaseURL: *baseURL,
APIKey: *apiKey,
Timeout: *timeout,
TLSInsecure: false,
}
c := client.NewRESTClient(cfg)
notifiers, err := c.GetNotifiers(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
data, _ := json.MarshalIndent(notifiers, "", " ")
fmt.Println(string(data))
}
func cmdHealth(args []string) {
fs := flag.NewFlagSet("health", flag.ExitOnError)
fs.Usage = func() {
fmt.Print(`Check service health
Usage:
client health [options]
Options:
--url Service URL (default: http://localhost:8080)
--timeout Request timeout (default: 30s)
`)
}
baseURL := fs.String("url", "http://localhost:8080", "")
timeout := fs.Duration("timeout", 30*time.Second, "")
_ = fs.Parse(args) // flag.ExitOnError means Parse never returns a non-nil error
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
cfg := client.Config{
BaseURL: *baseURL,
Timeout: *timeout,
TLSInsecure: false,
}
c := client.NewRESTClient(cfg)
healthy, err := c.HealthCheck(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if healthy {
fmt.Println("Service is healthy")
os.Exit(0)
}
fmt.Println("Service is unhealthy")
os.Exit(1)
}