Skip to content

Commit accaa37

Browse files
SmilingNavernmholt
authored andcommitted
Add -env-file flag (caddyserver#2176)
This adds new feature to load envs from file provided from command line argument Implement parsing of the env file for simple KEY=VALUE format
1 parent 60a0208 commit accaa37

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

caddy/caddymain/run.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"errors"
2020
"flag"
2121
"fmt"
22+
"io"
2223
"io/ioutil"
2324
"log"
2425
"os"
@@ -50,6 +51,7 @@ func init() {
5051
flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable")
5152
flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")")
5253
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
54+
flag.StringVar(&envFile, "env", "", "Path to file with environment variables to load in KEY=VALUE format")
5355
flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
5456
flag.StringVar(&caddytls.DefaultEmail, "email", "", "Default ACME CA account email address")
5557
flag.DurationVar(&acme.HTTPClient.Timeout, "catimeout", acme.HTTPClient.Timeout, "Default ACME CA HTTP timeout")
@@ -90,6 +92,11 @@ func Run() {
9092
})
9193
}
9294

95+
//Load all additional envs as soon as possible
96+
if err := LoadEnvFromFile(envFile); err != nil {
97+
mustLogFatalf("%v", err)
98+
}
99+
93100
// initialize telemetry client
94101
if enableTelemetry {
95102
err := initTelemetry()
@@ -409,13 +416,88 @@ func initTelemetry() error {
409416
return nil
410417
}
411418

419+
// LoadEnvFromFile loads additional envs if file provided and exists
420+
// Envs in file should be in KEY=VALUE format
421+
func LoadEnvFromFile(envFile string) error {
422+
if envFile == "" {
423+
return nil
424+
}
425+
426+
file, err := os.Open(envFile)
427+
if err != nil {
428+
return err
429+
}
430+
defer file.Close()
431+
432+
envMap, err := ParseEnvFile(file)
433+
if err != nil {
434+
return err
435+
}
436+
437+
for k, v := range envMap {
438+
if err := os.Setenv(k, v); err != nil {
439+
return err
440+
}
441+
}
442+
443+
return nil
444+
}
445+
446+
// ParseEnvFile implements parse logic for environment files
447+
func ParseEnvFile(envInput io.Reader) (map[string]string, error) {
448+
envMap := make(map[string]string)
449+
450+
scanner := bufio.NewScanner(envInput)
451+
var line string
452+
lineNumber := 0
453+
454+
for scanner.Scan() {
455+
line = strings.TrimSpace(scanner.Text())
456+
lineNumber++
457+
458+
// skip lines starting with comment
459+
if strings.HasPrefix(line, "#") {
460+
continue
461+
}
462+
463+
// skip empty line
464+
if len(line) == 0 {
465+
continue
466+
}
467+
468+
fields := strings.SplitN(line, "=", 2)
469+
if len(fields) != 2 {
470+
return nil, fmt.Errorf("Can't parse line %d; line should be in KEY=VALUE format", lineNumber)
471+
}
472+
473+
if strings.Contains(fields[0], " ") {
474+
return nil, fmt.Errorf("Can't parse line %d; KEY contains whitespace", lineNumber)
475+
}
476+
477+
key := fields[0]
478+
val := fields[1]
479+
480+
if key == "" {
481+
return nil, fmt.Errorf("Can't parse line %d; KEY can't be empty string", lineNumber)
482+
}
483+
envMap[key] = val
484+
}
485+
486+
if err := scanner.Err(); err != nil {
487+
return nil, err
488+
}
489+
490+
return envMap, nil
491+
}
492+
412493
const appName = "Caddy"
413494

414495
// Flags that control program flow or startup
415496
var (
416497
serverType string
417498
conf string
418499
cpu string
500+
envFile string
419501
logfile string
420502
revoke string
421503
version bool

caddy/caddymain/run_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
package caddymain
1616

1717
import (
18+
"reflect"
1819
"runtime"
20+
"strings"
1921
"testing"
2022
)
2123

@@ -57,3 +59,34 @@ func TestSetCPU(t *testing.T) {
5759
runtime.GOMAXPROCS(currentCPU)
5860
}
5961
}
62+
63+
func TestParseEnvFile(t *testing.T) {
64+
tests := []struct {
65+
name string
66+
input string
67+
want map[string]string
68+
wantErr bool
69+
}{
70+
{"parsing KEY=VALUE", "PORT=4096", map[string]string{"PORT": "4096"}, false},
71+
{"empty KEY", "=4096", nil, true},
72+
{"one value", "test", nil, true},
73+
{"comments skipped", "#TEST=1\nPORT=8888", map[string]string{"PORT": "8888"}, false},
74+
{"empty line", "\nPORT=7777", map[string]string{"PORT": "7777"}, false},
75+
{"comments with space skipped", " #TEST=1", map[string]string{}, false},
76+
{"KEY with space", "PORT =8888", nil, true},
77+
{"only spaces", " ", map[string]string{}, false},
78+
}
79+
for _, tt := range tests {
80+
t.Run(tt.name, func(t *testing.T) {
81+
reader := strings.NewReader(tt.input)
82+
got, err := ParseEnvFile(reader)
83+
if (err != nil) != tt.wantErr {
84+
t.Errorf("ParseEnvFile() error = %v, wantErr %v", err, tt.wantErr)
85+
return
86+
}
87+
if !reflect.DeepEqual(got, tt.want) {
88+
t.Errorf("ParseEnvFile() = %v, want %v", got, tt.want)
89+
}
90+
})
91+
}
92+
}

0 commit comments

Comments
 (0)