1package env
2
3import (
4	"errors"
5	"fmt"
6	"os"
7	"strconv"
8	"strings"
9	"time"
10
11	"github.com/go-acme/lego/v4/log"
12)
13
14// Get environment variables.
15func Get(names ...string) (map[string]string, error) {
16	values := map[string]string{}
17
18	var missingEnvVars []string
19	for _, envVar := range names {
20		value := GetOrFile(envVar)
21		if value == "" {
22			missingEnvVars = append(missingEnvVars, envVar)
23		}
24		values[envVar] = value
25	}
26
27	if len(missingEnvVars) > 0 {
28		return nil, fmt.Errorf("some credentials information are missing: %s", strings.Join(missingEnvVars, ","))
29	}
30
31	return values, nil
32}
33
34// GetWithFallback Get environment variable values.
35// The first name in each group is use as key in the result map.
36//
37// case 1:
38//
39//	// LEGO_ONE="ONE"
40//	// LEGO_TWO="TWO"
41//	env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
42//	// => "LEGO_ONE" = "ONE"
43//
44// case 2:
45//
46//	// LEGO_ONE=""
47//	// LEGO_TWO="TWO"
48//	env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
49//	// => "LEGO_ONE" = "TWO"
50//
51// case 3:
52//
53//	// LEGO_ONE=""
54//	// LEGO_TWO=""
55//	env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
56//	// => error
57//
58func GetWithFallback(groups ...[]string) (map[string]string, error) {
59	values := map[string]string{}
60
61	var missingEnvVars []string
62	for _, names := range groups {
63		if len(names) == 0 {
64			return nil, errors.New("undefined environment variable names")
65		}
66
67		value, envVar := getOneWithFallback(names[0], names[1:]...)
68		if value == "" {
69			missingEnvVars = append(missingEnvVars, envVar)
70			continue
71		}
72		values[envVar] = value
73	}
74
75	if len(missingEnvVars) > 0 {
76		return nil, fmt.Errorf("some credentials information are missing: %s", strings.Join(missingEnvVars, ","))
77	}
78
79	return values, nil
80}
81
82func getOneWithFallback(main string, names ...string) (string, string) {
83	value := GetOrFile(main)
84	if len(value) > 0 {
85		return value, main
86	}
87
88	for _, name := range names {
89		value := GetOrFile(name)
90		if len(value) > 0 {
91			return value, main
92		}
93	}
94
95	return "", main
96}
97
98// GetOrDefaultInt returns the given environment variable value as an integer.
99// Returns the default if the envvar cannot be coopered to an int, or is not found.
100func GetOrDefaultInt(envVar string, defaultValue int) int {
101	v, err := strconv.Atoi(GetOrFile(envVar))
102	if err != nil {
103		return defaultValue
104	}
105
106	return v
107}
108
109// GetOrDefaultSecond returns the given environment variable value as an time.Duration (second).
110// Returns the default if the envvar cannot be coopered to an int, or is not found.
111func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
112	v := GetOrDefaultInt(envVar, -1)
113	if v < 0 {
114		return defaultValue
115	}
116
117	return time.Duration(v) * time.Second
118}
119
120// GetOrDefaultString returns the given environment variable value as a string.
121// Returns the default if the envvar cannot be find.
122func GetOrDefaultString(envVar, defaultValue string) string {
123	v := GetOrFile(envVar)
124	if v == "" {
125		return defaultValue
126	}
127
128	return v
129}
130
131// GetOrDefaultBool returns the given environment variable value as a boolean.
132// Returns the default if the envvar cannot be coopered to a boolean, or is not found.
133func GetOrDefaultBool(envVar string, defaultValue bool) bool {
134	v, err := strconv.ParseBool(GetOrFile(envVar))
135	if err != nil {
136		return defaultValue
137	}
138
139	return v
140}
141
142// GetOrFile Attempts to resolve 'key' as an environment variable.
143// Failing that, it will check to see if '<key>_FILE' exists.
144// If so, it will attempt to read from the referenced file to populate a value.
145func GetOrFile(envVar string) string {
146	envVarValue := os.Getenv(envVar)
147	if envVarValue != "" {
148		return envVarValue
149	}
150
151	fileVar := envVar + "_FILE"
152	fileVarValue := os.Getenv(fileVar)
153	if fileVarValue == "" {
154		return envVarValue
155	}
156
157	fileContents, err := os.ReadFile(fileVarValue)
158	if err != nil {
159		log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
160		return ""
161	}
162
163	return strings.TrimSuffix(string(fileContents), "\n")
164}
165