main merge

This commit is contained in:
Ben Duncan
2026-04-23 14:10:34 +10:00
60 changed files with 1077 additions and 294 deletions
+3
View File
@@ -34,6 +34,9 @@ else
endif
# Quiet-mode filters (active when QUIET=1, set by preflight via recursive make)
# Note: grep pipelines use PIPESTATUS[0] so the exit status of `go test`
# propagates through the filter — otherwise a test failure is swallowed by
# grep's own (success) exit code and preflight prints "passed" on red.
ifdef QUIET
_Q = @
_COVQ = 2>&1 | { grep -Ev '^\s*(ok|PASS|\?|=== RUN|--- PASS:)\s' | grep -v 'coverage: 0\.0%' || true; }; exit $${PIPESTATUS[0]}
+108 -27
View File
@@ -253,7 +253,8 @@ func init() {
adminInitCmd.Flags().Int("nodes", 3, "Number of nodes to expect for cluster")
adminInitCmd.Flags().String("host", "", "Leader node to join (if not specified, tries multicast discovery)")
adminInitCmd.Flags().Int("port", 4432, "Port to bind cluster services on")
adminInitCmd.Flags().String("bind", "127.0.0.1", "IP address to bind services to (e.g., 10.11.12.1 for multi-node)")
adminInitCmd.Flags().String("bind", "0.0.0.0", "IP address to bind services to (e.g., 10.11.12.1 for multi-node). Default 0.0.0.0 listens on all interfaces.")
adminInitCmd.Flags().String("advertise", "", "External IP that off-host clients (ALB VMs, remote operators) should dial. Auto-detected from default route when empty.")
adminInitCmd.Flags().String("cluster-bind", "", "IP address to bind NATS cluster services to (e.g., 10.11.12.1 for multi-node)")
adminInitCmd.Flags().String("cluster-routes", "", "NATS cluster hosts for routing specify multiple with comma (e.g., 10.11.12.1:4248,10.11.12.2:4248 for multi-node)")
adminInitCmd.Flags().String("predastore-nodes", "", "Comma-separated IPs for multi-node Predastore cluster (e.g., 10.11.12.1,10.11.12.2,10.11.12.3). Requires >= 3 nodes.")
@@ -282,6 +283,7 @@ func init() {
adminJoinCmd.Flags().String("data-dir", "", "Data directory for this node (default: ~/spinifex)")
adminJoinCmd.Flags().Int("port", 4432, "Port to bind cluster services on")
adminJoinCmd.Flags().String("bind", "0.0.0.0", "IP address to bind services to (e.g., 10.11.12.2 for multi-node on single host)")
adminJoinCmd.Flags().String("advertise", "", "External IP this node advertises to other cluster members. Defaults to --bind, or auto-detected WAN IP when --bind is 0.0.0.0.")
adminJoinCmd.Flags().String("cluster-bind", "", "IP address to bind NATS cluster services to (e.g., 10.11.12.1 for multi-node)")
adminJoinCmd.Flags().String("cluster-routes", "", "NATS cluster hosts for routing specify multiple with comma (e.g., 10.11.12.1:4248,10.11.12.2:4248 for multi-node)")
adminJoinCmd.Flags().String("token", "", "Join token from the init node (required)")
@@ -623,6 +625,7 @@ func runAdminInit(cmd *cobra.Command, args []string) {
nodes, _ := cmd.Flags().GetInt("nodes")
port, _ := cmd.Flags().GetInt("port")
bindIP, _ := cmd.Flags().GetString("bind")
advertiseFlag, _ := cmd.Flags().GetString("advertise")
clusterBind, _ := cmd.Flags().GetString("cluster-bind")
clusterRoutesStr, _ := cmd.Flags().GetString("cluster-routes")
var clusterRoutes []string
@@ -806,6 +809,19 @@ func runAdminInit(cmd *cobra.Command, args []string) {
os.Exit(1)
}
// Resolve the off-host advertise IP. DetectNetwork may have been skipped
// when --no-external is set; detect lazily if we need the WAN IP.
if advertiseFlag == "" && (bindIP == "0.0.0.0" || bindIP == "127.0.0.1") && detectedNet == nil {
if d, derr := admin.DetectNetwork(); derr == nil {
detectedNet = d
}
}
advertiseIP, err := resolveAdvertiseIP(bindIP, advertiseFlag, detectedNet)
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
os.Exit(1)
}
// Validate port range
if port < 1 || port > 65535 {
fmt.Fprintf(os.Stderr, "❌ Error: Port must be between 1 and 65535, got: %d\n", port)
@@ -817,7 +833,7 @@ func runAdminInit(cmd *cobra.Command, args []string) {
clusterBind = bindIP
}
fmt.Printf("Initializing Spinifex with bind IP: %s, port: %d\n", bindIP, port)
fmt.Printf("Initializing Spinifex with bind IP: %s, advertise IP: %s, port: %d\n", bindIP, advertiseIP, port)
// Default config directory
if configDir == "" {
@@ -910,8 +926,9 @@ func runAdminInit(cmd *cobra.Command, args []string) {
}
spxRoot = filepath.Clean(spxRoot)
// Determine if this is a multi-node formation
isMultiNode := nodes >= 2 && bindIP != "0.0.0.0" && bindIP != "127.0.0.1"
// Determine if this is a multi-node formation. Operator intent comes from
// --nodes, not from whether --bind was left at the 0.0.0.0 default.
isMultiNode := nodes >= 2
if isMultiNode {
// Build cluster-wide network config for propagation to joining nodes
@@ -941,7 +958,7 @@ func runAdminInit(cmd *cobra.Command, args []string) {
}
runAdminInitMultiNode(cmd, accessKey, secretKey, accountID, natsToken, clusterName,
configDir, spxRoot, certPath, region, az, node, bindIP, clusterBind, email,
configDir, spxRoot, certPath, region, az, node, bindIP, advertiseIP, clusterBind, email,
port, nodes, formationTimeoutStr, tokenTTLStr, services, networkConfig)
return
}
@@ -1024,6 +1041,7 @@ func runAdminInit(cmd *cobra.Command, args []string) {
Az: az,
Port: portStr,
BindIP: bindIP,
AdvertiseIP: advertiseIP,
ClusterBindIP: clusterBind,
ClusterRoutes: clusterRoutes,
ClusterName: clusterName,
@@ -1078,7 +1096,7 @@ func runAdminInit(cmd *cobra.Command, args []string) {
os.Exit(1)
}
finalizeNodeSetup(spxRoot, certPath, bootstrapResult.AdminAccessKey, bootstrapResult.AdminSecretKey, region, bindIP)
finalizeNodeSetup(spxRoot, certPath, bootstrapResult.AdminAccessKey, bootstrapResult.AdminSecretKey, region, bindIP, advertiseIP)
// Print success message
fmt.Println("\n🎉 Spinifex initialization complete!")
@@ -1086,6 +1104,9 @@ func runAdminInit(cmd *cobra.Command, args []string) {
fmt.Println("🔗 Configuration:")
fmt.Printf(" Config file: %s\n", spinifexTomlPath)
fmt.Printf(" Data directory: %s\n", spxRoot)
fmt.Printf(" Bind IP: %s (listen)\n", bindIP)
fmt.Printf(" Advertise IP: %s (off-host dial target)\n", advertiseIP)
fmt.Printf(" Loopback: 127.0.0.1 (in-process dial target)\n")
fmt.Println()
}
@@ -1093,7 +1114,7 @@ func runAdminInit(cmd *cobra.Command, args []string) {
// It starts a formation server, registers this node, waits for all nodes to join,
// then generates configs with complete cluster topology.
func runAdminInitMultiNode(cmd *cobra.Command, accessKey, secretKey, accountID, natsToken, clusterName,
configDir, spxRoot, certPath, region, az, node, bindIP, clusterBind, email string,
configDir, spxRoot, certPath, region, az, node, bindIP, advertiseIP, clusterBind, email string,
port, expectedNodes int, formationTimeoutStr, tokenTTLStr string, services []string, networkConfig *formation.NetworkConfig) {
formationTimeout, err := time.ParseDuration(formationTimeoutStr)
if err != nil {
@@ -1162,12 +1183,13 @@ func runAdminInitMultiNode(cmd *cobra.Command, accessKey, secretKey, accountID,
// Register self (init node) as the first node
selfNode := formation.NodeInfo{
Name: node,
BindIP: bindIP,
ClusterIP: clusterBind,
Region: region,
AZ: az,
Port: port,
Name: node,
BindIP: bindIP,
AdvertiseIP: advertiseIP,
ClusterIP: clusterBind,
Region: region,
AZ: az,
Port: port,
}
if err := fs.RegisterNode(selfNode); err != nil {
fmt.Fprintf(os.Stderr, "❌ Error registering self: %v\n", err)
@@ -1255,6 +1277,7 @@ func runAdminInitMultiNode(cmd *cobra.Command, accessKey, secretKey, accountID,
Az: az,
Port: portStr,
BindIP: bindIP,
AdvertiseIP: advertiseIP,
ClusterBindIP: clusterBind,
ClusterRoutes: clusterRoutes,
ClusterName: clusterName,
@@ -1279,7 +1302,7 @@ func runAdminInitMultiNode(cmd *cobra.Command, accessKey, secretKey, accountID,
os.Exit(1)
}
finalizeNodeSetup(spxRoot, certPath, bootstrapResult.AdminAccessKey, bootstrapResult.AdminSecretKey, region, bindIP)
finalizeNodeSetup(spxRoot, certPath, bootstrapResult.AdminAccessKey, bootstrapResult.AdminSecretKey, region, bindIP, advertiseIP)
// Keep formation server running briefly so joining nodes can fetch complete status
fmt.Println("\n⏳ Waiting for joining nodes to fetch cluster data...")
@@ -1295,9 +1318,14 @@ func runAdminInitMultiNode(cmd *cobra.Command, accessKey, secretKey, accountID,
fmt.Println("\n🎉 Cluster formation complete!")
fmt.Printf(" Cluster: %s (%d nodes)\n", clusterName, expectedNodes)
fmt.Printf(" Region: %s\n", region)
fmt.Printf(" Bind: %s Advertise: %s Loopback: 127.0.0.1\n", bindIP, advertiseIP)
fmt.Println(" Nodes:")
for name, n := range allNodes {
fmt.Printf(" - %s (%s)\n", name, n.BindIP)
adv := n.AdvertiseIP
if adv == "" {
adv = n.BindIP
}
fmt.Printf(" - %s (bind=%s advertise=%s)\n", name, n.BindIP, adv)
}
fmt.Println("\n📋 Next steps:")
fmt.Println(" 1. Start services on ALL nodes:")
@@ -1320,6 +1348,7 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
dataDir, _ := cmd.Flags().GetString("data-dir")
port, _ := cmd.Flags().GetInt("port")
bindIP, _ := cmd.Flags().GetString("bind")
advertiseFlag, _ := cmd.Flags().GetString("advertise")
configDir, _ := cmd.Flags().GetString("config-dir")
clusterBind, _ := cmd.Flags().GetString("cluster-bind")
services, _ := cmd.Flags().GetStringSlice("services")
@@ -1358,6 +1387,20 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
os.Exit(1)
}
// Resolve the off-host advertise IP before the JoinRequest is built so
// peers record a reachable dial target instead of 0.0.0.0.
var detectedNet *admin.DetectedNetwork
if advertiseFlag == "" && (bindIP == "0.0.0.0" || bindIP == "127.0.0.1") {
if d, derr := admin.DetectNetwork(); derr == nil {
detectedNet = d
}
}
advertiseIP, err := resolveAdvertiseIP(bindIP, advertiseFlag, detectedNet)
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
os.Exit(1)
}
// Validate port range
if port < 1 || port > 65535 {
fmt.Fprintf(os.Stderr, "❌ Error: Port must be between 1 and 65535, got: %d\n", port)
@@ -1380,17 +1423,19 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
fmt.Printf("Region: %s\n", region)
fmt.Printf("AZ: %s\n", az)
fmt.Printf("Bind IP: %s\n", bindIP)
fmt.Printf("Advertise IP: %s\n", advertiseIP)
fmt.Printf("Port: %d\n\n", port)
// POST join request to formation server
joinReq := formation.JoinRequest{
NodeInfo: formation.NodeInfo{
Name: node,
BindIP: bindIP,
ClusterIP: clusterBind,
Region: region,
AZ: az,
Port: port,
Name: node,
BindIP: bindIP,
AdvertiseIP: advertiseIP,
ClusterIP: clusterBind,
Region: region,
AZ: az,
Port: port,
},
}
@@ -1640,6 +1685,7 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
Az: az,
Port: portStr,
BindIP: bindIP,
AdvertiseIP: advertiseIP,
ClusterBindIP: clusterBind,
ClusterRoutes: clusterRoutes,
ClusterName: creds.ClusterName,
@@ -1664,17 +1710,44 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
os.Exit(1)
}
finalizeNodeSetup(dataDir, caCertPath, creds.AdminAccessKey, creds.AdminSecretKey, creds.Region, bindIP)
finalizeNodeSetup(dataDir, caCertPath, creds.AdminAccessKey, creds.AdminSecretKey, creds.Region, bindIP, advertiseIP)
// Print cluster summary
fmt.Println("\n🎉 Node successfully joined cluster!")
fmt.Printf(" Cluster: %s (%d nodes)\n", creds.ClusterName, len(statusResp.Nodes))
fmt.Printf(" Bind: %s Advertise: %s Loopback: 127.0.0.1\n", bindIP, advertiseIP)
fmt.Println(" Nodes:")
for name, n := range statusResp.Nodes {
fmt.Printf(" - %s (%s)\n", name, n.BindIP)
adv := n.AdvertiseIP
if adv == "" {
adv = n.BindIP
}
fmt.Printf(" - %s (bind=%s advertise=%s)\n", name, n.BindIP, adv)
}
}
// resolveAdvertiseIP picks the off-host dial target for this node.
// Precedence: explicit --advertise flag > specific --bind IP > auto-detected
// WAN IP > loopback (with warning that off-host clients cannot reach this node).
func resolveAdvertiseIP(bindIP, advertiseFlag string, detected *admin.DetectedNetwork) (string, error) {
if advertiseFlag != "" {
if net.ParseIP(advertiseFlag) == nil {
return "", fmt.Errorf("--advertise: invalid IP %q", advertiseFlag)
}
return advertiseFlag, nil
}
if bindIP != "" && bindIP != "0.0.0.0" && bindIP != "127.0.0.1" {
return bindIP, nil
}
if detected != nil && detected.WAN != nil && detected.WAN.IP != "" {
return detected.WAN.IP, nil
}
fmt.Fprintln(os.Stderr,
"⚠️ Could not auto-detect a WAN IP. Off-host clients (ALB VMs, remote operators) "+
"will not be able to reach this node. Re-run with --advertise <IP> to fix.")
return "127.0.0.1", nil
}
// buildRemoteNodes converts formation NodeInfo into RemoteNode entries,
// excluding the local node. This puts all cluster members into spinifex.toml
// so config is the source of truth for expected cluster membership.
@@ -1684,9 +1757,15 @@ func buildRemoteNodes(allNodes map[string]formation.NodeInfo, localNode string)
if name == localNode {
continue
}
// Prefer the peer's advertise IP (off-host dial target); fall back
// to BindIP for pre-siv-8 joiners that didn't send AdvertiseIP.
host := n.AdvertiseIP
if host == "" {
host = n.BindIP
}
remote = append(remote, admin.RemoteNode{
Name: name,
Host: n.BindIP,
Host: host,
Region: n.Region,
AZ: n.AZ,
Services: n.Services,
@@ -1939,10 +2018,12 @@ func generateAndWriteConfigs(dirs configDirs, spinifexTomlPath string, settings
}
// finalizeNodeSetup configures AWS credentials, creates service directories,
// and sets ownership when running as root.
func finalizeNodeSetup(dataDir, certPath, adminAccessKey, adminSecretKey, region, bindIP string) {
// and sets ownership when running as root. advertiseIP is the off-host dial
// target for this node; it is threaded through SetupAWSCredentials's wanIP
// param for a future --operator-endpoint flag (see SetupAWSCredentials doc).
func finalizeNodeSetup(dataDir, certPath, adminAccessKey, adminSecretKey, region, bindIP, advertiseIP string) {
fmt.Println("\n🔧 Configuring AWS credentials...")
if err := admin.SetupAWSCredentials(adminAccessKey, adminSecretKey, region, certPath, bindIP); err != nil {
if err := admin.SetupAWSCredentials(adminAccessKey, adminSecretKey, region, certPath, bindIP, advertiseIP); err != nil {
fmt.Fprintf(os.Stderr, "Warning: Could not update AWS credentials: %v\n", err)
} else {
fmt.Println("✅ AWS credentials configured")
+131
View File
@@ -4,10 +4,15 @@ import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/mulgadc/spinifex/spinifex/admin"
"github.com/mulgadc/spinifex/spinifex/formation"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Regression guard: source URL must print for every error kind, not just
@@ -32,3 +37,129 @@ func TestPrintChecksumError(t *testing.T) {
assert.Contains(t, out, "spx admin images import --name "+imageName+" --force")
}
}
// buildRemoteNodes must prefer AdvertiseIP (off-host dial target) and fall
// back to BindIP when the peer pre-dates siv-8 and didn't send AdvertiseIP.
func TestBuildRemoteNodes_AdvertiseFallback(t *testing.T) {
nodes := map[string]formation.NodeInfo{
"node1": {Name: "node1", BindIP: "10.0.0.1", AdvertiseIP: "203.0.113.1"},
"node2": {Name: "node2", BindIP: "10.0.0.2"}, // legacy joiner
"node3": {Name: "node3", BindIP: "10.0.0.3", AdvertiseIP: "203.0.113.3"},
}
got := buildRemoteNodes(nodes, "node3")
if assert.Len(t, got, 2) {
assert.Equal(t, "node1", got[0].Name)
assert.Equal(t, "203.0.113.1", got[0].Host, "advertise wins when set")
assert.Equal(t, "node2", got[1].Name)
assert.Equal(t, "10.0.0.2", got[1].Host, "bind fallback when advertise empty")
}
}
func TestResolveAdvertiseIP(t *testing.T) {
wan := &admin.DetectedNetwork{WAN: &admin.DetectedInterface{IP: "192.168.1.21"}}
noWAN := &admin.DetectedNetwork{}
tests := []struct {
name string
bindIP string
advertise string
detected *admin.DetectedNetwork
want string
wantErr bool
}{
{"single-node default, WAN detected", "0.0.0.0", "", wan, "192.168.1.21", false},
{"single-node default, no WAN", "0.0.0.0", "", noWAN, "127.0.0.1", false},
{"single-node explicit advertise", "0.0.0.0", "203.0.113.5", wan, "203.0.113.5", false},
{"multi-node init, specific bind", "10.11.12.1", "", nil, "10.11.12.1", false},
{"multi-node dual-homed override", "10.11.12.1", "203.0.113.5", nil, "203.0.113.5", false},
{"loopback-only test", "127.0.0.1", "", noWAN, "127.0.0.1", false},
{"invalid advertise flag", "0.0.0.0", "not-an-ip", wan, "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := resolveAdvertiseIP(tt.bindIP, tt.advertise, tt.detected)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
// Generated spinifex.toml must include both host (listen, BindIP) and
// advertise (off-host dial target, AdvertiseIP) for the local node.
func TestSpinifexTomlTemplate_AdvertiseField(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "spinifex.toml")
settings := admin.ConfigSettings{
Node: "node1",
Az: "ap-southeast-2a",
Port: "4432",
Region: "ap-southeast-2",
BindIP: "0.0.0.0",
AdvertiseIP: "192.168.1.21",
AccessKey: "AKIATEST",
SecretKey: "SECRET",
AccountID: "123456789012",
NatsToken: "token",
ConfigDir: dir,
OVNNBAddr: "tcp:127.0.0.1:6641",
OVNSBAddr: "tcp:127.0.0.1:6642",
}
require.NoError(t, admin.GenerateConfigFile(path, spinifexTomlTemplate, settings))
data, err := os.ReadFile(path)
require.NoError(t, err)
content := string(data)
assert.Contains(t, content, `host = "0.0.0.0"`, "listen address")
assert.Contains(t, content, `advertise = "192.168.1.21"`, "off-host dial target")
}
// Empty AdvertiseIP (e.g. loading an existing cluster pre-siv-8) must NOT
// render an empty advertise = "" line — downstream fallback to Host kicks in.
func TestSpinifexTomlTemplate_AdvertiseOmittedWhenEmpty(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "spinifex.toml")
settings := admin.ConfigSettings{
Node: "node1",
Az: "ap-southeast-2a",
Port: "4432",
Region: "ap-southeast-2",
BindIP: "10.11.12.1",
AccessKey: "AKIATEST",
SecretKey: "SECRET",
AccountID: "123456789012",
NatsToken: "token",
ConfigDir: dir,
OVNNBAddr: "tcp:127.0.0.1:6641",
OVNSBAddr: "tcp:127.0.0.1:6642",
}
require.NoError(t, admin.GenerateConfigFile(path, spinifexTomlTemplate, settings))
data, _ := os.ReadFile(path)
assert.NotContains(t, string(data), "advertise =")
}
// iam.nats_url must not embed a bare 0.0.0.0 — the predastore process dials
// its own NATS via loopback when listening on wildcard.
func TestPredastoreMultinodeTemplate_NatsURLLoopbackShim(t *testing.T) {
nodes := []admin.PredastoreNodeConfig{
{ID: 1, Host: "10.0.0.1"},
{ID: 2, Host: "10.0.0.2"},
{ID: 3, Host: "10.0.0.3"},
}
content, err := admin.GenerateMultiNodePredastoreConfig(
predastoreMultiNodeTemplate, nodes, "AK", "SK", "ap-southeast-2", "token", "/tmp", "0.0.0.0",
)
require.NoError(t, err)
assert.Contains(t, content, `nats_url = "nats://localhost:4222"`)
assert.NotContains(t, content, `nats_url = "nats://0.0.0.0:4222"`)
// Specific bind IP → stays as-is.
content2, err := admin.GenerateMultiNodePredastoreConfig(
predastoreMultiNodeTemplate, nodes, "AK", "SK", "ap-southeast-2", "token", "/tmp", "10.11.12.1",
)
require.NoError(t, err)
assert.Contains(t, content2, `nats_url = "nats://10.11.12.1:4222"`)
}
@@ -63,7 +63,7 @@ burst = 200
# IAM authentication via NATS KV (enables multi-account S3 access)
[iam]
nats_url = "nats://{{.BindIP}}:4222"
nats_url = "nats://{{if eq .BindIP "0.0.0.0"}}localhost{{else}}{{.BindIP}}{{end}}:4222"
nats_token = "{{.NatsToken}}"
nats_ca_cert = "{{.ConfigDir}}/ca.pem"
master_key_path = "{{.ConfigDir}}/master.key"
+3
View File
@@ -47,6 +47,9 @@ subnet_cidr = "{{.BootstrapSubnetCidr}}"
[nodes.{{.Node}}]
node = "{{.Node}}"
host = "{{.BindIP}}"
{{- if .AdvertiseIP}}
advertise = "{{.AdvertiseIP}}"
{{- end}}
region = "{{.Region}}"
az = "{{.Az}}"
{{- if .Services}}
+77 -1
View File
@@ -20,6 +20,10 @@
# --macvlan Create macvlan off --wan-iface instead of moving NIC directly.
# SSH-safe for single-NIC hosts where WAN NIC carries SSH.
# --dhcp Obtain gateway IP via DHCP on the WAN bridge interface
# --mgmt-bridge=NAME OVS bridge for system-instance control plane (default: br-mgmt)
# --mgmt-cidr=CIDR IPv4 CIDR to assign on the mgmt bridge (default: 10.15.8.1/24)
# --mgmt-iface=NAME Physical/virtual NIC to enslave to the mgmt bridge (multi-node only)
# --no-mgmt-bridge Skip mgmt bridge provisioning (for dev-networking hosts)
# --ovn-remote=ADDR OVN SB DB address (default: tcp:127.0.0.1:6642)
# --encap-ip=IP Geneve tunnel endpoint IP (default: auto-detect)
# --chassis-id=ID OVN chassis identifier (default: chassis-$(hostname -s))
@@ -55,6 +59,10 @@ WAN_BRIDGE=""
WAN_IFACE=""
MACVLAN_MODE=false
EXTERNAL_DHCP=false
MGMT_BRIDGE_ENABLED=true
MGMT_BRIDGE="br-mgmt"
MGMT_CIDR="10.15.8.1/24"
MGMT_IFACE=""
OVN_REMOTE="tcp:127.0.0.1:6642"
ENCAP_IP=""
CHASSIS_ID=""
@@ -67,11 +75,15 @@ for arg in "$@"; do
--dhcp) EXTERNAL_DHCP=true ;;
--wan-bridge=*) WAN_BRIDGE="${arg#*=}" ;;
--wan-iface=*) WAN_IFACE="${arg#*=}" ;;
--mgmt-bridge=*) MGMT_BRIDGE="${arg#*=}" ;;
--mgmt-cidr=*) MGMT_CIDR="${arg#*=}" ;;
--mgmt-iface=*) MGMT_IFACE="${arg#*=}" ;;
--no-mgmt-bridge) MGMT_BRIDGE_ENABLED=false ;;
--ovn-remote=*) OVN_REMOTE="${arg#*=}" ;;
--encap-ip=*) ENCAP_IP="${arg#*=}" ;;
--chassis-id=*) CHASSIS_ID="${arg#*=}" ;;
--help|-h)
head -44 "$0" | tail -42
head -50 "$0" | tail -48
exit 0
;;
*)
@@ -451,6 +463,59 @@ if [ -n "$WAN_BRIDGE" ]; then
fi
fi
# --- Step 3d: Management bridge for system-instance control plane ---
# br-mgmt is an OVS bridge (not L2-learning Linux bridge, not part of OVN
# overlay). fail-mode=standalone makes it behave like a plain L2 switch
# without OVN flows; it is excluded from ovn-bridge-mappings so
# ovn-controller ignores it. System instance TAPs attach via ovs-vsctl
# add-port (see SetupMgmtTapDevice in daemon/network.go).
if [ "$MGMT_BRIDGE_ENABLED" = true ]; then
echo ""
echo "Step 3d: Configuring management bridge ($MGMT_BRIDGE)..."
sudo ovs-vsctl --may-exist add-br "$MGMT_BRIDGE"
sudo ovs-vsctl set Bridge "$MGMT_BRIDGE" \
fail-mode=standalone \
other-config:disable-in-band=true
sudo ip link set "$MGMT_BRIDGE" up
echo " $MGMT_BRIDGE: OVS bridge, fail-mode=standalone, up"
# Enslave physical/virtual mgmt NIC when provided (multi-node deployments
# where the node's mgmt subnet spans multiple hosts).
if [ -n "$MGMT_IFACE" ]; then
if ! ip link show "$MGMT_IFACE" >/dev/null 2>&1; then
echo " ERROR: mgmt interface $MGMT_IFACE does not exist"
ip -o link show | awk -F': ' '{print " " $2}'
exit 1
fi
# Drop any existing IP on the NIC — IP belongs on the bridge.
sudo ip addr flush dev "$MGMT_IFACE" || true
sudo ovs-vsctl --may-exist add-port "$MGMT_BRIDGE" "$MGMT_IFACE"
sudo ip link set "$MGMT_IFACE" up
echo " $MGMT_IFACE: port on $MGMT_BRIDGE"
fi
if [ -n "$MGMT_CIDR" ]; then
sudo ip addr replace "$MGMT_CIDR" dev "$MGMT_BRIDGE"
echo " $MGMT_BRIDGE: address $MGMT_CIDR"
# Persist the IP across reboots via a systemd-networkd drop-in. The
# OVS bridge definition itself survives in ovsdb; only the L3 IP
# needs re-applying on boot.
MGMT_NETD_UNIT="/etc/systemd/network/10-spinifex-mgmt.network"
sudo tee "$MGMT_NETD_UNIT" >/dev/null <<NETD
[Match]
Name=$MGMT_BRIDGE
[Network]
Address=$MGMT_CIDR
ConfigureWithoutCarrier=yes
NETD
echo " wrote $MGMT_NETD_UNIT (IP persists on reboot via systemd-networkd)"
fi
fi
# --- Step 4: Configure OVN external_ids ---
echo ""
echo "Step 4: Setting OVS external_ids for OVN..."
@@ -669,6 +734,17 @@ if [ -n "$WAN_BRIDGE" ]; then
fi
fi
# Check mgmt bridge (only if enabled)
if [ "$MGMT_BRIDGE_ENABLED" = true ]; then
if sudo ovs-vsctl br-exists "$MGMT_BRIDGE"; then
MGMT_IP_ACTUAL=$(ip -4 -o addr show dev "$MGMT_BRIDGE" 2>/dev/null | awk '{print $4}' | head -1)
echo " $MGMT_BRIDGE:$(printf '%*s' $((15 - ${#MGMT_BRIDGE})) '') OK (${MGMT_IP_ACTUAL:-no IP})"
else
echo " $MGMT_BRIDGE:$(printf '%*s' $((15 - ${#MGMT_BRIDGE})) '') FAILED"
OK=false
fi
fi
# Check ovn-controller
if sudo ovs-appctl -t ovn-controller version >/dev/null 2>&1 || systemctl is-active --quiet ovn-controller 2>/dev/null; then
echo " ovn-controller: OK"
+10 -2
View File
@@ -48,6 +48,9 @@ type ConfigSettings struct {
Az string
Port string
BindIP string
// AdvertiseIP is the off-host dial target rendered into the local node's
// [nodes.X].advertise field. Empty → callers fall back to BindIP.
AdvertiseIP string
// Cluster settings
ClusterBindIP string
@@ -789,9 +792,14 @@ func GenerateSelfSignedCert(certPath, keyPath string) error {
}
// SetupAWSCredentials updates ~/.aws/credentials and ~/.aws/config.
// bindIP is the IP the AWS gateway listens on. If empty or "0.0.0.0", defaults to "localhost".
// bindIP is the IP the AWS gateway listens on. If empty or "0.0.0.0", the
// operator's local ~/.aws/config endpoint_url falls back to "localhost" (this
// runs on the same box as the gateway). wanIP is reserved for a future
// --operator-endpoint flag that will let remote operators point their CLI at
// the host's WAN IP; today it is accepted but unused.
// When running under sudo, writes to SUDO_USER's home instead of root's.
func SetupAWSCredentials(accessKey, secretKey, region, certPath, bindIP string) error {
func SetupAWSCredentials(accessKey, secretKey, region, certPath, bindIP, wanIP string) error {
_ = wanIP
homeDir, err := os.UserHomeDir()
if err != nil {
return err
+4 -4
View File
@@ -222,7 +222,7 @@ func TestSetupAWSCredentials_CreatesFiles(t *testing.T) {
dir := t.TempDir()
t.Setenv("HOME", dir)
err := SetupAWSCredentials("AKIATEST123", "secret123", "us-east-1", "/path/to/ca.pem", "")
err := SetupAWSCredentials("AKIATEST123", "secret123", "us-east-1", "/path/to/ca.pem", "", "")
require.NoError(t, err)
credData, _ := os.ReadFile(filepath.Join(dir, ".aws", "credentials"))
@@ -245,7 +245,7 @@ func TestSetupAWSCredentials_PreservesExistingProfiles(t *testing.T) {
"aws_access_key_id": "EXISTING_KEY",
}))
err := SetupAWSCredentials("NEWAKEY", "NEWSECRET", "us-west-2", "/ca.pem", "")
err := SetupAWSCredentials("NEWAKEY", "NEWSECRET", "us-west-2", "/ca.pem", "", "")
require.NoError(t, err)
data, _ := os.ReadFile(filepath.Join(awsDir, "credentials"))
@@ -258,7 +258,7 @@ func TestSetupAWSCredentials_UsesBindIP(t *testing.T) {
dir := t.TempDir()
t.Setenv("HOME", dir)
err := SetupAWSCredentials("AKIATEST123", "secret123", "us-east-1", "/ca.pem", "10.11.12.1")
err := SetupAWSCredentials("AKIATEST123", "secret123", "us-east-1", "/ca.pem", "10.11.12.1", "")
require.NoError(t, err)
configData, _ := os.ReadFile(filepath.Join(dir, ".aws", "config"))
@@ -270,7 +270,7 @@ func TestSetupAWSCredentials_FallsBackToLocalhostForWildcard(t *testing.T) {
dir := t.TempDir()
t.Setenv("HOME", dir)
err := SetupAWSCredentials("AKIATEST123", "secret123", "us-east-1", "/ca.pem", "0.0.0.0")
err := SetupAWSCredentials("AKIATEST123", "secret123", "us-east-1", "/ca.pem", "0.0.0.0", "")
require.NoError(t, err)
configData, _ := os.ReadFile(filepath.Join(dir, ".aws", "config"))
+42
View File
@@ -0,0 +1,42 @@
package admin
import "net"
// DialTarget returns a host:port suitable for an in-process client to dial,
// given a configured listen address. Inputs of the form "0.0.0.0:N" or "[::]:N"
// are rewritten to "127.0.0.1:N" or "[::1]:N" — a service listening on the
// wildcard is reachable on loopback, and loopback is the TLS SAN that the
// daemon's own cert always covers. Loopback, specific IPs, and bare hostnames
// pass through unchanged. Malformed input (no host:port) passes through so
// callers still see the original value in error messages.
func DialTarget(listenAddr string) string {
host, port, err := net.SplitHostPort(listenAddr)
if err != nil {
return listenAddr
}
switch host {
case "0.0.0.0", "":
return net.JoinHostPort("127.0.0.1", port)
case "::":
return net.JoinHostPort("::1", port)
}
return listenAddr
}
// AdvertiseHost picks the host that off-host clients should dial for a service
// listening on listenAddr. If the listen host is a wildcard (0.0.0.0, ::, or
// empty), the caller-supplied advertiseIP is returned; otherwise the listen
// host itself is returned. Port is intentionally dropped — callers append the
// port appropriate for the protocol (some services listen and advertise on
// different ports).
func AdvertiseHost(listenAddr, advertiseIP string) string {
host, _, err := net.SplitHostPort(listenAddr)
if err != nil {
host = listenAddr
}
switch host {
case "0.0.0.0", "::", "":
return advertiseIP
}
return host
}
+59
View File
@@ -0,0 +1,59 @@
package admin
import "testing"
func TestDialTarget(t *testing.T) {
t.Parallel()
cases := []struct {
name string
in string
want string
}{
{"ipv4 wildcard", "0.0.0.0:4222", "127.0.0.1:4222"},
{"ipv6 wildcard", "[::]:4222", "[::1]:4222"},
{"ipv4 loopback passthrough", "127.0.0.1:4222", "127.0.0.1:4222"},
{"ipv6 loopback passthrough", "[::1]:4222", "[::1]:4222"},
{"specific ipv4 passthrough", "192.168.1.5:4222", "192.168.1.5:4222"},
{"hostname passthrough", "nats.cluster.local:4222", "nats.cluster.local:4222"},
{"empty host rewrites to loopback", ":4222", "127.0.0.1:4222"},
{"malformed passthrough", "malformed", "malformed"},
{"ipv4 wildcard tls port", "0.0.0.0:8443", "127.0.0.1:8443"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := DialTarget(tc.in)
if got != tc.want {
t.Fatalf("DialTarget(%q) = %q, want %q", tc.in, got, tc.want)
}
})
}
}
func TestAdvertiseHost(t *testing.T) {
t.Parallel()
cases := []struct {
name string
listen string
advertiseIP string
want string
}{
{"ipv4 wildcard resolves", "0.0.0.0:9999", "192.168.1.5", "192.168.1.5"},
{"ipv6 wildcard resolves", "[::]:9999", "192.168.1.5", "192.168.1.5"},
{"specific ip passthrough", "10.11.12.1:9999", "192.168.1.5", "10.11.12.1"},
{"loopback passthrough", "127.0.0.1:9999", "192.168.1.5", "127.0.0.1"},
{"hostname passthrough", "nats.cluster.local:9999", "192.168.1.5", "nats.cluster.local"},
{"no-port input treated as host", "0.0.0.0", "192.168.1.5", "192.168.1.5"},
{"empty listen falls back to advertise", "", "192.168.1.5", "192.168.1.5"},
{"empty advertise with wildcard returns empty", "0.0.0.0:9999", "", ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := AdvertiseHost(tc.listen, tc.advertiseIP)
if got != tc.want {
t.Fatalf("AdvertiseHost(%q, %q) = %q, want %q", tc.listen, tc.advertiseIP, got, tc.want)
}
})
}
}
+9 -6
View File
@@ -55,12 +55,15 @@ type BootstrapConfig struct {
// Config holds all configuration for the application
type Config struct {
// Node config
Node string `json:"Node" mapstructure:"node"`
Host string `json:"Host" mapstructure:"host"` // Unique hostname or IP of this node
Region string `json:"Region" mapstructure:"region"`
AZ string `json:"AZ" mapstructure:"az"`
DataDir string `json:"DataDir" mapstructure:"data_dir"`
Services []string `json:"Services" mapstructure:"services"` // Which services this node runs locally
Node string `json:"Node" mapstructure:"node"`
Host string `json:"Host" mapstructure:"host"` // Unique hostname or IP of this node
// AdvertiseIP is the off-host dial target for this node. Empty → callers
// fall back to Host for backward compat with pre-siv-8 cluster configs.
AdvertiseIP string `json:"AdvertiseIP" mapstructure:"advertise"`
Region string `json:"Region" mapstructure:"region"`
AZ string `json:"AZ" mapstructure:"az"`
DataDir string `json:"DataDir" mapstructure:"data_dir"`
Services []string `json:"Services" mapstructure:"services"` // Which services this node runs locally
Daemon DaemonConfig `json:"Daemon" mapstructure:"daemon"`
NATS NATSConfig `json:"NATS" mapstructure:"nats"`
+32 -22
View File
@@ -569,15 +569,15 @@ func (d *Daemon) Start() error {
if err := d.jsManager.WriteServiceManifest(
d.node,
d.config.GetServices(),
d.config.NATS.Host,
d.config.Predastore.Host,
admin.DialTarget(d.config.NATS.Host),
admin.DialTarget(d.config.Predastore.Host),
); err != nil {
slog.Warn("Failed to write service manifest", "error", err)
}
}
// Create services before loading/launching instances, since LaunchInstance depends on them
store := objectstore.NewS3ObjectStoreFromConfig(d.config.Predastore.Host, d.config.Predastore.Region, d.config.Predastore.AccessKey, d.config.Predastore.SecretKey)
store := objectstore.NewS3ObjectStoreFromConfig(admin.DialTarget(d.config.Predastore.Host), d.config.Predastore.Region, d.config.Predastore.AccessKey, d.config.Predastore.SecretKey)
d.instanceService = handlers_ec2_instance.NewInstanceServiceImpl(d.config, d.resourceMgr.instanceTypes, d.natsConn, &d.Instances, store)
d.keyService = handlers_ec2_key.NewKeyServiceImpl(d.config)
d.imageService = handlers_ec2_image.NewImageServiceImpl(d.config, d.natsConn)
@@ -844,7 +844,7 @@ func (d *Daemon) Start() error {
// be ready immediately after daemon start (e.g. if start-dev.sh is still
// launching services). This retries for up to 5 minutes before giving up.
func (d *Daemon) connectNATS() error {
nc, err := utils.ConnectNATSWithRetry(d.config.NATS.Host, d.config.NATS.ACL.Token, d.config.NATS.CACert, d.natsRetryOpts...)
nc, err := utils.ConnectNATSWithRetry(admin.DialTarget(d.config.NATS.Host), d.config.NATS.ACL.Token, d.config.NATS.CACert, d.natsRetryOpts...)
if err != nil {
return err
}
@@ -993,7 +993,7 @@ func (d *Daemon) checkViperblockReady() bool {
// checkPredastoreReady checks if predastore is reachable via TCP.
func (d *Daemon) checkPredastoreReady() bool {
host := d.config.Predastore.Host
host := admin.DialTarget(d.config.Predastore.Host)
if host == "" {
return true // no predastore configured, skip check
}
@@ -2782,9 +2782,15 @@ func (d *Daemon) wireLBAgentConfig() {
}
// Resolve gateway URL — the address LB VMs use to reach the AWS gateway.
// Internet-facing LB VMs reach it via VPC networking; internal LB VMs
// reach it via the management NIC (br-mgmt) with a host route added
// to their cloud-init when AWSGW binds to a specific WAN IP.
// Precedence:
// 1. AWSGW listens on a specific WAN IP + br-mgmt present → use WAN IP
// and add host route via br-mgmt for internal LBs.
// 2. br-mgmt present + AWSGW on 0.0.0.0 → br-mgmt IP (both LB flavours
// reach the daemon via mgmt).
// 3. This node's AdvertiseIP (single-node default install) → AdvertiseIP.
// 4. DevNetworking shim → 10.0.2.2.
// 5. AWSGW bound to specific IP (no br-mgmt, no advertise) → that IP.
// 6. Else: error and skip assignment — no silent empty URL.
var gatewayHost string
awsgwBindIP := ""
if d.config.AWSGW.Host != "" {
@@ -2793,20 +2799,23 @@ func (d *Daemon) wireLBAgentConfig() {
}
}
if d.mgmtBridgeIP != "" {
if awsgwBindIP != "" && awsgwBindIP != "0.0.0.0" && !net.ParseIP(awsgwBindIP).IsLoopback() {
// Multi-node: AWSGW listens on a specific WAN IP. Use that IP
// as the gateway URL. Internal LBs will get a host route via
// br-mgmt to reach it (see LaunchSystemInstance).
gatewayHost = awsgwBindIP
d.mgmtRouteVia = awsgwBindIP
} else {
// Single-node: AWSGW listens on all interfaces — br-mgmt IP works.
gatewayHost = d.mgmtBridgeIP
}
} else if d.config.Daemon.DevNetworking {
advertiseIP := d.config.AdvertiseIP
switch {
case d.mgmtBridgeIP != "" && awsgwBindIP != "" && awsgwBindIP != "0.0.0.0" && !net.ParseIP(awsgwBindIP).IsLoopback():
// Multi-node: AWSGW listens on a specific WAN IP. Use that IP as the
// gateway URL; internal LBs get a host route via br-mgmt.
gatewayHost = awsgwBindIP
d.mgmtRouteVia = awsgwBindIP
case d.mgmtBridgeIP != "":
// Multi-node with AWSGW on 0.0.0.0 — br-mgmt reaches all LBs.
gatewayHost = d.mgmtBridgeIP
case advertiseIP != "" && advertiseIP != "0.0.0.0":
// Single-node default install — off-host clients dial the advertised IP.
gatewayHost = advertiseIP
case d.config.Daemon.DevNetworking:
gatewayHost = "10.0.2.2"
} else if awsgwBindIP != "" && awsgwBindIP != "0.0.0.0" {
case awsgwBindIP != "" && awsgwBindIP != "0.0.0.0":
gatewayHost = awsgwBindIP
}
@@ -2823,7 +2832,8 @@ func (d *Daemon) wireLBAgentConfig() {
d.elbv2Service.GatewayURL = gatewayURL
slog.Info("LB agent gateway URL configured", "url", gatewayURL)
} else {
slog.Error("LB agent gateway URL not configured: no reachable host found — all CreateLoadBalancer calls will fail")
slog.Error("LB agent gateway URL not configured: no reachable host found — CreateLoadBalancer will fail until --advertise or br-mgmt is configured",
"awsgwBindIP", awsgwBindIP, "mgmtBridgeIP", d.mgmtBridgeIP, "advertiseIP", advertiseIP)
}
// Pass mgmt route info so lbVMUserData can add a bootcmd route for
+30
View File
@@ -172,3 +172,33 @@ func TestWireLBAgentConfig_NoMgmtRoute(t *testing.T) {
assert.Empty(t, d.mgmtRouteVia)
}
// AdvertiseIP covers the single-node default install (no br-mgmt, AWSGW on
// wildcard): the gateway URL falls back to the node's advertised off-host IP.
// Replaces the siv-6 "empty gateway URL" silent failure.
func TestWireLBAgentConfig_GatewayURL_AdvertiseFallback(t *testing.T) {
cfg := &config.Config{
AdvertiseIP: "192.168.1.21",
AWSGW: config.AWSGWConfig{Host: "0.0.0.0:9999"},
}
d := newWireLBTestDaemon(t, cfg)
d.wireLBAgentConfig()
assert.Equal(t, "https://192.168.1.21:9999", d.elbv2Service.GatewayURL)
assert.Empty(t, d.mgmtRouteVia)
}
// When there's no br-mgmt, no AdvertiseIP, no DevNetworking, and AWSGW is on
// the wildcard, the gateway URL must stay empty and the daemon must log an
// error (rather than silently assigning a broken URL like pre-siv-8).
func TestWireLBAgentConfig_GatewayURL_NoAdvertiseNoBridge(t *testing.T) {
cfg := &config.Config{
AWSGW: config.AWSGWConfig{Host: "0.0.0.0:9999"},
}
d := newWireLBTestDaemon(t, cfg)
d.wireLBAgentConfig()
assert.Empty(t, d.elbv2Service.GatewayURL)
}
+11 -8
View File
@@ -203,15 +203,17 @@ func GetBridgeIPv4(bridgeName string) (string, error) {
return "", fmt.Errorf("no IPv4 address on %s", bridgeName)
}
// SetupMgmtTapDevice creates a TAP device and adds it to the management Linux bridge.
// Unlike VPC TAPs (OVS/OVN), this is a plain Linux bridge TAP.
// SetupMgmtTapDevice creates a TAP device and attaches it to the management
// OVS bridge (provisioned by scripts/setup-ovn.sh). br-mgmt uses
// fail-mode=standalone and is excluded from ovn-bridge-mappings, so it
// behaves as a plain L2 switch outside the OVN overlay.
func SetupMgmtTapDevice(instanceID, mac, bridge string) (string, error) {
tapName := MgmtTapName(instanceID)
// Clean up stale TAP if present
if _, err := os.Stat("/sys/class/net/" + tapName); err == nil {
slog.Warn("Stale mgmt tap found, cleaning up", "tap", tapName)
if err := sudoCommand("ip", "link", "set", tapName, "nomaster").Run(); err != nil {
if err := sudoCommand("ovs-vsctl", "--if-exists", "del-port", bridge, tapName).Run(); err != nil {
slog.Warn("Failed to remove stale mgmt tap from bridge", "tap", tapName, "err", err)
}
if err := sudoCommand("ip", "tuntap", "del", "dev", tapName, "mode", "tap").Run(); err != nil {
@@ -230,8 +232,8 @@ func SetupMgmtTapDevice(instanceID, mac, bridge string) (string, error) {
return "", fmt.Errorf("bring up mgmt tap %s: %s: %w", tapName, strings.TrimSpace(string(out)), err)
}
// Add to Linux bridge
if out, err := sudoCommand("ip", "link", "set", tapName, "master", bridge).CombinedOutput(); err != nil {
// Attach to OVS mgmt bridge
if out, err := sudoCommand("ovs-vsctl", "--may-exist", "add-port", bridge, tapName).CombinedOutput(); err != nil {
_ = sudoCommand("ip", "tuntap", "del", "dev", tapName, "mode", "tap").Run()
return "", fmt.Errorf("add mgmt tap to %s: %s: %w", bridge, strings.TrimSpace(string(out)), err)
}
@@ -240,10 +242,11 @@ func SetupMgmtTapDevice(instanceID, mac, bridge string) (string, error) {
return tapName, nil
}
// CleanupMgmtTapDevice removes a management TAP device from its bridge and deletes it.
// CleanupMgmtTapDevice removes a management TAP device from the mgmt OVS
// bridge and deletes it.
func CleanupMgmtTapDevice(tapName string) error {
// Remove from bridge
if out, err := sudoCommand("ip", "link", "set", tapName, "nomaster").CombinedOutput(); err != nil {
// Detach from whichever OVS bridge holds this tap (usually br-mgmt)
if out, err := sudoCommand("ovs-vsctl", "--if-exists", "del-port", tapName).CombinedOutput(); err != nil {
slog.Warn("Failed to remove mgmt tap from bridge", "tap", tapName, "err", err, "out", strings.TrimSpace(string(out)))
}
+11 -7
View File
@@ -17,13 +17,17 @@ import (
// NodeInfo describes a node participating in cluster formation.
type NodeInfo struct {
Name string `json:"name"`
BindIP string `json:"bind_ip"`
ClusterIP string `json:"cluster_ip"`
Region string `json:"region"`
AZ string `json:"az"`
Port int `json:"port"`
Services []string `json:"services,omitempty"`
Name string `json:"name"`
// BindIP is the listen address on the joining node.
BindIP string `json:"bind_ip"`
// AdvertiseIP is the off-host dial target for this node. Empty →
// peers fall back to BindIP for backward compat with older joiners.
AdvertiseIP string `json:"advertise_ip,omitempty"`
ClusterIP string `json:"cluster_ip"`
Region string `json:"region"`
AZ string `json:"az"`
Port int `json:"port"`
Services []string `json:"services,omitempty"`
}
// JoinRequest is the payload POSTed by joining nodes.
@@ -9,8 +9,6 @@ import (
// DescribeAccountAttributes returns static account attributes for the Spinifex platform
func DescribeAccountAttributes(input *ec2.DescribeAccountAttributesInput) (*ec2.DescribeAccountAttributesOutput, error) {
slog.Debug("DescribeAccountAttributes called")
// Build set of requested attribute names for filtering
requestedAttrs := make(map[string]bool)
for _, name := range input.AttributeNames {
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateAssociateAddressInput validates the input parameters
func ValidateAssociateAddressInput(input *ec2.AssociateAddressInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.AllocationId == nil || *input.AllocationId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// AssociateAddress handles the EC2 AssociateAddress API call.
func AssociateAddress(input *ec2.AssociateAddressInput, natsConn *nats.Conn, accountID string) (ec2.AssociateAddressOutput, error) {
var output ec2.AssociateAddressOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.AllocationId == nil || *input.AllocationId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateAssociateAddressInput(input); err != nil {
return output, err
}
svc := handlers_ec2_eip.NewNATSEIPService(natsConn)
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDisassociateAddressInput validates the input parameters
func ValidateDisassociateAddressInput(input *ec2.DisassociateAddressInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.AssociationId == nil || *input.AssociationId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DisassociateAddress handles the EC2 DisassociateAddress API call.
func DisassociateAddress(input *ec2.DisassociateAddressInput, natsConn *nats.Conn, accountID string) (ec2.DisassociateAddressOutput, error) {
var output ec2.DisassociateAddressOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.AssociationId == nil || *input.AssociationId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDisassociateAddressInput(input); err != nil {
return output, err
}
svc := handlers_ec2_eip.NewNATSEIPService(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateReleaseAddressInput validates the input parameters
func ValidateReleaseAddressInput(input *ec2.ReleaseAddressInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.AllocationId == nil || *input.AllocationId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// ReleaseAddress handles the EC2 ReleaseAddress API call.
func ReleaseAddress(input *ec2.ReleaseAddressInput, natsConn *nats.Conn, accountID string) (ec2.ReleaseAddressOutput, error) {
var output ec2.ReleaseAddressOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.AllocationId == nil || *input.AllocationId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateReleaseAddressInput(input); err != nil {
return output, err
}
svc := handlers_ec2_eip.NewNATSEIPService(natsConn)
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateAttachInternetGatewayInput validates the input parameters
func ValidateAttachInternetGatewayInput(input *ec2.AttachInternetGatewayInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.InternetGatewayId == nil || *input.InternetGatewayId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if input.VpcId == nil || *input.VpcId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// AttachInternetGateway handles the EC2 AttachInternetGateway API call
func AttachInternetGateway(input *ec2.AttachInternetGatewayInput, natsConn *nats.Conn, accountID string) (ec2.AttachInternetGatewayOutput, error) {
var output ec2.AttachInternetGatewayOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.InternetGatewayId == nil || *input.InternetGatewayId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if input.VpcId == nil || *input.VpcId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateAttachInternetGatewayInput(input); err != nil {
return output, err
}
svc := handlers_ec2_igw.NewNATSIGWService(natsConn)
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDeleteInternetGatewayInput validates the input parameters
func ValidateDeleteInternetGatewayInput(input *ec2.DeleteInternetGatewayInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.InternetGatewayId == nil || *input.InternetGatewayId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DeleteInternetGateway handles the EC2 DeleteInternetGateway API call
func DeleteInternetGateway(input *ec2.DeleteInternetGatewayInput, natsConn *nats.Conn, accountID string) (ec2.DeleteInternetGatewayOutput, error) {
var output ec2.DeleteInternetGatewayOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.InternetGatewayId == nil || *input.InternetGatewayId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDeleteInternetGatewayInput(input); err != nil {
return output, err
}
svc := handlers_ec2_igw.NewNATSIGWService(natsConn)
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDetachInternetGatewayInput validates the input parameters
func ValidateDetachInternetGatewayInput(input *ec2.DetachInternetGatewayInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.InternetGatewayId == nil || *input.InternetGatewayId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if input.VpcId == nil || *input.VpcId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DetachInternetGateway handles the EC2 DetachInternetGateway API call
func DetachInternetGateway(input *ec2.DetachInternetGatewayInput, natsConn *nats.Conn, accountID string) (ec2.DetachInternetGatewayOutput, error) {
var output ec2.DetachInternetGatewayOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.InternetGatewayId == nil || *input.InternetGatewayId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if input.VpcId == nil || *input.VpcId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDetachInternetGatewayInput(input); err != nil {
return output, err
}
svc := handlers_ec2_igw.NewNATSIGWService(natsConn)
@@ -8,15 +8,27 @@ import (
"time"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/nats-io/nats.go"
)
// ValidateGetConsoleOutputInput validates the input parameters
func ValidateGetConsoleOutputInput(input *ec2.GetConsoleOutputInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.InstanceId == nil || *input.InstanceId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// GetConsoleOutput retrieves console output for a specific instance via NATS.
// Routes directly to the node running the instance via ec2.{instanceID}.GetConsoleOutput.
func GetConsoleOutput(input *ec2.GetConsoleOutputInput, natsConn *nats.Conn, accountID string) (*ec2.GetConsoleOutputOutput, error) {
if input.InstanceId == nil || *input.InstanceId == "" {
return nil, fmt.Errorf("InstanceId is required")
if err := ValidateGetConsoleOutputInput(input); err != nil {
return nil, err
}
jsonData, err := json.Marshal(input)
@@ -14,12 +14,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateRebootInstancesInput validates the input parameters
func ValidateRebootInstancesInput(input *ec2.RebootInstancesInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if len(input.InstanceIds) == 0 {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// RebootInstances sends reboot commands to specified instances via NATS.
// Unlike stop+start, reboot keeps the instance running and sends a QMP system_reset.
// Returns an empty response on success (AWS returns no state-change data).
func RebootInstances(input *ec2.RebootInstancesInput, natsConn *nats.Conn, accountID string) (*ec2.RebootInstancesOutput, error) {
if len(input.InstanceIds) == 0 {
return nil, fmt.Errorf("no instance IDs provided")
if err := ValidateRebootInstancesInput(input); err != nil {
return nil, err
}
slog.Info("RebootInstances: Processing request", "instance_count", len(input.InstanceIds))
@@ -70,7 +70,7 @@ func TestRebootInstances_EmptyInstanceIds(t *testing.T) {
_, err := RebootInstances(input, nc, "123456789012")
require.Error(t, err)
assert.Contains(t, err.Error(), "no instance IDs provided")
assert.Equal(t, awserrors.ErrorMissingParameter, err.Error())
}
func TestRebootInstances_NATSRequestFails(t *testing.T) {
@@ -3,11 +3,11 @@ package gateway_ec2_instance
import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"time"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/nats-io/nats.go"
)
@@ -17,11 +17,22 @@ type startStoppedInstanceRequest struct {
InstanceID string `json:"instance_id"`
}
// ValidateStartInstancesInput validates the input parameters
func ValidateStartInstancesInput(input *ec2.StartInstancesInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if len(input.InstanceIds) == 0 {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// StartInstances sends start requests to the ec2.start queue group topic.
// Any available daemon can pick up the request and launch the stopped instance.
func StartInstances(input *ec2.StartInstancesInput, natsConn *nats.Conn, accountID string) (*ec2.StartInstancesOutput, error) {
if len(input.InstanceIds) == 0 {
return nil, fmt.Errorf("no instance IDs provided")
if err := ValidateStartInstancesInput(input); err != nil {
return nil, err
}
slog.Info("StartInstances: Processing request", "instance_count", len(input.InstanceIds))
@@ -6,6 +6,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -82,7 +83,7 @@ func TestStartInstances_EmptyInstanceIds(t *testing.T) {
_, err := StartInstances(input, nc, "123456789012")
require.Error(t, err)
assert.Contains(t, err.Error(), "no instance IDs provided")
assert.Equal(t, awserrors.ErrorMissingParameter, err.Error())
}
func TestStartInstances_NilInstanceIdSkipped(t *testing.T) {
+14 -2
View File
@@ -8,16 +8,28 @@ import (
"time"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/types"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/nats-io/nats.go"
)
// ValidateStopInstancesInput validates the input parameters
func ValidateStopInstancesInput(input *ec2.StopInstancesInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if len(input.InstanceIds) == 0 {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// StopInstances sends stop commands to specified instances via NATS
// Uses system_powerdown with stop_instance attribute to prevent auto-restart on daemon boot
func StopInstances(input *ec2.StopInstancesInput, natsConn *nats.Conn, accountID string) (*ec2.StopInstancesOutput, error) {
if len(input.InstanceIds) == 0 {
return nil, fmt.Errorf("no instance IDs provided")
if err := ValidateStopInstancesInput(input); err != nil {
return nil, err
}
slog.Info("StopInstances: Processing request", "instance_count", len(input.InstanceIds))
@@ -6,6 +6,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/types"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
@@ -83,7 +84,7 @@ func TestStopInstances_EmptyInstanceIds(t *testing.T) {
_, err := StopInstances(input, nc, "123456789012")
require.Error(t, err)
assert.Contains(t, err.Error(), "no instance IDs provided")
assert.Equal(t, awserrors.ErrorMissingParameter, err.Error())
}
func TestStopInstances_NilInstanceIdSkipped(t *testing.T) {
@@ -8,6 +8,7 @@ import (
"time"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/types"
"github.com/mulgadc/spinifex/spinifex/utils"
"github.com/nats-io/nats.go"
@@ -18,11 +19,22 @@ type terminateStoppedInstanceRequest struct {
InstanceID string `json:"instance_id"`
}
// ValidateTerminateInstancesInput validates the input parameters
func ValidateTerminateInstancesInput(input *ec2.TerminateInstancesInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if len(input.InstanceIds) == 0 {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// TerminateInstances sends terminate commands to specified instances via NATS
// Uses system_powerdown with stop_instance attribute to prevent restart
func TerminateInstances(input *ec2.TerminateInstancesInput, natsConn *nats.Conn, accountID string) (*ec2.TerminateInstancesOutput, error) {
if len(input.InstanceIds) == 0 {
return nil, fmt.Errorf("no instance IDs provided")
if err := ValidateTerminateInstancesInput(input); err != nil {
return nil, err
}
slog.Info("TerminateInstances: Processing request", "instance_count", len(input.InstanceIds))
@@ -6,6 +6,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/awserrors"
"github.com/mulgadc/spinifex/spinifex/types"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
@@ -83,7 +84,7 @@ func TestTerminateInstances_EmptyInstanceIds(t *testing.T) {
_, err := TerminateInstances(input, nc, "123456789012")
require.Error(t, err)
assert.Contains(t, err.Error(), "no instance IDs provided")
assert.Equal(t, awserrors.ErrorMissingParameter, err.Error())
}
func TestTerminateInstances_NilInstanceIdSkipped(t *testing.T) {
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateCreatePlacementGroupInput validates the input parameters
func ValidateCreatePlacementGroupInput(input *ec2.CreatePlacementGroupInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.GroupName == nil || *input.GroupName == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if input.Strategy == nil || *input.Strategy == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// CreatePlacementGroup handles the EC2 CreatePlacementGroup API call.
func CreatePlacementGroup(input *ec2.CreatePlacementGroupInput, natsConn *nats.Conn, accountID string) (ec2.CreatePlacementGroupOutput, error) {
var output ec2.CreatePlacementGroupOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.GroupName == nil || *input.GroupName == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if input.Strategy == nil || *input.Strategy == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateCreatePlacementGroupInput(input); err != nil {
return output, err
}
svc := handlers_ec2_placementgroup.NewNATSPlacementGroupService(natsConn)
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDeletePlacementGroupInput validates the input parameters
func ValidateDeletePlacementGroupInput(input *ec2.DeletePlacementGroupInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.GroupName == nil || *input.GroupName == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DeletePlacementGroup handles the EC2 DeletePlacementGroup API call.
func DeletePlacementGroup(input *ec2.DeletePlacementGroupInput, natsConn *nats.Conn, accountID string) (ec2.DeletePlacementGroupOutput, error) {
var output ec2.DeletePlacementGroupOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.GroupName == nil || *input.GroupName == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDeletePlacementGroupInput(input); err != nil {
return output, err
}
svc := handlers_ec2_placementgroup.NewNATSPlacementGroupService(natsConn)
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateCreateNetworkInterfaceInput validates the input parameters
func ValidateCreateNetworkInterfaceInput(input *ec2.CreateNetworkInterfaceInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.SubnetId == nil || *input.SubnetId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// CreateNetworkInterface handles the EC2 CreateNetworkInterface API call
func CreateNetworkInterface(input *ec2.CreateNetworkInterfaceInput, natsConn *nats.Conn, accountID string) (ec2.CreateNetworkInterfaceOutput, error) {
var output ec2.CreateNetworkInterfaceOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.SubnetId == nil || *input.SubnetId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateCreateNetworkInterfaceInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
+16 -8
View File
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateCreateSubnetInput validates the input parameters
func ValidateCreateSubnetInput(input *ec2.CreateSubnetInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.VpcId == nil || *input.VpcId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if input.CidrBlock == nil || *input.CidrBlock == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// CreateSubnet handles the EC2 CreateSubnet API call
func CreateSubnet(input *ec2.CreateSubnetInput, natsConn *nats.Conn, accountID string) (ec2.CreateSubnetOutput, error) {
var output ec2.CreateSubnetOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.VpcId == nil || *input.VpcId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if input.CidrBlock == nil || *input.CidrBlock == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateCreateSubnetInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateCreateVpcInput validates the input parameters
func ValidateCreateVpcInput(input *ec2.CreateVpcInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.CidrBlock == nil || *input.CidrBlock == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// CreateVpc handles the EC2 CreateVpc API call
func CreateVpc(input *ec2.CreateVpcInput, natsConn *nats.Conn, accountID string) (ec2.CreateVpcOutput, error) {
var output ec2.CreateVpcOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.CidrBlock == nil || *input.CidrBlock == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateCreateVpcInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDeleteNetworkInterfaceInput validates the input parameters
func ValidateDeleteNetworkInterfaceInput(input *ec2.DeleteNetworkInterfaceInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.NetworkInterfaceId == nil || *input.NetworkInterfaceId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DeleteNetworkInterface handles the EC2 DeleteNetworkInterface API call
func DeleteNetworkInterface(input *ec2.DeleteNetworkInterfaceInput, natsConn *nats.Conn, accountID string) (ec2.DeleteNetworkInterfaceOutput, error) {
var output ec2.DeleteNetworkInterfaceOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.NetworkInterfaceId == nil || *input.NetworkInterfaceId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDeleteNetworkInterfaceInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDeleteSubnetInput validates the input parameters
func ValidateDeleteSubnetInput(input *ec2.DeleteSubnetInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.SubnetId == nil || *input.SubnetId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DeleteSubnet handles the EC2 DeleteSubnet API call
func DeleteSubnet(input *ec2.DeleteSubnetInput, natsConn *nats.Conn, accountID string) (ec2.DeleteSubnetOutput, error) {
var output ec2.DeleteSubnetOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.SubnetId == nil || *input.SubnetId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDeleteSubnetInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDeleteVpcInput validates the input parameters
func ValidateDeleteVpcInput(input *ec2.DeleteVpcInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.VpcId == nil || *input.VpcId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DeleteVpc handles the EC2 DeleteVpc API call
func DeleteVpc(input *ec2.DeleteVpcInput, natsConn *nats.Conn, accountID string) (ec2.DeleteVpcOutput, error) {
var output ec2.DeleteVpcOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.VpcId == nil || *input.VpcId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDeleteVpcInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDescribeVpcAttributeInput validates the input parameters
func ValidateDescribeVpcAttributeInput(input *ec2.DescribeVpcAttributeInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.VpcId == nil || *input.VpcId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if input.Attribute == nil || *input.Attribute == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DescribeVpcAttribute handles the EC2 DescribeVpcAttribute API call
func DescribeVpcAttribute(input *ec2.DescribeVpcAttributeInput, natsConn *nats.Conn, accountID string) (ec2.DescribeVpcAttributeOutput, error) {
var output ec2.DescribeVpcAttributeOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.VpcId == nil || *input.VpcId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if input.Attribute == nil || *input.Attribute == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDescribeVpcAttributeInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateModifyNetworkInterfaceAttributeInput validates the input parameters
func ValidateModifyNetworkInterfaceAttributeInput(input *ec2.ModifyNetworkInterfaceAttributeInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.NetworkInterfaceId == nil || *input.NetworkInterfaceId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// ModifyNetworkInterfaceAttribute handles the EC2 ModifyNetworkInterfaceAttribute API call
func ModifyNetworkInterfaceAttribute(input *ec2.ModifyNetworkInterfaceAttributeInput, natsConn *nats.Conn, accountID string) (ec2.ModifyNetworkInterfaceAttributeOutput, error) {
var output ec2.ModifyNetworkInterfaceAttributeOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.NetworkInterfaceId == nil || *input.NetworkInterfaceId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateModifyNetworkInterfaceAttributeInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateModifySubnetAttributeInput validates the input parameters
func ValidateModifySubnetAttributeInput(input *ec2.ModifySubnetAttributeInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.SubnetId == nil || *input.SubnetId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// ModifySubnetAttribute handles the EC2 ModifySubnetAttribute API call
func ModifySubnetAttribute(input *ec2.ModifySubnetAttributeInput, natsConn *nats.Conn, accountID string) (ec2.ModifySubnetAttributeOutput, error) {
var output ec2.ModifySubnetAttributeOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.SubnetId == nil || *input.SubnetId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateModifySubnetAttributeInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateModifyVpcAttributeInput validates the input parameters
func ValidateModifyVpcAttributeInput(input *ec2.ModifyVpcAttributeInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.VpcId == nil || *input.VpcId == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// ModifyVpcAttribute handles the EC2 ModifyVpcAttribute API call
func ModifyVpcAttribute(input *ec2.ModifyVpcAttributeInput, natsConn *nats.Conn, accountID string) (ec2.ModifyVpcAttributeOutput, error) {
var output ec2.ModifyVpcAttributeOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.VpcId == nil || *input.VpcId == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateModifyVpcAttributeInput(input); err != nil {
return output, err
}
svc := handlers_ec2_vpc.NewNATSVPCService(natsConn)
+16 -8
View File
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateCreateListenerInput validates the input parameters
func ValidateCreateListenerInput(input *elbv2.CreateListenerInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LoadBalancerArn == nil || *input.LoadBalancerArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if len(input.DefaultActions) == 0 {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// CreateListener handles the ELBv2 CreateListener API call.
func CreateListener(input *elbv2.CreateListenerInput, natsConn *nats.Conn, accountID string) (elbv2.CreateListenerOutput, error) {
var output elbv2.CreateListenerOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LoadBalancerArn == nil || *input.LoadBalancerArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if len(input.DefaultActions) == 0 {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateCreateListenerInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateCreateLoadBalancerInput validates the input parameters
func ValidateCreateLoadBalancerInput(input *elbv2.CreateLoadBalancerInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.Name == nil || *input.Name == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// CreateLoadBalancer handles the ELBv2 CreateLoadBalancer API call.
func CreateLoadBalancer(input *elbv2.CreateLoadBalancerInput, natsConn *nats.Conn, accountID string) (elbv2.CreateLoadBalancerOutput, error) {
var output elbv2.CreateLoadBalancerOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.Name == nil || *input.Name == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateCreateLoadBalancerInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateCreateTargetGroupInput validates the input parameters
func ValidateCreateTargetGroupInput(input *elbv2.CreateTargetGroupInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.Name == nil || *input.Name == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// CreateTargetGroup handles the ELBv2 CreateTargetGroup API call.
func CreateTargetGroup(input *elbv2.CreateTargetGroupInput, natsConn *nats.Conn, accountID string) (elbv2.CreateTargetGroupOutput, error) {
var output elbv2.CreateTargetGroupOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.Name == nil || *input.Name == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateCreateTargetGroupInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDeleteListenerInput validates the input parameters
func ValidateDeleteListenerInput(input *elbv2.DeleteListenerInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.ListenerArn == nil || *input.ListenerArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DeleteListener handles the ELBv2 DeleteListener API call.
func DeleteListener(input *elbv2.DeleteListenerInput, natsConn *nats.Conn, accountID string) (elbv2.DeleteListenerOutput, error) {
var output elbv2.DeleteListenerOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.ListenerArn == nil || *input.ListenerArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDeleteListenerInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDeleteLoadBalancerInput validates the input parameters
func ValidateDeleteLoadBalancerInput(input *elbv2.DeleteLoadBalancerInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LoadBalancerArn == nil || *input.LoadBalancerArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DeleteLoadBalancer handles the ELBv2 DeleteLoadBalancer API call.
func DeleteLoadBalancer(input *elbv2.DeleteLoadBalancerInput, natsConn *nats.Conn, accountID string) (elbv2.DeleteLoadBalancerOutput, error) {
var output elbv2.DeleteLoadBalancerOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LoadBalancerArn == nil || *input.LoadBalancerArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDeleteLoadBalancerInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDeleteTargetGroupInput validates the input parameters
func ValidateDeleteTargetGroupInput(input *elbv2.DeleteTargetGroupInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DeleteTargetGroup handles the ELBv2 DeleteTargetGroup API call.
func DeleteTargetGroup(input *elbv2.DeleteTargetGroupInput, natsConn *nats.Conn, accountID string) (elbv2.DeleteTargetGroupOutput, error) {
var output elbv2.DeleteTargetGroupOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDeleteTargetGroupInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+16 -8
View File
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDeregisterTargetsInput validates the input parameters
func ValidateDeregisterTargetsInput(input *elbv2.DeregisterTargetsInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if len(input.Targets) == 0 {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DeregisterTargets handles the ELBv2 DeregisterTargets API call.
func DeregisterTargets(input *elbv2.DeregisterTargetsInput, natsConn *nats.Conn, accountID string) (elbv2.DeregisterTargetsOutput, error) {
var output elbv2.DeregisterTargetsOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if len(input.Targets) == 0 {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDeregisterTargetsInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDescribeLoadBalancerAttributesInput validates the input parameters
func ValidateDescribeLoadBalancerAttributesInput(input *elbv2.DescribeLoadBalancerAttributesInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LoadBalancerArn == nil || *input.LoadBalancerArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DescribeLoadBalancerAttributes handles the ELBv2 DescribeLoadBalancerAttributes API call.
func DescribeLoadBalancerAttributes(input *elbv2.DescribeLoadBalancerAttributesInput, natsConn *nats.Conn, accountID string) (elbv2.DescribeLoadBalancerAttributesOutput, error) {
var output elbv2.DescribeLoadBalancerAttributesOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LoadBalancerArn == nil || *input.LoadBalancerArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDescribeLoadBalancerAttributesInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDescribeTargetGroupAttributesInput validates the input parameters
func ValidateDescribeTargetGroupAttributesInput(input *elbv2.DescribeTargetGroupAttributesInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DescribeTargetGroupAttributes handles the ELBv2 DescribeTargetGroupAttributes API call.
func DescribeTargetGroupAttributes(input *elbv2.DescribeTargetGroupAttributesInput, natsConn *nats.Conn, accountID string) (elbv2.DescribeTargetGroupAttributesOutput, error) {
var output elbv2.DescribeTargetGroupAttributesOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDescribeTargetGroupAttributesInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+13 -5
View File
@@ -9,15 +9,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateDescribeTargetHealthInput validates the input parameters
func ValidateDescribeTargetHealthInput(input *elbv2.DescribeTargetHealthInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// DescribeTargetHealth handles the ELBv2 DescribeTargetHealth API call.
func DescribeTargetHealth(input *elbv2.DescribeTargetHealthInput, natsConn *nats.Conn, accountID string) (elbv2.DescribeTargetHealthOutput, error) {
var output elbv2.DescribeTargetHealthOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateDescribeTargetHealthInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+13 -5
View File
@@ -8,15 +8,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateGetLBConfigInput validates the input parameters
func ValidateGetLBConfigInput(input *handlers_elbv2.GetLBConfigInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LBID == nil || *input.LBID == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// GetLBConfig handles the ELBv2 GetLBConfig API call.
func GetLBConfig(input *handlers_elbv2.GetLBConfigInput, natsConn *nats.Conn, accountID string) (handlers_elbv2.GetLBConfigOutput, error) {
var output handlers_elbv2.GetLBConfigOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LBID == nil || *input.LBID == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateGetLBConfigInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+13 -5
View File
@@ -8,15 +8,23 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateLBAgentHeartbeatInput validates the input parameters
func ValidateLBAgentHeartbeatInput(input *handlers_elbv2.LBAgentHeartbeatInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LBID == nil || *input.LBID == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// LBAgentHeartbeat handles the ELBv2 LBAgentHeartbeat API call.
func LBAgentHeartbeat(input *handlers_elbv2.LBAgentHeartbeatInput, natsConn *nats.Conn, accountID string) (handlers_elbv2.LBAgentHeartbeatOutput, error) {
var output handlers_elbv2.LBAgentHeartbeatOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LBID == nil || *input.LBID == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateLBAgentHeartbeatInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateModifyLoadBalancerAttributesInput validates the input parameters
func ValidateModifyLoadBalancerAttributesInput(input *elbv2.ModifyLoadBalancerAttributesInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LoadBalancerArn == nil || *input.LoadBalancerArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if len(input.Attributes) == 0 {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// ModifyLoadBalancerAttributes handles the ELBv2 ModifyLoadBalancerAttributes API call.
func ModifyLoadBalancerAttributes(input *elbv2.ModifyLoadBalancerAttributesInput, natsConn *nats.Conn, accountID string) (elbv2.ModifyLoadBalancerAttributesOutput, error) {
var output elbv2.ModifyLoadBalancerAttributesOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.LoadBalancerArn == nil || *input.LoadBalancerArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if len(input.Attributes) == 0 {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateModifyLoadBalancerAttributesInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateModifyTargetGroupAttributesInput validates the input parameters
func ValidateModifyTargetGroupAttributesInput(input *elbv2.ModifyTargetGroupAttributesInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if len(input.Attributes) == 0 {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// ModifyTargetGroupAttributes handles the ELBv2 ModifyTargetGroupAttributes API call.
func ModifyTargetGroupAttributes(input *elbv2.ModifyTargetGroupAttributesInput, natsConn *nats.Conn, accountID string) (elbv2.ModifyTargetGroupAttributesOutput, error) {
var output elbv2.ModifyTargetGroupAttributesOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if len(input.Attributes) == 0 {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateModifyTargetGroupAttributesInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+16 -8
View File
@@ -9,18 +9,26 @@ import (
"github.com/nats-io/nats.go"
)
// ValidateRegisterTargetsInput validates the input parameters
func ValidateRegisterTargetsInput(input *elbv2.RegisterTargetsInput) error {
if input == nil {
return errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return errors.New(awserrors.ErrorMissingParameter)
}
if len(input.Targets) == 0 {
return errors.New(awserrors.ErrorMissingParameter)
}
return nil
}
// RegisterTargets handles the ELBv2 RegisterTargets API call.
func RegisterTargets(input *elbv2.RegisterTargetsInput, natsConn *nats.Conn, accountID string) (elbv2.RegisterTargetsOutput, error) {
var output elbv2.RegisterTargetsOutput
if input == nil {
return output, errors.New(awserrors.ErrorInvalidParameterValue)
}
if input.TargetGroupArn == nil || *input.TargetGroupArn == "" {
return output, errors.New(awserrors.ErrorMissingParameter)
}
if len(input.Targets) == 0 {
return output, errors.New(awserrors.ErrorMissingParameter)
if err := ValidateRegisterTargetsInput(input); err != nil {
return output, err
}
svc := handlers_elbv2.NewNATSELBv2Service(natsConn)
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/mulgadc/predastore/ratelimit"
"github.com/mulgadc/spinifex/spinifex/admin"
"github.com/mulgadc/spinifex/spinifex/config"
"github.com/mulgadc/spinifex/spinifex/gateway"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
@@ -102,7 +103,7 @@ func launchService(config *config.ClusterConfig) error {
// Connect to NATS for service communication. On concurrent startup the
// local NATS server may not be listening yet, so retry with backoff.
natsConn, err := utils.ConnectNATSWithRetry(nodeConfig.NATS.Host, nodeConfig.NATS.ACL.Token, nodeConfig.NATS.CACert)
natsConn, err := utils.ConnectNATSWithRetry(admin.DialTarget(nodeConfig.NATS.Host), nodeConfig.NATS.ACL.Token, nodeConfig.NATS.CACert)
if err != nil {
return err
}
@@ -24,7 +24,13 @@ export default defineConfig({
"@tanstack/router/create-route-property-order": "error",
"@tanstack/router/route-param-names": "error",
"eslint/complexity": "off",
"eslint/func-style": "off",
"eslint/func-style": [
"error",
"declaration",
{
allowArrowFunctions: true,
},
],
"eslint/no-plusplus": ["error", { allowForLoopAfterthoughts: true }],
"eslint/no-use-before-define": "off",
"eslint/prefer-destructuring": "off",
@@ -33,15 +39,15 @@ export default defineConfig({
"eslint/sort-vars": "off",
"import/consistent-type-specifier-style": "off",
"promise/prefer-await-to-callbacks": "off",
"react/jsx-handler-names": "off",
"react-perf/jsx-no-new-function-as-prop": "off",
"typescript/no-confusing-void-expression": "off",
"typescript/no-floating-promises": "off",
"typescript/no-misused-promises": "off",
"typescript/only-throw-error": "off",
"typescript/prefer-readonly-parameter-types": "off",
"typescript/promise-function-async": "off",
"typescript/return-await": "off",
"typescript/strict-boolean-expressions": "off",
"typescript/strict-void-return": "off",
"unicorn/filename-case": [
"error",
{ cases: { kebabCase: true, camelCase: true } },