Skip to content

Commit fd1846f

Browse files
committed
feat: require explicit region for env creation and add region resolution tests
**Added:** - Unit tests for ResolveRegion and ResolveRegionWithInventory to verify correct region selection and error handling in different scenarios - config_test.go **Changed:** - Region flag for env create now defaults to empty and is required, with improved help text describing all ways to set the region - Logic for env creation updated to resolve region using config, environment variable, or flag, and to error clearly if no region is set - Refactored environment scaffolding logic into a new scaffoldEnv function to improve readability and separation of concerns in env_cmd.go
1 parent 8307391 commit fd1846f

2 files changed

Lines changed: 95 additions & 4 deletions

File tree

cli/cmd/env_cmd.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func init() {
5151
envCmd.AddCommand(envCreateCmd)
5252
envCmd.AddCommand(envListCmd)
5353

54-
envCreateCmd.Flags().String("region", "us-east-1", "AWS region for the environment")
54+
envCreateCmd.Flags().String("region", "", "AWS region for the environment (required; or set via dreadgoad.yaml / DREADGOAD_REGION / --region)")
5555
envCreateCmd.Flags().String("vpc-cidr", "", "VPC CIDR block (default: auto-assigned)")
5656
envCreateCmd.Flags().String("reference", "staging", "Reference environment to copy infrastructure from")
5757
envCreateCmd.Flags().Bool("variant", false, "Generate randomized variant config")
@@ -70,17 +70,26 @@ func runEnvCreate(cmd *cobra.Command, args []string) error {
7070
}
7171

7272
region, _ := cmd.Flags().GetString("region")
73+
if region == "" {
74+
region, err = cfg.ResolveRegion()
75+
if err != nil {
76+
return fmt.Errorf("env create requires a region: pass --region, set 'region' in dreadgoad.yaml, or export DREADGOAD_REGION")
77+
}
78+
}
7379
vpcCIDR, _ := cmd.Flags().GetString("vpc-cidr")
7480
reference, _ := cmd.Flags().GetString("reference")
7581
useVariant, _ := cmd.Flags().GetBool("variant")
7682
force, _ := cmd.Flags().GetBool("force")
7783

78-
deployment := cfg.Infra.Deployment
79-
infraBase := filepath.Join(cfg.ProjectRoot, "infra", deployment)
80-
8184
if vpcCIDR == "" {
8285
vpcCIDR = cfg.VpcCIDR(envName)
8386
}
87+
88+
return scaffoldEnv(cfg, envName, region, vpcCIDR, reference, useVariant, force)
89+
}
90+
91+
func scaffoldEnv(cfg *config.Config, envName, region, vpcCIDR, reference string, useVariant, force bool) error {
92+
infraBase := filepath.Join(cfg.ProjectRoot, "infra", cfg.Infra.Deployment)
8493
envDir := filepath.Join(infraBase, envName)
8594
regionDir := filepath.Join(envDir, region)
8695

cli/internal/config/config_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,90 @@ import (
55
"path/filepath"
66
"strings"
77
"testing"
8+
9+
"github.com/dreadnode/dreadgoad/internal/inventory"
810
)
911

12+
func TestResolveRegion(t *testing.T) {
13+
t.Run("returns configured region", func(t *testing.T) {
14+
c := &Config{Region: "eu-west-1"}
15+
got, err := c.ResolveRegion()
16+
if err != nil {
17+
t.Fatalf("unexpected error: %v", err)
18+
}
19+
if got != "eu-west-1" {
20+
t.Errorf("ResolveRegion() = %q, want %q", got, "eu-west-1")
21+
}
22+
})
23+
24+
t.Run("errors when region is empty", func(t *testing.T) {
25+
c := &Config{Region: ""}
26+
_, err := c.ResolveRegion()
27+
if err == nil {
28+
t.Fatal("expected error for empty region, got nil")
29+
}
30+
if !strings.Contains(err.Error(), "region") {
31+
t.Errorf("error should mention region, got: %v", err)
32+
}
33+
})
34+
}
35+
36+
func TestResolveRegionWithInventory(t *testing.T) {
37+
t.Run("prefers inventory region", func(t *testing.T) {
38+
c := &Config{Region: "us-west-1"}
39+
inv := &inventory.Inventory{
40+
Vars: map[string]string{"ansible_aws_ssm_region": "ap-southeast-1"},
41+
}
42+
got, err := c.ResolveRegionWithInventory(inv)
43+
if err != nil {
44+
t.Fatalf("unexpected error: %v", err)
45+
}
46+
if got != "ap-southeast-1" {
47+
t.Errorf("ResolveRegionWithInventory() = %q, want %q", got, "ap-southeast-1")
48+
}
49+
})
50+
51+
t.Run("falls back to config when inventory has no region", func(t *testing.T) {
52+
c := &Config{Region: "us-east-2"}
53+
inv := &inventory.Inventory{Vars: map[string]string{}}
54+
got, err := c.ResolveRegionWithInventory(inv)
55+
if err != nil {
56+
t.Fatalf("unexpected error: %v", err)
57+
}
58+
if got != "us-east-2" {
59+
t.Errorf("ResolveRegionWithInventory() = %q, want %q", got, "us-east-2")
60+
}
61+
})
62+
63+
t.Run("falls back to config when inventory is nil", func(t *testing.T) {
64+
c := &Config{Region: "eu-central-1"}
65+
got, err := c.ResolveRegionWithInventory(nil)
66+
if err != nil {
67+
t.Fatalf("unexpected error: %v", err)
68+
}
69+
if got != "eu-central-1" {
70+
t.Errorf("ResolveRegionWithInventory() = %q, want %q", got, "eu-central-1")
71+
}
72+
})
73+
74+
t.Run("errors when both inventory and config have no region", func(t *testing.T) {
75+
c := &Config{Region: ""}
76+
inv := &inventory.Inventory{Vars: map[string]string{}}
77+
_, err := c.ResolveRegionWithInventory(inv)
78+
if err == nil {
79+
t.Fatal("expected error when no region available, got nil")
80+
}
81+
})
82+
83+
t.Run("errors when nil inventory and config has no region", func(t *testing.T) {
84+
c := &Config{Region: ""}
85+
_, err := c.ResolveRegionWithInventory(nil)
86+
if err == nil {
87+
t.Fatal("expected error when no region available, got nil")
88+
}
89+
})
90+
}
91+
1092
func TestConfigInventoryPath(t *testing.T) {
1193
c := &Config{ProjectRoot: "/opt/goad", Env: "dev"}
1294
got := c.InventoryPath()

0 commit comments

Comments
 (0)