Skip to content

Commit 0d93b20

Browse files
authored
Merge pull request #297 from michaelw/codex/align-config-discovery-docs
fix: align config path docs and implementation
2 parents 0cc2733 + 5b51cfa commit 0d93b20

8 files changed

Lines changed: 99 additions & 22 deletions

File tree

cmd/cli/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ thand request access --provider aws-prod --role admin --duration 4h --reason "Da
2121

2222
All commands support these global flags:
2323

24-
- `--config <path>` - Config file (default: `$HOME/.thand/config.yaml`)
24+
- `--config <path>` - Config file (default: `$HOME/.config/thand/config.yaml`)
2525
- `--login-server <url>` - Override default login server URL (e.g., `http://localhost:8080`)
2626
- `--verbose`, `-v` - Enable verbose output
2727

@@ -214,7 +214,7 @@ Launches a guided wizard that walks through creating an access request with prop
214214

215215
## Configuration
216216

217-
The agent uses a YAML configuration file located at `$HOME/.thand/config.yaml` by default. You can specify a different config file using the `--config` flag.
217+
The agent uses a YAML configuration file located at `$HOME/.config/thand/config.yaml` by default. You can specify a different config file using the `--config` flag.
218218

219219
**Example configuration:**
220220
```yaml
@@ -260,4 +260,4 @@ thand service start
260260
261261
# Update to latest version
262262
thand update --check
263-
```
263+
```

cmd/cli/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ func init() {
311311

312312
// Add global flags
313313
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose output")
314-
rootCmd.PersistentFlags().String("config", "", "Config file (default is $HOME/.thand/config.yaml)")
314+
rootCmd.PersistentFlags().String("config", "", "Config file (default is $HOME/.config/thand/config.yaml)")
315315
// Add the login-server flag
316316
rootCmd.PersistentFlags().String("login-server", "", "Override the default login server URL (e.g., http://localhost:8080)")
317317

docs/configuration/cli.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ thand --verbose server
5454

5555
```
5656

57+
`--config` takes precedence over `THAND_CONFIG_PATH`. `THAND_CONFIG_PATH` chooses the config file location, while other `THAND_*` environment variables override values loaded from that file.
58+
5759
---
5860

5961
## Main Command

docs/configuration/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ Thand Agent uses YAML configuration files to define behavior, providers, roles,
1919
Configuration is loaded in this order (later sources override earlier ones):
2020

2121
1. Default values
22-
2. Configuration file (`~/.thand/config.yaml`)
22+
2. Configuration file (`~/.config/thand/config.yaml` by default, or the path set by `THAND_CONFIG_PATH`)
2323
3. Environment variables (prefixed with `THAND_`)
2424
4. Command line flags
2525

26+
`--config` overrides `THAND_CONFIG_PATH`. `THAND_CONFIG_PATH` selects which config file to read, while other `THAND_*` variables override values after the file is loaded.
27+
2628
{: .warning }
2729
> **Important:** If you are using Temporal (recommended for production), you must configure specific Search Attributes in your Temporal Namespace. See **[Temporal Configuration](temporal.md)** for critical setup instructions.
2830

docs/environments/docker/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,11 @@ docker run -d \
126126
-p 8080:8080 \
127127
-v $(pwd)/thand-config:/app/config:ro \
128128
-e THAND_CONFIG_PATH=/app/config/config.yaml \
129-
ghcr.io/thand-io/agent:latest \
130-
./thand server --config /app/config/config.yaml
129+
ghcr.io/thand-io/agent:latest
131130
```
132131

132+
`THAND_CONFIG_PATH` selects the config file to load inside the container. Other `THAND_*` variables still override values from that file.
133+
133134
### Option 2: Environment Variables
134135

135136
Run with environment-based configuration:
@@ -425,4 +426,3 @@ docker run -d \
425426
- Set up [approval workflows](../../configuration/workflows)
426427
- Integrate with your existing authentication systems
427428
- Set up monitoring and alerting for production deployments
428-

docs/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ The binary will be available at `bin/agent`.
8282

8383
### Basic Configuration
8484

85-
Create a configuration file at `~/.thand/config.yaml`:
85+
Create a configuration file at `~/.config/thand/config.yaml`:
8686

8787
```yaml
8888
server:

internal/config/config.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"log"
99
"net/http"
1010
"os"
11-
"os/user"
1211
"path/filepath"
1312
"runtime"
1413
"runtime/debug"
@@ -30,6 +29,8 @@ import (
3029
var ErrNoActiveLoginSession = fmt.Errorf(
3130
"you must login first. No valid session found to sync with login server")
3231

32+
const configPathEnvVar = "THAND_CONFIG_PATH"
33+
3334
func DefaultConfig() *Config {
3435

3536
v := viper.New()
@@ -111,9 +112,8 @@ func setupViperConfig(v *viper.Viper, configFile string) error {
111112
v.AddConfigPath(".")
112113
v.AddConfigPath("./config")
113114
v.AddConfigPath("/etc/thand")
114-
v.AddConfigPath("~/.config/thand")
115115

116-
if len(configFile) > 0 {
116+
if configFile = resolveConfigFileOverride(configFile); len(configFile) > 0 {
117117
v.SetConfigFile(configFile)
118118
}
119119

@@ -133,21 +133,23 @@ func setupViperConfig(v *viper.Viper, configFile string) error {
133133
return nil
134134
}
135135

136+
func resolveConfigFileOverride(configFile string) string {
137+
configFile = strings.TrimSpace(configFile)
138+
if len(configFile) > 0 {
139+
return configFile
140+
}
141+
142+
return strings.TrimSpace(os.Getenv(configPathEnvVar))
143+
}
144+
136145
// setupHomeConfigPath adds the home directory config path if available
137146
func setupHomeConfigPath(v *viper.Viper) error {
138-
home := os.Getenv("HOME")
139-
if len(home) == 0 {
147+
home, err := os.UserHomeDir()
148+
if err != nil || len(home) == 0 {
140149
return nil
141150
}
142151

143-
// Get the user's home directory
144-
usr, err := user.Current()
145-
if err != nil {
146-
logrus.Fatalf("Failed to get current user: %v", err)
147-
}
148-
149-
// Expand the session manager path to use the actual home directory
150-
sessionPath := filepath.Join(usr.HomeDir, ".config", "thand")
152+
sessionPath := filepath.Join(home, ".config", "thand")
151153
v.AddConfigPath(sessionPath)
152154

153155
// Check if the folder exists and create it if it does not exist
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package config
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
"github.com/thand-io/agent/internal/common"
11+
)
12+
13+
func TestLoad_PrefersExplicitConfigFileOverEnvOverride(t *testing.T) {
14+
envConfigPath := writeTestConfigFile(t, filepath.Join(t.TempDir(), "env.yaml"), "env-secret")
15+
explicitConfigPath := writeTestConfigFile(t, filepath.Join(t.TempDir(), "explicit.yaml"), "explicit-secret")
16+
17+
t.Setenv(configPathEnvVar, envConfigPath)
18+
19+
cfg, err := Load(explicitConfigPath)
20+
require.NoError(t, err)
21+
22+
assert.Equal(t, "explicit-secret", cfg.Secret)
23+
}
24+
25+
func TestLoad_UsesConfigPathEnvOverrideWhenFlagNotProvided(t *testing.T) {
26+
configPath := writeTestConfigFile(t, filepath.Join(t.TempDir(), "env.yaml"), "env-secret")
27+
28+
t.Setenv(configPathEnvVar, configPath)
29+
30+
cfg, err := Load("")
31+
require.NoError(t, err)
32+
33+
assert.Equal(t, "env-secret", cfg.Secret)
34+
}
35+
36+
func TestLoad_FindsDefaultConfigInHomeConfigDir(t *testing.T) {
37+
homeDir := t.TempDir()
38+
t.Setenv("HOME", homeDir)
39+
t.Setenv(configPathEnvVar, "")
40+
41+
configPath := filepath.Join(homeDir, ".config", "thand", "config.yaml")
42+
writeTestConfigFile(t, configPath, "home-secret")
43+
44+
cfg, err := Load("")
45+
require.NoError(t, err)
46+
47+
assert.Equal(t, "home-secret", cfg.Secret)
48+
}
49+
50+
func TestLoad_DoesNotUseLegacyThandConfigPath(t *testing.T) {
51+
homeDir := t.TempDir()
52+
t.Setenv("HOME", homeDir)
53+
t.Setenv(configPathEnvVar, "")
54+
55+
legacyConfigPath := filepath.Join(homeDir, ".thand", "config.yaml")
56+
writeTestConfigFile(t, legacyConfigPath, "legacy-secret")
57+
58+
cfg, err := Load("")
59+
require.NoError(t, err)
60+
61+
assert.Equal(t, common.DefaultServerSecret, cfg.Secret)
62+
}
63+
64+
func writeTestConfigFile(t *testing.T, path string, secret string) string {
65+
t.Helper()
66+
67+
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
68+
require.NoError(t, os.WriteFile(path, []byte("secret: "+secret+"\n"), 0o644))
69+
70+
return path
71+
}

0 commit comments

Comments
 (0)