dnscontrol/pkg/credsfile/providerConfig.go
Tom Limoncelli 9e6d642e35
NEW FEATURE: Moving provider TYPE from dnsconfig.js to creds.json (#1500)
Fixes https://github.com/StackExchange/dnscontrol/issues/1457

* New-style creds.json implememented backwards compatible

* Update tests

* Update docs

* Assume new-style TYPE
2022-05-08 14:23:45 -04:00

134 lines
3.6 KiB
Go

// 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
}