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  }