// Package config provides functions for reading and parsing the provider credentials json file. // It cleans nonstandard json features (comments and trailing commas), as well as replaces environment variable placeholders with // their environment variable equivalents. To reference an environment variable in your json file, simply use values in this format: // "key"="$ENV_VAR_NAME" package credsfile import ( "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/DisposaBoy/JsonConfigReader" "github.com/TomOnTime/utfutil" ) func quotedList(l []string) string { if len(l) == 0 { return "" } return `"` + strings.Join(l, `", "`) + `"` } func keysWithColons(list []string) []string { var r []string for _, k := range list { if strings.Contains(k, ":") { r = append(r, k) } } return r } // LoadProviderConfigs will open or execute the specified file name, and parse its contents. It will replace environment variables it finds if any value matches $[A-Za-z_-0-9]+ func LoadProviderConfigs(fname string) (map[string]map[string]string, error) { var results = map[string]map[string]string{} var dat []byte var err error if strings.HasPrefix(fname, "!") { dat, err = executeCredsFile(strings.TrimPrefix(fname, "!")) if err != nil { return nil, err } } else if strings.HasSuffix(fname, ".json") { // .json files are never executable (needed because in Windows WSL // all files are executable). dat, err = readCredsFile(fname) if err != nil { return nil, err } } else if isExecutable(fname) { dat, err = executeCredsFile(fname) if err != nil { return nil, err } } else { // no executable bit found nor marked as executable so read it in dat, err = readCredsFile(fname) if err != nil { return nil, err } } s := string(dat) r := JsonConfigReader.New(strings.NewReader(s)) err = json.NewDecoder(r).Decode(&results) if err != nil { return nil, fmt.Errorf("failed parsing provider credentials file %v: %v", fname, err) } if err = replaceEnvVars(results); err != nil { return nil, err } // For backwards compatibility, insert NONE and BIND entries if // they do not exist. These are the only providers that previously // did not require entries in creds.json prior to v4.0. if _, ok := results["none"]; !ok { results["none"] = map[string]string{"TYPE": "NONE"} } if _, ok := results["bind"]; !ok { results["bind"] = map[string]string{"TYPE": "BIND"} } return results, nil } func isExecutable(filename string) bool { if stat, statErr := os.Stat(filename); statErr == nil { if mode := stat.Mode(); mode&0111 == 0111 { return true } } return false } func readCredsFile(filename string) ([]byte, error) { dat, err := utfutil.ReadFile(filename, utfutil.POSIX) if err != nil { // no creds file is ok. Bind requires nothing for example. Individual providers will error if things not found. if os.IsNotExist(err) { fmt.Printf("INFO: Config file %q does not exist. Skipping.\n", filename) return []byte{}, nil } return nil, fmt.Errorf("failed reading provider credentials file %v: %v", filename, err) } return dat, nil } func executeCredsFile(filename string) ([]byte, error) { cmd := filename if !strings.HasPrefix(filename, "/") { // if the path doesn't start with `/` make sure we aren't relying on $PATH. cmd = strings.Join([]string{".", filename}, string(filepath.Separator)) } out, err := exec.Command(cmd).Output() return out, err } func replaceEnvVars(m map[string]map[string]string) error { for _, keys := range m { for k, v := range keys { if strings.HasPrefix(v, "$") { env := v[1:] newVal := os.Getenv(env) keys[k] = newVal } } } return nil }