Commit accaa378 authored by Alexander Danilov's avatar Alexander Danilov Committed by Matt Holt

Add -env-file flag (#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
parent 60a0208e
...@@ -19,6 +19,7 @@ import ( ...@@ -19,6 +19,7 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
...@@ -50,6 +51,7 @@ func init() { ...@@ -50,6 +51,7 @@ func init() {
flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable") flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable")
flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")") flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")")
flag.StringVar(&cpu, "cpu", "100%", "CPU cap") flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
flag.StringVar(&envFile, "env", "", "Path to file with environment variables to load in KEY=VALUE format")
flag.BoolVar(&plugins, "plugins", false, "List installed plugins") flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
flag.StringVar(&caddytls.DefaultEmail, "email", "", "Default ACME CA account email address") flag.StringVar(&caddytls.DefaultEmail, "email", "", "Default ACME CA account email address")
flag.DurationVar(&acme.HTTPClient.Timeout, "catimeout", acme.HTTPClient.Timeout, "Default ACME CA HTTP timeout") flag.DurationVar(&acme.HTTPClient.Timeout, "catimeout", acme.HTTPClient.Timeout, "Default ACME CA HTTP timeout")
...@@ -90,6 +92,11 @@ func Run() { ...@@ -90,6 +92,11 @@ func Run() {
}) })
} }
//Load all additional envs as soon as possible
if err := LoadEnvFromFile(envFile); err != nil {
mustLogFatalf("%v", err)
}
// initialize telemetry client // initialize telemetry client
if enableTelemetry { if enableTelemetry {
err := initTelemetry() err := initTelemetry()
...@@ -409,6 +416,80 @@ func initTelemetry() error { ...@@ -409,6 +416,80 @@ func initTelemetry() error {
return nil return nil
} }
// LoadEnvFromFile loads additional envs if file provided and exists
// Envs in file should be in KEY=VALUE format
func LoadEnvFromFile(envFile string) error {
if envFile == "" {
return nil
}
file, err := os.Open(envFile)
if err != nil {
return err
}
defer file.Close()
envMap, err := ParseEnvFile(file)
if err != nil {
return err
}
for k, v := range envMap {
if err := os.Setenv(k, v); err != nil {
return err
}
}
return nil
}
// ParseEnvFile implements parse logic for environment files
func ParseEnvFile(envInput io.Reader) (map[string]string, error) {
envMap := make(map[string]string)
scanner := bufio.NewScanner(envInput)
var line string
lineNumber := 0
for scanner.Scan() {
line = strings.TrimSpace(scanner.Text())
lineNumber++
// skip lines starting with comment
if strings.HasPrefix(line, "#") {
continue
}
// skip empty line
if len(line) == 0 {
continue
}
fields := strings.SplitN(line, "=", 2)
if len(fields) != 2 {
return nil, fmt.Errorf("Can't parse line %d; line should be in KEY=VALUE format", lineNumber)
}
if strings.Contains(fields[0], " ") {
return nil, fmt.Errorf("Can't parse line %d; KEY contains whitespace", lineNumber)
}
key := fields[0]
val := fields[1]
if key == "" {
return nil, fmt.Errorf("Can't parse line %d; KEY can't be empty string", lineNumber)
}
envMap[key] = val
}
if err := scanner.Err(); err != nil {
return nil, err
}
return envMap, nil
}
const appName = "Caddy" const appName = "Caddy"
// Flags that control program flow or startup // Flags that control program flow or startup
...@@ -416,6 +497,7 @@ var ( ...@@ -416,6 +497,7 @@ var (
serverType string serverType string
conf string conf string
cpu string cpu string
envFile string
logfile string logfile string
revoke string revoke string
version bool version bool
......
...@@ -15,7 +15,9 @@ ...@@ -15,7 +15,9 @@
package caddymain package caddymain
import ( import (
"reflect"
"runtime" "runtime"
"strings"
"testing" "testing"
) )
...@@ -57,3 +59,34 @@ func TestSetCPU(t *testing.T) { ...@@ -57,3 +59,34 @@ func TestSetCPU(t *testing.T) {
runtime.GOMAXPROCS(currentCPU) runtime.GOMAXPROCS(currentCPU)
} }
} }
func TestParseEnvFile(t *testing.T) {
tests := []struct {
name string
input string
want map[string]string
wantErr bool
}{
{"parsing KEY=VALUE", "PORT=4096", map[string]string{"PORT": "4096"}, false},
{"empty KEY", "=4096", nil, true},
{"one value", "test", nil, true},
{"comments skipped", "#TEST=1\nPORT=8888", map[string]string{"PORT": "8888"}, false},
{"empty line", "\nPORT=7777", map[string]string{"PORT": "7777"}, false},
{"comments with space skipped", " #TEST=1", map[string]string{}, false},
{"KEY with space", "PORT =8888", nil, true},
{"only spaces", " ", map[string]string{}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reader := strings.NewReader(tt.input)
got, err := ParseEnvFile(reader)
if (err != nil) != tt.wantErr {
t.Errorf("ParseEnvFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseEnvFile() = %v, want %v", got, tt.want)
}
})
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment