Skip to content

Commit eca6cf5

Browse files
author
tytv2
committed
feat: add GRN_ACCESS_KEY_ID/GRN_SECRET_ACCESS_KEY env var support
1 parent 87f56b1 commit eca6cf5

6 files changed

Lines changed: 91 additions & 29 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ go/
8080
## Security rules
8181

8282
- **Credential masking**: `configure list` and `configure get` mask client_id/client_secret (last 4 chars only)
83-
- **No credential env vars**: `GRN_CLIENT_ID`/`GRN_CLIENT_SECRET` not supported — file only
83+
- **Credential env vars supported**: `GRN_ACCESS_KEY_ID`/`GRN_SECRET_ACCESS_KEY` override credentials file (highest priority)
8484
- **Input validation**: All cluster-id/nodegroup-id validated via `validator.ValidateID()` before URLs
8585
- **SSL default on**: `--no-verify-ssl` prints warning to stderr
8686
- **Tokens in memory only**: Never written to disk or logged

README.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,17 @@ grn --version
4343

4444
### Configuration
4545

46-
Before using the GreenNode CLI, you need to configure your credentials. The quickest way is to run:
46+
Before using the GreenNode CLI, you need to configure your credentials. There are three ways:
47+
48+
**Method 1: Environment variables**
49+
50+
```bash
51+
export GRN_ACCESS_KEY_ID=your-client-id
52+
export GRN_SECRET_ACCESS_KEY=your-client-secret
53+
export GRN_DEFAULT_REGION=HCM-3
54+
```
55+
56+
**Method 2: Interactive setup (recommended)**
4757

4858
```bash
4959
grn configure
@@ -56,9 +66,7 @@ Default region name [HCM-3]:
5666
Default output format [json]:
5767
```
5868

59-
Credentials are obtained from the [VNG Cloud IAM Portal](https://hcm-3.console.vngcloud.vn/iam/) under Service Accounts.
60-
61-
Or create the credential files directly:
69+
**Method 3: Credentials file (manual)**
6270

6371
```ini
6472
# ~/.greenode/credentials
@@ -74,14 +82,18 @@ region = HCM-3
7482
output = json
7583
```
7684

85+
Credentials are obtained from the [VNG Cloud IAM Portal](https://hcm-3.console.vngcloud.vn/iam/) under Service Accounts.
86+
87+
Credential resolution order: environment variables take priority over the credentials file.
88+
7789
To use multiple profiles:
7890

7991
```bash
8092
grn configure --profile staging
8193
grn --profile staging vks list-clusters
8294
```
8395

84-
For more configuration options, see the [Configuration Guide](https://vngcloud.github.io/greennode-cli/configuration/).
96+
For more configuration options, see the [Configuration Guide](https://vngcloud.github.io/greenode-cli/configuration/).
8597

8698
### Basic Commands
8799

@@ -120,7 +132,7 @@ The best way to interact with our team is through GitHub:
120132

121133
## More Resources
122134

123-
- [Documentation](https://vngcloud.github.io/greennode-cli/)
135+
- [Documentation](https://vngcloud.github.io/greenode-cli/)
124136
- [Changelog](CHANGELOG.md)
125137
- [Contributing Guide](CONTRIBUTING.md)
126138
- [VNG Cloud Console](https://hcm-3.console.vngcloud.vn/)

docs/configuration.md

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,37 @@ Default output format [json]:
1717

1818
Credentials are obtained from the [VNG Cloud IAM Portal](https://hcm-3.console.vngcloud.vn/iam/) under Service Accounts.
1919

20+
## Credential resolution order
21+
22+
Credentials are resolved in the following order (highest to lowest priority):
23+
24+
1. **Environment variables**: `GRN_ACCESS_KEY_ID`, `GRN_SECRET_ACCESS_KEY`
25+
2. **Shared credentials file**: `~/.greenode/credentials`
26+
27+
## Environment variables
28+
29+
| Variable | Description |
30+
|----------|-------------|
31+
| `GRN_ACCESS_KEY_ID` | Client ID (overrides credentials file) |
32+
| `GRN_SECRET_ACCESS_KEY` | Client Secret (overrides credentials file) |
33+
| `GRN_DEFAULT_REGION` | Default region |
34+
| `GRN_PROFILE` | Profile name (default: "default") |
35+
| `GRN_DEFAULT_OUTPUT` | Output format |
36+
37+
Environment variables take priority over config file values.
38+
39+
### Example
40+
41+
```bash
42+
# Set credentials via environment variables
43+
export GRN_ACCESS_KEY_ID=your-client-id
44+
export GRN_SECRET_ACCESS_KEY=your-client-secret
45+
export GRN_DEFAULT_REGION=HCM-3
46+
47+
# Commands will use env var credentials automatically
48+
grn vks list-clusters
49+
```
50+
2051
## Config files
2152

2253
Credentials and config are stored in separate files:
@@ -80,16 +111,6 @@ export GRN_PROFILE=staging
80111
grn vks list-clusters
81112
```
82113

83-
## Environment variables
84-
85-
| Variable | Description |
86-
|----------|-------------|
87-
| `GRN_DEFAULT_REGION` | Default region |
88-
| `GRN_PROFILE` | Profile name |
89-
| `GRN_DEFAULT_OUTPUT` | Output format |
90-
91-
Environment variables take priority over config file values.
92-
93114
## Available regions
94115

95116
| Region | VKS Endpoint |

docs/usage/global-options.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ Global options are placed before the service name:
66
grn [global-options] <service> <command> [command-options]
77
```
88

9+
## Environment variables
10+
11+
Global options can also be set via environment variables. See the [Configuration Guide](../configuration.md#environment-variables) for the full list, including `GRN_ACCESS_KEY_ID` and `GRN_SECRET_ACCESS_KEY` for credential overrides.
12+
913
## Options
1014

1115
| Option | Description |

go/cmd/configure/list.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ func resolveCredEntry(name, value, credsFile string) configEntry {
7070
if value == "" {
7171
return configEntry{name: name, value: "<not set>", typ: "None", location: "None"}
7272
}
73+
74+
// Check if value came from env var
75+
envMap := map[string]string{
76+
"client_id": "GRN_ACCESS_KEY_ID",
77+
"client_secret": "GRN_SECRET_ACCESS_KEY",
78+
}
79+
if envVar, ok := envMap[name]; ok {
80+
if os.Getenv(envVar) != "" {
81+
return configEntry{name: name, value: config.MaskCredential(value), typ: "env", location: envVar}
82+
}
83+
}
84+
7385
home, _ := os.UserHomeDir()
7486
loc := "~" + credsFile[len(home):]
7587
return configEntry{name: name, value: config.MaskCredential(value), typ: "config-file", location: loc}

go/internal/config/config.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,32 @@ func LoadConfig(profile string) (*Config, error) {
5252
Regions: REGIONS,
5353
}
5454

55-
// Load credentials
56-
credsFile := filepath.Join(configDir, "credentials")
57-
if _, err := os.Stat(credsFile); err == nil {
58-
iniCreds, err := ini.Load(credsFile)
59-
if err != nil {
60-
return nil, fmt.Errorf("failed to parse credentials file: %w", err)
61-
}
62-
section, err := iniCreds.GetSection(profile)
63-
if err != nil {
64-
return nil, fmt.Errorf("profile '%s' does not exist in %s", profile, credsFile)
55+
// Load credentials — env vars override file
56+
if v := os.Getenv("GRN_ACCESS_KEY_ID"); v != "" {
57+
cfg.ClientID = v
58+
}
59+
if v := os.Getenv("GRN_SECRET_ACCESS_KEY"); v != "" {
60+
cfg.ClientSecret = v
61+
}
62+
63+
if cfg.ClientID == "" || cfg.ClientSecret == "" {
64+
credsFile := filepath.Join(configDir, "credentials")
65+
if _, err := os.Stat(credsFile); err == nil {
66+
iniCreds, err := ini.Load(credsFile)
67+
if err != nil {
68+
return nil, fmt.Errorf("failed to parse credentials file: %w", err)
69+
}
70+
section, err := iniCreds.GetSection(profile)
71+
if err != nil {
72+
return nil, fmt.Errorf("profile '%s' does not exist in %s", profile, credsFile)
73+
}
74+
if cfg.ClientID == "" {
75+
cfg.ClientID = section.Key("client_id").String()
76+
}
77+
if cfg.ClientSecret == "" {
78+
cfg.ClientSecret = section.Key("client_secret").String()
79+
}
6580
}
66-
cfg.ClientID = section.Key("client_id").String()
67-
cfg.ClientSecret = section.Key("client_secret").String()
6881
}
6982

7083
// Load config file

0 commit comments

Comments
 (0)