github.com/42z-io/confik@v0.0.2-0.20231103050132-21d8f377356c/env.go (about) 1 package confik 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "strconv" 10 "strings" 11 ) 12 13 // loadEnvFile will locate and load the environment file into a map[string]string 14 // 15 // loadEnvFile will update the current environment with the files found in the environment file 16 func loadEnvFile[T any](cfg Config[T]) (map[string]string, error) { 17 var envPath string 18 if cfg.EnvFilePath == "" { 19 foundPath, err := findEnvFile() 20 if err != nil { 21 return nil, err 22 } 23 envPath = foundPath 24 } else { 25 envPath = cfg.EnvFilePath 26 } 27 28 // no .env found or provided - return empty map 29 if envPath == "" { 30 envMap := make(map[string]string) 31 return envMap, nil 32 } 33 34 // check if the .env file exists 35 stat, err := os.Stat(envPath) 36 if err != nil { 37 if os.IsNotExist(err) { 38 return nil, fmt.Errorf("environment file does not exist: %s", envPath) 39 } 40 return nil, err 41 } 42 43 // check if the .env file is a directory 44 if stat.IsDir() { 45 return nil, fmt.Errorf("environment file is a directory: %s", envPath) 46 } 47 48 // open and parse the env file 49 file, err := os.Open(envPath) 50 if err != nil { 51 return nil, err 52 } 53 defer file.Close() 54 55 kv, err := parseEnvFile(file) 56 if err != nil { 57 return nil, err 58 } 59 60 // add the discovered environment variables in the environment file to the environment 61 for k, v := range kv { 62 _, exists := os.LookupEnv(k) 63 if cfg.EnvFileOverride || !exists { 64 os.Setenv(k, v) 65 } 66 } 67 return kv, nil 68 } 69 70 // findEnvFile will locate the .env file by looking in the current directory and recursing up the directory structure 71 func findEnvFile() (string, error) { 72 // Start looking for the ".env" in the current directory 73 path, err := os.Getwd() 74 if err != nil { 75 return "", err 76 } 77 var lastPath = filepath.Clean(path) 78 for { 79 var checkPath = filepath.Join(path, ".env") 80 stat, err := os.Stat(checkPath) 81 // If we cant find the ".env" in this directory look in the parent directory 82 if os.IsNotExist(err) { 83 path = filepath.Dir(path) 84 if path == lastPath { 85 return "", nil 86 } 87 lastPath = path 88 } else if err != nil { 89 return "", err 90 } else if stat.IsDir() { 91 return "", fmt.Errorf("environment file is a directory: %s", checkPath) 92 } else { 93 return checkPath, nil 94 } 95 } 96 } 97 98 // parseEnvVar will parse an environment variable in the format NAME=VALUE. 99 func parseEnvVar(expression string) (string, string, error) { 100 // ensure format of the expression is correct 101 if !strings.Contains(expression, "=") { 102 return "", "", fmt.Errorf("invalid expression in env file: %s", expression) 103 } 104 105 // split the variable NAME=value [NAME, value] 106 parts := strings.SplitN(expression, "=", 2) 107 variable := strings.Trim(parts[0], " ") 108 109 // remove any quotes 110 unquoted, err := strconv.Unquote(parts[1]) 111 if err != nil { 112 return variable, parts[1], nil 113 } 114 return variable, unquoted, nil 115 } 116 117 // parseEnvFile will convert an environment file into a map[string]string 118 // 119 // Expects the file in the format: 120 // 121 // MY_VARIABLE=MY_NAME 122 // OTHER_VARIABLE="QUOTED_VALUE" 123 // 124 // Notes: 125 // - Quoted values will be unquoted 126 // - Blank lines will be ingored 127 // - Comments (starting with // or #) will be ignored 128 // - Whitespace around variables and their values will be stripped 129 func parseEnvFile(reader io.Reader) (map[string]string, error) { 130 scanner := bufio.NewScanner(reader) 131 // map to store all the key => value pairs we find in the ".env" file 132 kv := make(map[string]string) 133 for scanner.Scan() { 134 expression := scanner.Text() 135 // ignore comments and blank lines 136 if strings.HasPrefix(expression, "#") || strings.HasPrefix(expression, "//") || strings.TrimSpace(expression) == "" { 137 continue 138 } 139 140 key, value, err := parseEnvVar(expression) 141 if err != nil { 142 return nil, err 143 } 144 145 // update the store 146 kv[strings.TrimSpace(key)] = strings.TrimSpace(value) 147 } 148 return kv, nil 149 }