Skip to content

Commit 561b061

Browse files
mkultraWasHerel50
authored andcommitted
feat: add instance_profile config field for EC2 Image Builder (#203)
- Add `InstanceProfile` field to `Config` struct with `mapstructure:"instance_profile"` tag - Wire `ami build` to fall back to `cfg.InstanceProfile` via `getFlagString` (flag > config > empty) - Add viper default and `dreadgoad.yaml` entry for `instance_profile` - Add `TestConfigInstanceProfile` (struct field + mapstructure tag validation) and `TestGetFlagString` (4-case precedence table) in new `cmd/ami_test.go` - [x] `go test ./internal/config/` — all pass - [x] `go test ./cmd/` — all pass (including new `TestGetFlagString`)
1 parent a790d8f commit 561b061

6 files changed

Lines changed: 117 additions & 16 deletions

File tree

cli/cmd/ami.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func runAMIBuild(cmd *cobra.Command, args []string) error {
174174
region: getFlagString(cmd, "region", cfg.Region, ""),
175175
instanceType: getFlagStringOpt(cmd, "instance-type"),
176176
profile: getFlagStringOpt(cmd, "profile"),
177-
instanceProfile: getFlagStringOpt(cmd, "instance-profile"),
177+
instanceProfile: getFlagString(cmd, "instance-profile", cfg.InstanceProfile, ""),
178178
reuseResources: getFlagBool(cmd, "reuse-resources"),
179179
}
180180

cli/cmd/ami_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func TestGetFlagString(t *testing.T) {
10+
newCmd := func(flagVal string) *cobra.Command {
11+
cmd := &cobra.Command{Use: "test"}
12+
cmd.Flags().String("instance-profile", "", "")
13+
if flagVal != "" {
14+
if err := cmd.Flags().Set("instance-profile", flagVal); err != nil {
15+
t.Fatalf("set flag: %v", err)
16+
}
17+
}
18+
return cmd
19+
}
20+
21+
tests := []struct {
22+
name string
23+
flagVal string
24+
fallback1 string
25+
fallback2 string
26+
want string
27+
}{
28+
{
29+
name: "flag wins over config fallback",
30+
flagVal: "FlagProfile",
31+
fallback1: "ConfigProfile",
32+
fallback2: "default",
33+
want: "FlagProfile",
34+
},
35+
{
36+
name: "config fallback used when flag empty",
37+
flagVal: "",
38+
fallback1: "ConfigProfile",
39+
fallback2: "default",
40+
want: "ConfigProfile",
41+
},
42+
{
43+
name: "second fallback when both flag and config empty",
44+
flagVal: "",
45+
fallback1: "",
46+
fallback2: "default",
47+
want: "default",
48+
},
49+
{
50+
name: "empty when all sources empty",
51+
flagVal: "",
52+
fallback1: "",
53+
fallback2: "",
54+
want: "",
55+
},
56+
}
57+
58+
for _, tt := range tests {
59+
t.Run(tt.name, func(t *testing.T) {
60+
cmd := newCmd(tt.flagVal)
61+
got := getFlagString(cmd, "instance-profile", tt.fallback1, tt.fallback2)
62+
if got != tt.want {
63+
t.Errorf("getFlagString() = %q, want %q", got, tt.want)
64+
}
65+
})
66+
}
67+
}

cli/internal/config/config.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -86,21 +86,22 @@ func (l LudusConfig) SSHTarget() string {
8686

8787
// Config holds all CLI configuration.
8888
type Config struct {
89-
Env string `mapstructure:"env"`
90-
Provider string `mapstructure:"provider"`
91-
Region string `mapstructure:"region"`
92-
Debug bool `mapstructure:"debug"`
93-
MaxRetries int `mapstructure:"max_retries"`
94-
RetryDelay int `mapstructure:"retry_delay"`
95-
IdleTimeout int `mapstructure:"idle_timeout"`
96-
LogDir string `mapstructure:"log_dir"`
97-
Playbooks []string `mapstructure:"playbooks"`
98-
ProjectRoot string `mapstructure:"project_root"`
99-
Environments map[string]EnvironmentConfig `mapstructure:"environments"`
100-
Extensions map[string]ExtensionConfig `mapstructure:"extensions"`
101-
Infra InfraConfig `mapstructure:"infra"`
102-
Proxmox ProxmoxConfig `mapstructure:"proxmox"`
103-
Ludus LudusConfig `mapstructure:"ludus"`
89+
Env string `mapstructure:"env"`
90+
Provider string `mapstructure:"provider"`
91+
Region string `mapstructure:"region"`
92+
InstanceProfile string `mapstructure:"instance_profile"`
93+
Debug bool `mapstructure:"debug"`
94+
MaxRetries int `mapstructure:"max_retries"`
95+
RetryDelay int `mapstructure:"retry_delay"`
96+
IdleTimeout int `mapstructure:"idle_timeout"`
97+
LogDir string `mapstructure:"log_dir"`
98+
Playbooks []string `mapstructure:"playbooks"`
99+
ProjectRoot string `mapstructure:"project_root"`
100+
Environments map[string]EnvironmentConfig `mapstructure:"environments"`
101+
Extensions map[string]ExtensionConfig `mapstructure:"extensions"`
102+
Infra InfraConfig `mapstructure:"infra"`
103+
Proxmox ProxmoxConfig `mapstructure:"proxmox"`
104+
Ludus LudusConfig `mapstructure:"ludus"`
104105
}
105106

106107
var (

cli/internal/config/config_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
"os"
55
"path/filepath"
6+
"reflect"
67
"strings"
78
"testing"
89

@@ -185,6 +186,36 @@ func TestLabConfigPath(t *testing.T) {
185186
})
186187
}
187188

189+
func TestConfigInstanceProfile(t *testing.T) {
190+
t.Run("field accessible on struct", func(t *testing.T) {
191+
c := &Config{InstanceProfile: "WarpgateImageBuilderInstanceProfile"}
192+
if c.InstanceProfile != "WarpgateImageBuilderInstanceProfile" {
193+
t.Errorf("InstanceProfile = %q, want %q", c.InstanceProfile, "WarpgateImageBuilderInstanceProfile")
194+
}
195+
})
196+
197+
t.Run("zero value is empty string", func(t *testing.T) {
198+
c := &Config{}
199+
if c.InstanceProfile != "" {
200+
t.Errorf("InstanceProfile = %q, want empty", c.InstanceProfile)
201+
}
202+
})
203+
204+
t.Run("mapstructure tag is instance_profile", func(t *testing.T) {
205+
// Verify the struct tag so viper unmarshals the YAML key correctly.
206+
var c Config
207+
typ := reflect.TypeOf(c)
208+
f, ok := typ.FieldByName("InstanceProfile")
209+
if !ok {
210+
t.Fatal("Config struct missing InstanceProfile field")
211+
}
212+
tag := f.Tag.Get("mapstructure")
213+
if tag != "instance_profile" {
214+
t.Errorf("mapstructure tag = %q, want %q", tag, "instance_profile")
215+
}
216+
})
217+
}
218+
188219
func TestConfigInventoryPathDifferentEnvs(t *testing.T) {
189220
tests := []struct {
190221
env string

cli/internal/config/defaults.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var RebootPlaybooks = []string{
3333
func setDefaults() {
3434
viper.SetDefault("env", "staging")
3535
viper.SetDefault("region", "")
36+
viper.SetDefault("instance_profile", "")
3637
viper.SetDefault("debug", false)
3738
viper.SetDefault("max_retries", 3)
3839
viper.SetDefault("retry_delay", 30)

dreadgoad.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
env: staging
33
provider: aws # Provider: aws, proxmox, ludus
44
# region: us-west-2 # Override AWS region (default: from inventory)
5+
instance_profile: WarpgateImageBuilderInstanceProfile # IAM instance profile for EC2 Image Builder
56
debug: false
67
max_retries: 3
78
retry_delay: 30

0 commit comments

Comments
 (0)