Skip to content

Commit 8f74409

Browse files
committed
refactor: replace manual env overrides with caarlos0/env
1 parent a4d2d84 commit 8f74409

4 files changed

Lines changed: 52 additions & 64 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.25.0
44

55
require (
66
github.com/BurntSushi/toml v1.6.0
7+
github.com/caarlos0/env/v11 v11.4.0
78
github.com/deevus/truenas-go v0.4.0
89
github.com/dustin/go-humanize v1.0.1
910
github.com/spf13/cobra v1.10.2

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeX
22
al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
33
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
44
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
5+
github.com/caarlos0/env/v11 v11.4.0 h1:Kcb6t5kIIr4XkoQC9AF2j+8E1Jsrl3Wz/hhm1LtoGAc=
6+
github.com/caarlos0/env/v11 v11.4.0/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
57
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
68
github.com/deevus/truenas-go v0.4.0 h1:gESJ0naqtwzgdN1/gG5wBrp/Lm/5HF8xCBsRcwNgg78=
79
github.com/deevus/truenas-go v0.4.0/go.mod h1:a5MwZEqT4NE8jwSA9BHONOAO8yH4kCaS5a+d4ad6sLA=

internal/config/config.go

Lines changed: 21 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7-
"strconv"
87
"strings"
98

109
"github.com/BurntSushi/toml"
10+
"github.com/caarlos0/env/v11"
1111
)
1212

1313
type Config struct {
@@ -21,36 +21,36 @@ type Config struct {
2121
}
2222

2323
type TrueNAS struct {
24-
Host string `toml:"host"`
25-
Port int `toml:"port"`
26-
Username string `toml:"username"`
27-
APIKey string `toml:"api_key"`
28-
InsecureSkipVerify *bool `toml:"insecure_skip_verify"`
24+
Host string `toml:"host" env:"PIXELS_TRUENAS_HOST"`
25+
Port int `toml:"port" env:"PIXELS_TRUENAS_PORT"`
26+
Username string `toml:"username" env:"PIXELS_TRUENAS_USERNAME"`
27+
APIKey string `toml:"api_key" env:"PIXELS_TRUENAS_API_KEY"`
28+
InsecureSkipVerify *bool `toml:"insecure_skip_verify" env:"PIXELS_TRUENAS_INSECURE"`
2929
}
3030

3131
type Defaults struct {
32-
Image string `toml:"image"`
33-
CPU string `toml:"cpu"`
34-
Memory int64 `toml:"memory"` // MiB
35-
Pool string `toml:"pool"`
36-
NICType string `toml:"nic_type"` // "macvlan" or "bridged"
37-
Parent string `toml:"parent"` // parent interface (e.g. "eno1", "br0")
32+
Image string `toml:"image" env:"PIXELS_DEFAULT_IMAGE"`
33+
CPU string `toml:"cpu" env:"PIXELS_DEFAULT_CPU"`
34+
Memory int64 `toml:"memory" env:"PIXELS_DEFAULT_MEMORY"` // MiB
35+
Pool string `toml:"pool" env:"PIXELS_DEFAULT_POOL"`
36+
NICType string `toml:"nic_type"` // "macvlan" or "bridged"
37+
Parent string `toml:"parent"` // parent interface (e.g. "eno1", "br0")
3838
Network string `toml:"network"` // Incus network name (e.g. "incusbr0")
3939
DNS []string `toml:"dns"` // nameservers to write into containers
4040
}
4141

4242
type SSH struct {
43-
User string `toml:"user"`
44-
Key string `toml:"key"`
43+
User string `toml:"user" env:"PIXELS_SSH_USER"`
44+
Key string `toml:"key" env:"PIXELS_SSH_KEY"`
4545
}
4646

4747
type Checkpoint struct {
48-
DatasetPrefix string `toml:"dataset_prefix"`
48+
DatasetPrefix string `toml:"dataset_prefix" env:"PIXELS_CHECKPOINT_DATASET_PREFIX"`
4949
}
5050

5151
type Provision struct {
52-
Enabled *bool `toml:"enabled"`
53-
DevTools *bool `toml:"devtools"`
52+
Enabled *bool `toml:"enabled" env:"PIXELS_PROVISION_ENABLED"`
53+
DevTools *bool `toml:"devtools" env:"PIXELS_PROVISION_DEVTOOLS"`
5454
}
5555

5656
func (p *Provision) IsEnabled() bool {
@@ -68,7 +68,7 @@ func (p *Provision) DevToolsEnabled() bool {
6868
}
6969

7070
type Network struct {
71-
Egress string `toml:"egress"`
71+
Egress string `toml:"egress" env:"PIXELS_NETWORK_EGRESS"`
7272
Allow []string `toml:"allow"`
7373
}
7474

@@ -103,21 +103,9 @@ func Load() (*Config, error) {
103103
}
104104
}
105105

106-
applyEnv(&cfg.TrueNAS.Host, "PIXELS_TRUENAS_HOST")
107-
applyEnv(&cfg.TrueNAS.Username, "PIXELS_TRUENAS_USERNAME")
108-
applyEnv(&cfg.TrueNAS.APIKey, "PIXELS_TRUENAS_API_KEY")
109-
applyEnvInt(&cfg.TrueNAS.Port, "PIXELS_TRUENAS_PORT")
110-
applyEnvBool(&cfg.TrueNAS.InsecureSkipVerify, "PIXELS_TRUENAS_INSECURE")
111-
applyEnv(&cfg.Defaults.Image, "PIXELS_DEFAULT_IMAGE")
112-
applyEnv(&cfg.Defaults.CPU, "PIXELS_DEFAULT_CPU")
113-
applyEnvInt64(&cfg.Defaults.Memory, "PIXELS_DEFAULT_MEMORY")
114-
applyEnv(&cfg.Defaults.Pool, "PIXELS_DEFAULT_POOL")
115-
applyEnv(&cfg.SSH.User, "PIXELS_SSH_USER")
116-
applyEnv(&cfg.SSH.Key, "PIXELS_SSH_KEY")
117-
applyEnv(&cfg.Checkpoint.DatasetPrefix, "PIXELS_CHECKPOINT_DATASET_PREFIX")
118-
applyEnvBool(&cfg.Provision.Enabled, "PIXELS_PROVISION_ENABLED")
119-
applyEnvBool(&cfg.Provision.DevTools, "PIXELS_PROVISION_DEVTOOLS")
120-
applyEnv(&cfg.Network.Egress, "PIXELS_NETWORK_EGRESS")
106+
if err := env.Parse(cfg); err != nil {
107+
return nil, fmt.Errorf("parsing environment: %w", err)
108+
}
121109

122110
cfg.SSH.Key = expandHome(cfg.SSH.Key)
123111

@@ -136,37 +124,6 @@ func configPath() string {
136124
return filepath.Join(dir, "pixels", "config.toml")
137125
}
138126

139-
func applyEnv(dst *string, key string) {
140-
if v := os.Getenv(key); v != "" {
141-
*dst = v
142-
}
143-
}
144-
145-
func applyEnvInt(dst *int, key string) {
146-
if v := os.Getenv(key); v != "" {
147-
if n, err := strconv.Atoi(v); err == nil {
148-
*dst = n
149-
}
150-
}
151-
}
152-
153-
func applyEnvInt64(dst *int64, key string) {
154-
if v := os.Getenv(key); v != "" {
155-
if n, err := strconv.ParseInt(v, 10, 64); err == nil {
156-
*dst = n
157-
}
158-
}
159-
}
160-
161-
func applyEnvBool(dst **bool, key string) {
162-
if v := os.Getenv(key); v != "" {
163-
b, err := strconv.ParseBool(v)
164-
if err == nil {
165-
*dst = &b
166-
}
167-
}
168-
}
169-
170127
// InsecureSkipVerify returns whether TLS verification should be skipped.
171128
// Defaults to true (skip) when not explicitly set, since most TrueNAS boxes use self-signed certs.
172129
func (t *TrueNAS) InsecureSkipVerifyValue() bool {

internal/config/config_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,34 @@ api_key = "file-key"
191191
}
192192
}
193193

194+
func TestEmptyEnvDoesNotOverrideDefault(t *testing.T) {
195+
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
196+
t.Setenv("PIXELS_DEFAULT_IMAGE", "")
197+
t.Setenv("PIXELS_DEFAULT_POOL", "")
198+
199+
cfg, err := Load()
200+
if err != nil {
201+
t.Fatalf("Load() error: %v", err)
202+
}
203+
204+
if cfg.Defaults.Image != "ubuntu/24.04" {
205+
t.Errorf("image = %q, want %q (empty env should not override default)", cfg.Defaults.Image, "ubuntu/24.04")
206+
}
207+
if cfg.Defaults.Pool != "tank" {
208+
t.Errorf("pool = %q, want %q (empty env should not override default)", cfg.Defaults.Pool, "tank")
209+
}
210+
}
211+
212+
func TestInvalidEnvReturnsError(t *testing.T) {
213+
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
214+
t.Setenv("PIXELS_TRUENAS_PORT", "not-a-number")
215+
216+
_, err := Load()
217+
if err == nil {
218+
t.Fatal("expected error for invalid PIXELS_TRUENAS_PORT, got nil")
219+
}
220+
}
221+
194222
func TestProvisionEnvOverride(t *testing.T) {
195223
dir := t.TempDir()
196224
t.Setenv("XDG_CONFIG_HOME", dir)

0 commit comments

Comments
 (0)