Add the shared packages for the sharks stack
model, tunnel, wg, authtoken, netguard, docker and dotenv, split out of the original single repository so Hammerhead, Blacktip and Bamboo can live in their own repos and import them. README extended; LICENSE unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c7c2ae8f8d
commit
2259580adf
16 files changed
+1359
-1
No files matched your search
@@ -0,0 +1,89 @@
|
||||
// Package dotenv loads KEY=VALUE pairs from a .env file into the process
|
||||
// environment, so secrets can live in one file that is kept out of version
|
||||
// control instead of on command lines.
|
||||
//
|
||||
// Real environment variables always win over the file, so a deployment can
|
||||
// still override anything with its own environment.
|
||||
package dotenv
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Load reads the first of paths that exists and sets any variables it defines
|
||||
// that aren't already set. It returns the path used, or "" if none existed.
|
||||
func Load(paths ...string) (string, error) {
|
||||
for _, p := range paths {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
f, err := os.Open(p)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
sc := bufio.NewScanner(f)
|
||||
sc.Buffer(make([]byte, 64*1024), 1<<20)
|
||||
for sc.Scan() {
|
||||
k, v, ok := parseLine(sc.Text())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, set := os.LookupEnv(k); !set {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
return p, sc.Err()
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// ArgPath finds -env <path> / --env=<path> in args, so the file can be chosen
|
||||
// before the program's own flags are parsed (flags take their defaults from the environment).
|
||||
func ArgPath(args []string, def string) string {
|
||||
for i, a := range args {
|
||||
a = strings.TrimLeft(a, "-")
|
||||
switch {
|
||||
case a == "env" && i+1 < len(args):
|
||||
return args[i+1]
|
||||
case strings.HasPrefix(a, "env="):
|
||||
return a[len("env="):]
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func parseLine(line string) (key, val string, ok bool) {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
return "", "", false
|
||||
}
|
||||
line = strings.TrimSpace(strings.TrimPrefix(line, "export "))
|
||||
k, v, found := strings.Cut(line, "=")
|
||||
k = strings.TrimSpace(k)
|
||||
if !found || k == "" || strings.ContainsAny(k, " \t") {
|
||||
return "", "", false
|
||||
}
|
||||
v = strings.TrimSpace(v)
|
||||
switch {
|
||||
case len(v) >= 2 && v[0] == '"':
|
||||
if end := strings.LastIndexByte(v, '"'); end > 0 {
|
||||
v = strings.NewReplacer(`\n`, "\n", `\r`, "\r", `\t`, "\t", `\"`, `"`, `\\`, `\`).Replace(v[1:end])
|
||||
}
|
||||
case len(v) >= 2 && v[0] == '\'':
|
||||
if end := strings.LastIndexByte(v, '\''); end > 0 {
|
||||
v = v[1:end]
|
||||
}
|
||||
default:
|
||||
if i := strings.Index(v, " #"); i >= 0 { // trailing comment
|
||||
v = strings.TrimSpace(v[:i])
|
||||
}
|
||||
}
|
||||
return k, v, true
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package dotenv
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadParsesAndNeverOverridesTheRealEnvironment(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, ".env")
|
||||
os.WriteFile(path, []byte(`# a comment
|
||||
PLAIN=value one
|
||||
export EXPORTED=yes
|
||||
QUOTED="has # hash and \"quotes\"\nnewline"
|
||||
SINGLE='no $expansion \n here'
|
||||
TRAILING=kept # dropped
|
||||
EMPTY=
|
||||
SPACED = trimmed
|
||||
BAD LINE
|
||||
=novalue
|
||||
ALREADY=from-file
|
||||
`), 0o600)
|
||||
t.Setenv("ALREADY", "from-environment")
|
||||
for _, k := range []string{"PLAIN", "EXPORTED", "QUOTED", "SINGLE", "TRAILING", "EMPTY", "SPACED"} {
|
||||
os.Unsetenv(k)
|
||||
defer os.Unsetenv(k)
|
||||
}
|
||||
used, err := Load(filepath.Join(dir, "missing.env"), path)
|
||||
if err != nil || used != path {
|
||||
t.Fatalf("Load = %q, %v", used, err)
|
||||
}
|
||||
want := map[string]string{
|
||||
"PLAIN": "value one", "EXPORTED": "yes", "QUOTED": "has # hash and \"quotes\"\nnewline",
|
||||
"SINGLE": `no $expansion \n here`, "TRAILING": "kept", "EMPTY": "", "SPACED": "trimmed", "ALREADY": "from-environment",
|
||||
}
|
||||
for k, v := range want {
|
||||
if got, ok := os.LookupEnv(k); !ok || got != v {
|
||||
t.Errorf("%s = %q (set=%v), want %q", k, got, ok, v)
|
||||
}
|
||||
}
|
||||
if used, _ := Load(filepath.Join(dir, "nope")); used != "" {
|
||||
t.Errorf("a missing file should load nothing, got %q", used)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgPath(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{[]string{"-listen", ":1"}, ".env"},
|
||||
{[]string{"-env", "/etc/x.env"}, "/etc/x.env"},
|
||||
{[]string{"--env=/a/b"}, "/a/b"},
|
||||
{[]string{"-env"}, ".env"},
|
||||
} {
|
||||
if got := ArgPath(tc.args, ".env"); got != tc.want {
|
||||
t.Errorf("ArgPath(%v) = %q, want %q", tc.args, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user