github.com/containerd/nerdctl@v1.7.7/pkg/flagutil/flagutil.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package flagutil 18 19 import ( 20 "bufio" 21 "errors" 22 "fmt" 23 "os" 24 "strings" 25 26 "github.com/containerd/nerdctl/pkg/strutil" 27 ) 28 29 // ReplaceOrAppendEnvValues returns the defaults with the overrides either 30 // replaced by env key or appended to the list 31 // FYI: https://github.com/containerd/containerd/blob/698622b89a053294593b9b5a363efff7715e9394/oci/spec_opts.go#L186-L222 32 // defaults should have valid `k=v` strings. 33 // overrides may have the following formats: `k=v` (override k), `k=` (emptify k), `k` (remove k). 34 func ReplaceOrAppendEnvValues(defaults, overrides []string) []string { 35 cache := make(map[string]int, len(defaults)) 36 results := make([]string, 0, len(defaults)) 37 for i, e := range defaults { 38 k, _, _ := strings.Cut(e, "=") 39 results = append(results, e) 40 cache[k] = i 41 } 42 43 for _, value := range overrides { 44 // Values w/o = means they want this env to be removed/unset. 45 k, _, ok := strings.Cut(value, "=") 46 if !ok { 47 if i, exists := cache[k]; exists { 48 results[i] = "" // Used to indicate it should be removed 49 } 50 continue 51 } 52 53 // Just do a normal set/update 54 if i, exists := cache[k]; exists { 55 results[i] = value 56 } else { 57 results = append(results, value) 58 } 59 } 60 61 // Now remove all entries that we want to "unset" 62 for i := 0; i < len(results); i++ { 63 if results[i] == "" { 64 results = append(results[:i], results[i+1:]...) 65 i-- 66 } 67 } 68 69 return results 70 } 71 72 func parseEnvVars(paths []string) ([]string, error) { 73 vars := make([]string, 0) 74 for _, path := range paths { 75 f, err := os.Open(path) 76 if err != nil { 77 return nil, fmt.Errorf("failed to open env file %s: %w", path, err) 78 } 79 defer f.Close() 80 81 sc := bufio.NewScanner(f) 82 for sc.Scan() { 83 line := strings.TrimSpace(sc.Text()) 84 // skip comment lines and empty line 85 if len(line) == 0 || strings.HasPrefix(line, "#") { 86 continue 87 } 88 vars = append(vars, line) 89 } 90 if err = sc.Err(); err != nil { 91 return nil, err 92 } 93 } 94 return vars, nil 95 } 96 97 func withOSEnv(envs []string) ([]string, error) { 98 newEnvs := make([]string, len(envs)) 99 100 // from https://github.com/docker/cli/blob/v22.06.0-beta.0/opts/env.go#L18 101 getEnv := func(val string) (string, error) { 102 arr := strings.SplitN(val, "=", 2) 103 if arr[0] == "" { 104 return "", errors.New("invalid environment variable: " + val) 105 } 106 if len(arr) > 1 { 107 return val, nil 108 } 109 if envVal, ok := os.LookupEnv(arr[0]); ok { 110 return arr[0] + "=" + envVal, nil 111 } 112 return val, nil 113 } 114 for i := range envs { 115 env, err := getEnv(envs[i]) 116 if err != nil { 117 return nil, err 118 } 119 newEnvs[i] = env 120 } 121 122 return newEnvs, nil 123 } 124 125 // MergeEnvFileAndOSEnv combines environment variables from `--env-file` and `--env`. 126 // Pass an empty slice if any arg is not used. 127 func MergeEnvFileAndOSEnv(envFile []string, env []string) ([]string, error) { 128 var envs []string 129 var err error 130 131 if envFiles := strutil.DedupeStrSlice(envFile); len(envFiles) > 0 { 132 envs, err = parseEnvVars(envFiles) 133 if err != nil { 134 return nil, err 135 } 136 } 137 138 if env := strutil.DedupeStrSlice(env); len(env) > 0 { 139 envs = append(envs, env...) 140 } 141 142 if envs, err = withOSEnv(envs); err != nil { 143 return nil, err 144 } 145 146 return envs, nil 147 }