Skip to content

Commit c034e98

Browse files
authored
Merge pull request #77 from xadips/feature/add_environment_variable_support_2
feat: add environment variable support
2 parents 3d60771 + 42e98cd commit c034e98

File tree

4 files changed

+260
-8
lines changed

4 files changed

+260
-8
lines changed

README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ Flags:
7777
--version print the version and exit
7878
```
7979

80+
Environment variables can be used to set the flags, by setting the flag name in uppercase and prefixing it with `ETCD_DEFRAG_`. Please note that all hyphens should be replaced with underscores. For example, the flag `--move-leader` can be set with the environment variable `ETCD_DEFRAG_MOVE_LEADER`
81+
82+
Flag values are evaluated in the following order: (from highest to lowest priority)
83+
84+
1. Flags passed as command line arguments
85+
2. Environment variables
86+
3. Default values
87+
8088
## Integration with Kubernetes with a CronJob
8189

8290
It is possible to use [the example cronjob in
@@ -116,9 +124,9 @@ Output:
116124
Validating configuration.
117125
No defragmentation rule provided
118126
Performing health check.
119-
endpoint: https://127.0.0.1:2379, health: true, took: 4.702492ms, error:
120-
endpoint: https://127.0.0.1:22379, health: true, took: 5.017075ms, error:
121-
endpoint: https://127.0.0.1:32379, health: true, took: 4.747068ms, error:
127+
endpoint: https://127.0.0.1:2379, health: true, took: 4.702492ms, error:
128+
endpoint: https://127.0.0.1:22379, health: true, took: 5.017075ms, error:
129+
endpoint: https://127.0.0.1:32379, health: true, took: 4.747068ms, error:
122130
Getting members status
123131
endpoint: https://127.0.0.1:2379, dbSize: 172032, dbSizeInUse: 126976, memberId: 8211f1d0f64f3269, leader: 8211f1d0f64f3269, revision: 10365, term: 2, index: 10425
124132
endpoint: https://127.0.0.1:22379, dbSize: 122880, dbSizeInUse: 122880, memberId: 91bc3c398fb3c146, leader: 8211f1d0f64f3269, revision: 10365, term: 2, index: 10425
@@ -156,8 +164,8 @@ $ etcdctl endpoint status -w table --cluster
156164
+-------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
157165
```
158166
## Defragmentation rule
159-
Defragmentation is an expensive operation, so it should be executed as infrequent as possible. On the other hand,
160-
it's also necessary to make sure any etcd member will not run out of the storage quota. It's exactly the reason
167+
Defragmentation is an expensive operation, so it should be executed as infrequent as possible. On the other hand,
168+
it's also necessary to make sure any etcd member will not run out of the storage quota. It's exactly the reason
161169
why the defragmentation rule is introduced, it can skip unnecessary expensive defragmentation, and also keep
162170
each member safe.
163171

@@ -190,9 +198,9 @@ Output:
190198
Validating configuration.
191199
Validating the defragmentation rule: dbSize > dbQuota*80/100 || dbSize - dbSizeInUse > 200*1024*1024 ... valid
192200
Performing health check.
193-
endpoint: http://127.0.0.1:2379, health: true, took: 6.993264ms, error:
194-
endpoint: http://127.0.0.1:32379, health: true, took: 7.483368ms, error:
195-
endpoint: http://127.0.0.1:22379, health: true, took: 49.441931ms, error:
201+
endpoint: http://127.0.0.1:2379, health: true, took: 6.993264ms, error:
202+
endpoint: http://127.0.0.1:32379, health: true, took: 7.483368ms, error:
203+
endpoint: http://127.0.0.1:22379, health: true, took: 49.441931ms, error:
196204
Getting members status
197205
endpoint: http://127.0.0.1:2379, dbSize: 131072, dbSizeInUse: 131072, memberId: 8211f1d0f64f3269, leader: 8211f1d0f64f3269, revision: 10964, term: 2, index: 11028
198206
endpoint: http://127.0.0.1:22379, dbSize: 131072, dbSizeInUse: 131072, memberId: 91bc3c398fb3c146, leader: 8211f1d0f64f3269, revision: 10964, term: 2, index: 11028

cmd_test.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"testing"
6+
"time"
7+
8+
"github.com/spf13/cobra"
9+
"github.com/spf13/viper"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestAllFlags(t *testing.T) {
14+
testCases := []struct {
15+
name string
16+
env map[string]string
17+
cli []string
18+
want globalConfig
19+
}{
20+
{
21+
name: "defaults only",
22+
env: nil,
23+
cli: nil,
24+
want: globalConfig{
25+
endpoints: []string{"127.0.0.1:2379"},
26+
useClusterEndpoints: false,
27+
excludeLocalhost: false,
28+
moveLeader: false,
29+
dialTimeout: 2 * time.Second,
30+
commandTimeout: 30 * time.Second,
31+
keepAliveTime: 2 * time.Second,
32+
keepAliveTimeout: 6 * time.Second,
33+
insecure: true,
34+
insecureSkepVerify: false,
35+
certFile: "",
36+
keyFile: "",
37+
caFile: "",
38+
username: "",
39+
password: "",
40+
dnsDomain: "",
41+
dnsService: "",
42+
insecureDiscovery: true,
43+
compaction: true,
44+
continueOnError: true,
45+
dbQuotaBytes: 2 * 1024 * 1024 * 1024,
46+
defragRule: "",
47+
printVersion: false,
48+
dryRun: false,
49+
},
50+
},
51+
{
52+
name: "all from environment",
53+
env: map[string]string{
54+
"ETCD_DEFRAG_ENDPOINTS": "10.0.0.1:2379,10.0.0.2:2379",
55+
"ETCD_DEFRAG_CLUSTER": "true",
56+
"ETCD_DEFRAG_EXCLUDE_LOCALHOST": "true",
57+
"ETCD_DEFRAG_MOVE_LEADER": "true",
58+
"ETCD_DEFRAG_DIAL_TIMEOUT": "5s",
59+
"ETCD_DEFRAG_COMMAND_TIMEOUT": "45s",
60+
"ETCD_DEFRAG_KEEPALIVE_TIME": "3s",
61+
"ETCD_DEFRAG_KEEPALIVE_TIMEOUT": "8s",
62+
"ETCD_DEFRAG_INSECURE_TRANSPORT": "false",
63+
"ETCD_DEFRAG_INSECURE_SKIP_TLS_VERIFY": "true",
64+
"ETCD_DEFRAG_CERT": "/path/to/cert",
65+
"ETCD_DEFRAG_KEY": "/path/to/key",
66+
"ETCD_DEFRAG_CACERT": "/path/to/ca",
67+
"ETCD_DEFRAG_USER": "envuser",
68+
"ETCD_DEFRAG_PASSWORD": "envpassword",
69+
"ETCD_DEFRAG_DISCOVERY_SRV": "mydomain.com",
70+
"ETCD_DEFRAG_DISCOVERY_SRV_NAME": "etcd",
71+
"ETCD_DEFRAG_INSECURE_DISCOVERY": "false",
72+
"ETCD_DEFRAG_COMPACTION": "false",
73+
"ETCD_DEFRAG_CONTINUE_ON_ERROR": "false",
74+
"ETCD_DEFRAG_ETCD_STORAGE_QUOTA_BYTES": "1073741824",
75+
"ETCD_DEFRAG_DEFRAG_RULE": "size(db) > 500MB",
76+
"ETCD_DEFRAG_VERSION": "true",
77+
"ETCD_DEFRAG_DRY_RUN": "true",
78+
},
79+
cli: nil,
80+
want: globalConfig{
81+
endpoints: []string{"10.0.0.1:2379", "10.0.0.2:2379"},
82+
useClusterEndpoints: true,
83+
excludeLocalhost: true,
84+
moveLeader: true,
85+
dialTimeout: 5 * time.Second,
86+
commandTimeout: 45 * time.Second,
87+
keepAliveTime: 3 * time.Second,
88+
keepAliveTimeout: 8 * time.Second,
89+
insecure: false,
90+
insecureSkepVerify: true,
91+
certFile: "/path/to/cert",
92+
keyFile: "/path/to/key",
93+
caFile: "/path/to/ca",
94+
username: "envuser",
95+
password: "envpassword",
96+
dnsDomain: "mydomain.com",
97+
dnsService: "etcd",
98+
insecureDiscovery: false,
99+
compaction: false,
100+
continueOnError: false,
101+
dbQuotaBytes: 1073741824,
102+
defragRule: "size(db) > 500MB",
103+
printVersion: true,
104+
dryRun: true,
105+
},
106+
},
107+
{
108+
name: "all from CLI (override environment)",
109+
env: map[string]string{
110+
"ETCD_DEFRAG_ENDPOINTS": "shouldBeOverridden:9999",
111+
"ETCD_DEFRAG_CLUSTER": "false",
112+
"ETCD_DEFRAG_MOVE_LEADER": "false",
113+
"ETCD_DEFRAG_INSECURE_TRANSPORT": "true",
114+
},
115+
cli: []string{
116+
"--endpoints=192.168.1.100:2379,192.168.1.101:2379",
117+
"--cluster=true",
118+
"--exclude-localhost=true",
119+
"--move-leader=true",
120+
"--dial-timeout=7s",
121+
"--command-timeout=50s",
122+
"--keepalive-time=4s",
123+
"--keepalive-timeout=10s",
124+
"--insecure-transport=false",
125+
"--insecure-skip-tls-verify=true",
126+
"--cert=/cli/cert",
127+
"--key=/cli/key",
128+
"--cacert=/cli/ca",
129+
"--user=cliuser",
130+
"--password=clipass",
131+
"--discovery-srv=cli.mydomain",
132+
"--discovery-srv-name=clietcd",
133+
"--insecure-discovery=false",
134+
"--compaction=false",
135+
"--continue-on-error=false",
136+
"--etcd-storage-quota-bytes=999999999",
137+
"--defrag-rule=size(db) >= 1GB",
138+
"--version=true",
139+
"--dry-run=true",
140+
},
141+
want: globalConfig{
142+
endpoints: []string{"192.168.1.100:2379", "192.168.1.101:2379"},
143+
useClusterEndpoints: true,
144+
excludeLocalhost: true,
145+
moveLeader: true,
146+
dialTimeout: 7 * time.Second,
147+
commandTimeout: 50 * time.Second,
148+
keepAliveTime: 4 * time.Second,
149+
keepAliveTimeout: 10 * time.Second,
150+
insecure: false,
151+
insecureSkepVerify: true,
152+
certFile: "/cli/cert",
153+
keyFile: "/cli/key",
154+
caFile: "/cli/ca",
155+
username: "cliuser",
156+
password: "clipass",
157+
dnsDomain: "cli.mydomain",
158+
dnsService: "clietcd",
159+
insecureDiscovery: false,
160+
compaction: false,
161+
continueOnError: false,
162+
dbQuotaBytes: 999999999,
163+
defragRule: "size(db) >= 1GB",
164+
printVersion: true,
165+
dryRun: true,
166+
},
167+
},
168+
{
169+
name: "mixed env + CLI",
170+
env: map[string]string{
171+
"ETCD_DEFRAG_ENDPOINTS": "env:2379",
172+
"ETCD_DEFRAG_CLUSTER": "false",
173+
"ETCD_DEFRAG_MOVE_LEADER": "true",
174+
"ETCD_DEFRAG_COMPACTION": "false",
175+
"ETCD_DEFRAG_ETCD_STORAGE_QUOTA_BYTES": "555555555",
176+
},
177+
cli: []string{
178+
"--exclude-localhost=true", // override the default
179+
"--dial-timeout=10s", // override the default
180+
"--compaction=true", // override the env
181+
},
182+
want: globalConfig{
183+
endpoints: []string{"env:2379"},
184+
useClusterEndpoints: false, // env sets cluster=false
185+
excludeLocalhost: true, // from CLI
186+
moveLeader: true, // from env
187+
dialTimeout: 10 * time.Second,
188+
commandTimeout: 30 * time.Second, // default
189+
keepAliveTime: 2 * time.Second, // default
190+
keepAliveTimeout: 6 * time.Second, // default
191+
insecure: true, // default
192+
insecureSkepVerify: false, // default
193+
certFile: "",
194+
keyFile: "",
195+
caFile: "",
196+
username: "",
197+
password: "",
198+
dnsDomain: "",
199+
dnsService: "",
200+
insecureDiscovery: true,
201+
compaction: true, // CLI override
202+
continueOnError: true, // default
203+
dbQuotaBytes: 555555555, // from env
204+
defragRule: "",
205+
printVersion: false, // default
206+
dryRun: false, // default
207+
},
208+
},
209+
}
210+
211+
for _, tc := range testCases {
212+
t.Run(tc.name, func(t *testing.T) {
213+
viper.Reset()
214+
os.Clearenv()
215+
216+
for key, val := range tc.env {
217+
if err := os.Setenv(key, val); err != nil {
218+
t.Fatalf("failed to set env %s=%s: %v", key, val, err)
219+
}
220+
}
221+
222+
cmd := newDefragCommand()
223+
// Set empty Run function to avoid actual execution
224+
cmd.Run = func(cmd *cobra.Command, args []string) {}
225+
226+
if tc.cli != nil {
227+
cmd.SetArgs(tc.cli)
228+
}
229+
230+
if err := cmd.Execute(); err != nil {
231+
t.Fatalf("command execution failed: %v", err)
232+
}
233+
234+
require.Equal(t, tc.want, globalCfg)
235+
})
236+
}
237+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/maja42/goval v1.6.0
99
github.com/spf13/cobra v1.9.1
1010
github.com/spf13/viper v1.19.0
11+
github.com/stretchr/testify v1.9.0
1112
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0.0.20240409070941-65ac859a1b61
1213
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0.0.20240409070941-65ac859a1b61
1314
go.etcd.io/etcd/client/v3 v3.6.0-alpha.0.0.20240409070941-65ac859a1b61
@@ -18,6 +19,7 @@ require (
1819
require (
1920
github.com/coreos/go-semver v0.3.1 // indirect
2021
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
22+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
2123
github.com/fsnotify/fsnotify v1.7.0 // indirect
2224
github.com/gogo/protobuf v1.3.2 // indirect
2325
github.com/golang/protobuf v1.5.4 // indirect
@@ -27,6 +29,7 @@ require (
2729
github.com/magiconair/properties v1.8.7 // indirect
2830
github.com/mitchellh/mapstructure v1.5.0 // indirect
2931
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
32+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3033
github.com/sagikazarmark/locafero v0.4.0 // indirect
3134
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
3235
github.com/sourcegraph/conc v0.3.0 // indirect

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ func newDefragCommand() *cobra.Command {
2525
},
2626
Run: defragCommandFunc,
2727
}
28+
29+
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
30+
viper.SetEnvPrefix("ETCD_DEFRAG")
31+
viper.AutomaticEnv()
2832
setDefaults()
2933

3034
// Manually splitting, because GetStringSlice has inconsistent behavior for splitting command line flags and environment variables

0 commit comments

Comments
 (0)