podman
1// Package for processing environment variables.
2package env3
4// TODO: we need to add tests for this package.
5
6import (7"bufio"8"fmt"9"os"10"strings"11
12"golang.org/x/exp/maps"13)
14
15const whiteSpaces = " \t"16
17// DefaultEnvVariables returns a default environment, with $PATH and $TERM set.
18func DefaultEnvVariables() map[string]string {19return map[string]string{20"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",21"container": "podman",22}23}
24
25// Slice transforms the specified map of environment variables into a
26// slice. If a value is non-empty, the key and value are joined with '='.
27func Slice(m map[string]string) []string {28env := make([]string, 0, len(m))29for k, v := range m {30var s string31if len(v) > 0 {32s = fmt.Sprintf("%s=%s", k, v)33} else {34s = k35}36env = append(env, s)37}38return env39}
40
41// Map transforms the specified slice of environment variables into a
42// map.
43func Map(slice []string) map[string]string {44envmap := make(map[string]string, len(slice))45for _, line := range slice {46key, val, _ := strings.Cut(line, "=")47envmap[key] = val48}49return envmap50}
51
52// Join joins the two environment maps with override overriding base.
53func Join(base map[string]string, override map[string]string) map[string]string {54if len(base) == 0 {55return maps.Clone(override)56}57base = maps.Clone(base)58for k, v := range override {59base[k] = v60}61return base62}
63
64// ParseFile parses the specified path for environment variables and returns them
65// as a map.
66func ParseFile(path string) (_ map[string]string, err error) {67env := make(map[string]string)68defer func() {69if err != nil {70err = fmt.Errorf("parsing file %q: %w", path, err)71}72}()73
74fh, err := os.Open(path)75if err != nil {76return nil, err77}78defer fh.Close()79
80scanner := bufio.NewScanner(fh)81for scanner.Scan() {82// trim the line from all leading whitespace first83line := strings.TrimLeft(scanner.Text(), whiteSpaces)84// line is not empty, and not starting with '#'85if len(line) > 0 && !strings.HasPrefix(line, "#") {86if err := parseEnv(env, line); err != nil {87return nil, err88}89}90}91return env, scanner.Err()92}
93
94func parseEnv(env map[string]string, line string) error {95key, val, hasVal := strings.Cut(line, "=")96
97// catch invalid variables such as "=" or "=A"98if key == "" {99return fmt.Errorf("invalid variable: %q", line)100}101// trim the front of a variable, but nothing else102name := strings.TrimLeft(key, whiteSpaces)103if hasVal {104env[name] = val105} else {106if name, hasStar := strings.CutSuffix(name, "*"); hasStar {107for _, e := range os.Environ() {108envKey, envVal, hasEq := strings.Cut(e, "=")109if !hasEq {110continue111}112if strings.HasPrefix(envKey, name) {113env[envKey] = envVal114}115}116} else if val, ok := os.LookupEnv(name); ok {117// if only a pass-through variable is given, clean it up.118env[name] = val119}120}121return nil122}
123