Skip to content

Commit

Permalink
Merge pull request #20256 from Luap99/revert-env
Browse files Browse the repository at this point in the history
Revert --env-file changes
  • Loading branch information
openshift-ci[bot] authored Oct 5, 2023
2 parents 46ca057 + 1b3cedb commit 4a67d22
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 430 deletions.
6 changes: 1 addition & 5 deletions docs/source/markdown/options/env-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,4 @@
####> are applicable to all of those.
#### **--env-file**=*file*

Read the environment variables from the file, supporting prefix matching: `KEY*`, as well as multiline values in double quotes and single quotes, but not multiline values in backticks.
The env-file will ignore comments and empty lines. And spaces or tabs before and after the KEY.
If an invalid value is encountered, such as only an `=` sign, it will be skipped. If it is a prefix match (`KEY*`), all environment variables starting with KEY on the host machine will be loaded.
If it is only KEY (`KEY`), the KEY environment variable on the host machine will be loaded.
Compatible with the `export` syntax in **dotenv**, such as: `export KEY=bar`.
Read in a line-delimited file of environment variables.
142 changes: 16 additions & 126 deletions pkg/env/env.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@
// Package for processing environment variables.
package env

// TODO: we need to add tests for this package.

import (
"bufio"
"fmt"
"io"
"os"
"strings"

"github.com/containers/storage/pkg/regexp"
)

var (
// Form: https://github.com/motdotla/dotenv/blob/aa03dcad1002027390dac1e8d96ac236274de354/lib/main.js#L9C76-L9C76
// (?:export\s+)?([\w.-]+) match key
// ([\w.%-]+)(\s*[=|*]\s*?|:\s+?) match separator
// Remaining match value
// e.g. KEY=VALUE => KEY, =, VALUE
//
// KEY= => KEY, =, ""
// KEY* => KEY, *, ""
// KEY*=1 => KEY, *, =1
lineRegexp = regexp.Delayed(
`(?m)(?:^|^)\s*(?:export\s+)?([\w.%-]+)(\s*[=|*]\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*` +
"`(?:\\`|[^`])*`" + `|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)`,
)
onlyKeyRegexp = regexp.Delayed(`^[\w.-]+$`)
)

const whiteSpaces = " \t"
Expand Down Expand Up @@ -95,120 +79,26 @@ func ParseFile(path string) (_ map[string]string, err error) {
}
defer fh.Close()

content, err := io.ReadAll(fh)
if err != nil {
return nil, err
}

// replace all \r\n and \r with \n
text := strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(string(content))
if err := parseEnv(env, text); err != nil {
return nil, err
}

return env, nil
}

// parseEnv parse the given content into env format
//
// @example: parseEnv(env, "#comment") => nil
// @example: parseEnv(env, "") => nil
// @example: parseEnv(env, "KEY=FOO") => nil
// @example: parseEnv(env, "KEY") => nil
func parseEnv(env map[string]string, content string) error {
m := envMatch(content)

for _, match := range m {
key := match[1]
separator := strings.Trim(match[2], whiteSpaces)
value := match[3]

if strings.Contains(value, "\n") {
if strings.HasPrefix(value, "`") {
return fmt.Errorf("only support multi-line environment variables surrounded by "+
"double quotation marks or single quotation marks. invalid variable: %q", match[0])
}

// In the case of multi-line values, we need to remove the surrounding " '
value = strings.Trim(value, "\"'")
}

// KEY*=1 => KEY, *, =1 => KEY*, =, 1
if separator == "*" && strings.HasPrefix(value, "=") {
key += "*"
separator = "="
value = strings.TrimPrefix(value, "=")
}

switch separator {
case "=":
// KEY=
if value == "" {
if val, ok := os.LookupEnv(key); ok {
env[key] = val
}
} else {
env[key] = value
}
case "*":
for _, e := range os.Environ() {
part := strings.SplitN(e, "=", 2)
if len(part) < 2 {
continue
}
if strings.HasPrefix(part[0], key) {
env[part[0]] = part[1]
}
scanner := bufio.NewScanner(fh)
for scanner.Scan() {
// trim the line from all leading whitespace first
line := strings.TrimLeft(scanner.Text(), whiteSpaces)
// line is not empty, and not starting with '#'
if len(line) > 0 && !strings.HasPrefix(line, "#") {
if err := parseEnv(env, line); err != nil {
return nil, err
}
}
}
return nil
}

func envMatch(content string) [][]string {
m := lineRegexp.FindAllStringSubmatch(content, -1)

// KEY => KEY, =, ""
// Due to the above regex pattern, it will skip cases where only KEY is present (e.g., foo).
// However, in our requirement, this situation is equivalent to foo=(i.e., "foo" == "foo=").
// Therefore, we need to perform additional processing.
// The reason for needing to support this scenario is that we need to consider: `podman run -e CI -e USERNAME`.
{
noMatched := lineRegexp.ReplaceAllString(content, "")
nl := strings.Split(noMatched, "\n")
for _, key := range nl {
key := strings.Trim(key, whiteSpaces)
if key == "" {
continue
}
if onlyKeyRegexp.MatchString(key) {
m = append(m, []string{key, key, "=", ""})
}
}
}

return m
return env, scanner.Err()
}

// parseEnvWithSlice parsing a set of Env variables from a slice of strings
// because the majority of shell interpreters discard double quotes and single quotes,
// for example: podman run -e K='V', when passed into a program, it will become: K=V.
// This can lead to unexpected issues, as discussed in this link: https://github.com/containers/podman/pull/19096#issuecomment-1670164724.
//
// parseEnv method will discard all comments (#) that are not wrapped in quotation marks,
// so it cannot be used to parse env variables obtained from the command line.
//
// @example: parseEnvWithSlice(env, "KEY=FOO") => KEY: FOO
// @example: parseEnvWithSlice(env, "KEY") => KEY: ""
// @example: parseEnvWithSlice(env, "KEY=") => KEY: ""
// @example: parseEnvWithSlice(env, "KEY=FOO=BAR") => KEY: FOO=BAR
// @example: parseEnvWithSlice(env, "KEY=FOO#BAR") => KEY: FOO#BAR
func parseEnvWithSlice(env map[string]string, content string) error {
data := strings.SplitN(content, "=", 2)
func parseEnv(env map[string]string, line string) error {
data := strings.SplitN(line, "=", 2)

// catch invalid variables such as "=" or "=A"
if data[0] == "" {
return fmt.Errorf("invalid variable: %q", content)
return fmt.Errorf("invalid variable: %q", line)
}
// trim the front of a variable, but nothing else
name := strings.TrimLeft(data[0], whiteSpaces)
Expand Down
Loading

0 comments on commit 4a67d22

Please sign in to comment.