diff --git a/go.mod b/go.mod index 94d6c97d4..9dac4ee12 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,6 @@ require ( github.com/google/uuid v1.6.0 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-plugin v1.7.0 - github.com/joho/godotenv v1.3.0 github.com/kopia/kopia v0.16.0 github.com/kubernetes-csi/external-snapshot-metadata v1.0.0 github.com/kubernetes-csi/external-snapshotter/client/v8 v8.4.0 diff --git a/go.sum b/go.sum index a563480e3..7a04b3057 100644 --- a/go.sum +++ b/go.sum @@ -270,8 +270,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/pkg/cmd/cli/debug/debug.go b/pkg/cmd/cli/debug/debug.go index 400eddde1..1d511979d 100644 --- a/pkg/cmd/cli/debug/debug.go +++ b/pkg/cmd/cli/debug/debug.go @@ -191,7 +191,7 @@ func runCrashd(o *option) error { if o.verbose { logrus.SetLevel(logrus.DebugLevel) } - return exec.Execute("velero-debug-collector", bytes.NewReader(scriptBytes), o.asCrashdArgMap()) + return exec.Execute("velero-debug-collector", bytes.NewReader(scriptBytes), o.asCrashdArgMap(), false) } func kubeconfigAndContext(fs *pflag.FlagSet) (string, string) { diff --git a/pkg/util/azure/util.go b/pkg/util/azure/util.go index e708d6ce3..e00fdc9a9 100644 --- a/pkg/util/azure/util.go +++ b/pkg/util/azure/util.go @@ -29,8 +29,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/joho/godotenv" "github.com/pkg/errors" + "github.com/vmware-tanzu/velero/pkg/util/dotenv" ) const ( @@ -68,7 +68,7 @@ func LoadCredentials(config map[string]string) (map[string]string, error) { } // put the credential file content into a map - creds, err := godotenv.Read(credFile) + creds, err := dotenv.Read(credFile) if err != nil { return nil, errors.Wrapf(err, "failed to read credentials from file %s", credFile) } diff --git a/pkg/util/dotenv/dotenv.go b/pkg/util/dotenv/dotenv.go new file mode 100644 index 000000000..23c0cda01 --- /dev/null +++ b/pkg/util/dotenv/dotenv.go @@ -0,0 +1,165 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dotenv + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +// Read parses dotenv-style files and returns merged key/value pairs. +func Read(filenames ...string) (map[string]string, error) { + filenames = filenamesOrDefault(filenames) + envMap := make(map[string]string) + + for _, filename := range filenames { + fileMap, err := readFile(filename) + if err != nil { + return nil, err + } + + for key, value := range fileMap { + envMap[key] = value + } + } + + return envMap, nil +} + +// Overload loads dotenv-style files into process env vars, overriding existing values. +func Overload(filenames ...string) error { + filenames = filenamesOrDefault(filenames) + + for _, filename := range filenames { + envMap, err := readFile(filename) + if err != nil { + return err + } + + for key, value := range envMap { + if err := os.Setenv(key, value); err != nil { + return err + } + } + } + + return nil +} + +func filenamesOrDefault(filenames []string) []string { + if len(filenames) == 0 { + return []string{".env"} + } + return filenames +} + +func readFile(filename string) (map[string]string, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + envMap := make(map[string]string) + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + line = stripInlineComment(line) + key, value, err := parseLine(line) + if err != nil { + return nil, err + } + envMap[key] = value + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return envMap, nil +} + +func stripInlineComment(line string) string { + inSingle := false + inDouble := false + for i, r := range line { + switch r { + case '\'': + if !inDouble { + inSingle = !inSingle + } + case '"': + if !inSingle { + inDouble = !inDouble + } + case '#': + if !inSingle && !inDouble { + return strings.TrimSpace(line[:i]) + } + } + } + return line +} + +func parseLine(line string) (string, string, error) { + if strings.HasPrefix(line, "export ") { + line = strings.TrimSpace(strings.TrimPrefix(line, "export ")) + } + + sep := strings.Index(line, "=") + colon := strings.Index(line, ":") + if sep == -1 || (colon != -1 && colon < sep) { + sep = colon + } + if sep == -1 { + return "", "", fmt.Errorf("invalid dotenv line: %q", line) + } + + key := strings.TrimSpace(line[:sep]) + rawValue := strings.TrimSpace(line[sep+1:]) + if key == "" { + return "", "", fmt.Errorf("invalid dotenv line: %q", line) + } + + value := parseValue(rawValue) + return key, value, nil +} + +func parseValue(value string) string { + if len(value) >= 2 { + if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) { + unquoted := strings.TrimSuffix(strings.TrimPrefix(value, `"`), `"`) + unquoted = strings.ReplaceAll(unquoted, `\n`, "\n") + unquoted = strings.ReplaceAll(unquoted, `\r`, "\r") + unquoted = strings.ReplaceAll(unquoted, `\\`, `\`) + unquoted = strings.ReplaceAll(unquoted, `\"`, `"`) + return unquoted + } + if strings.HasPrefix(value, "'") && strings.HasSuffix(value, "'") { + return strings.TrimSuffix(strings.TrimPrefix(value, "'"), "'") + } + } + + return value +} diff --git a/test/util/providers/azure_utils.go b/test/util/providers/azure_utils.go index 468dbfe40..7bd7e0bba 100644 --- a/test/util/providers/azure_utils.go +++ b/test/util/providers/azure_utils.go @@ -36,10 +36,10 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" - "github.com/joho/godotenv" "github.com/pkg/errors" "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" + "github.com/vmware-tanzu/velero/pkg/util/dotenv" . "github.com/vmware-tanzu/velero/test" ) @@ -127,7 +127,7 @@ func loadCredentialsIntoEnv(credentialsFile string) error { return nil } - if err := godotenv.Overload(credentialsFile); err != nil { + if err := dotenv.Overload(credentialsFile); err != nil { return errors.Wrapf(err, "error loading environment from credentials file (%s)", credentialsFile) } return nil