11package main
22
33import (
4+ "bufio"
5+ "bytes"
46 "fmt"
57 "os"
68 "runtime"
79 "strconv"
810 "strings"
11+ "unicode"
12+ "unicode/utf8"
913
1014 rspec "github.com/opencontainers/runtime-spec/specs-go"
1115 "github.com/opencontainers/runtime-tools/generate"
@@ -25,6 +29,7 @@ var generateFlags = []cli.Flag{
2529 cli.StringFlag {Name : "cwd" , Value : "/" , Usage : "current working directory for the process" },
2630 cli.BoolFlag {Name : "disable-oom-kill" , Usage : "disable OOM Killer" },
2731 cli.StringSliceFlag {Name : "env" , Usage : "add environment variable e.g. key=value" },
32+ cli.StringSliceFlag {Name : "env-file" , Usage : "read in a file of environment variables" },
2833 cli.IntFlag {Name : "gid" , Usage : "gid for the process" },
2934 cli.StringSliceFlag {Name : "gidmappings" , Usage : "add GIDMappings e.g HostID:ContainerID:Size" },
3035 cli.StringSliceFlag {Name : "groups" , Usage : "supplementary groups for the process" },
@@ -193,8 +198,12 @@ func setupSpec(g *generate.Generator, context *cli.Context) error {
193198 g .SetProcessArgs (context .StringSlice ("args" ))
194199 }
195200
196- if context .IsSet ("env" ) {
197- envs := context .StringSlice ("env" )
201+ {
202+ envs , err := readKVStrings (context .StringSlice ("env-file" ), context .StringSlice ("env" ))
203+ if err != nil {
204+ return err
205+ }
206+
198207 for _ , env := range envs {
199208 g .AddProcessEnv (env )
200209 }
@@ -664,3 +673,94 @@ func seccompSet(context *cli.Context, seccompFlag string, g *generate.Generator)
664673 }
665674 return nil
666675}
676+
677+ // readKVStrings reads a file of line terminated key=value pairs, and overrides any keys
678+ // present in the file with additional pairs specified in the override parameter
679+ //
680+ // This function is copied from github.com/docker/docker/runconfig/opts/parse.go
681+ func readKVStrings (files []string , override []string ) ([]string , error ) {
682+ envVariables := []string {}
683+ for _ , ef := range files {
684+ parsedVars , err := parseEnvFile (ef )
685+ if err != nil {
686+ return nil , err
687+ }
688+ envVariables = append (envVariables , parsedVars ... )
689+ }
690+ // parse the '-e' and '--env' after, to allow override
691+ envVariables = append (envVariables , override ... )
692+
693+ return envVariables , nil
694+ }
695+
696+ // parseEnvFile reads a file with environment variables enumerated by lines
697+ //
698+ // ``Environment variable names used by the utilities in the Shell and
699+ // Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
700+ // letters, digits, and the '_' (underscore) from the characters defined in
701+ // Portable Character Set and do not begin with a digit. *But*, other
702+ // characters may be permitted by an implementation; applications shall
703+ // tolerate the presence of such names.''
704+ // -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
705+ //
706+ // As of #16585, it's up to application inside docker to validate or not
707+ // environment variables, that's why we just strip leading whitespace and
708+ // nothing more.
709+ //
710+ // This function is copied from github.com/docker/docker/runconfig/opts/envfile.go
711+ func parseEnvFile (filename string ) ([]string , error ) {
712+ fh , err := os .Open (filename )
713+ if err != nil {
714+ return []string {}, err
715+ }
716+ defer fh .Close ()
717+
718+ lines := []string {}
719+ scanner := bufio .NewScanner (fh )
720+ currentLine := 0
721+ utf8bom := []byte {0xEF , 0xBB , 0xBF }
722+ for scanner .Scan () {
723+ scannedBytes := scanner .Bytes ()
724+ if ! utf8 .Valid (scannedBytes ) {
725+ return []string {}, fmt .Errorf ("env file %s contains invalid utf8 bytes at line %d: %v" , filename , currentLine + 1 , scannedBytes )
726+ }
727+ // We trim UTF8 BOM
728+ if currentLine == 0 {
729+ scannedBytes = bytes .TrimPrefix (scannedBytes , utf8bom )
730+ }
731+ // trim the line from all leading whitespace first
732+ line := strings .TrimLeftFunc (string (scannedBytes ), unicode .IsSpace )
733+ currentLine ++
734+ // line is not empty, and not starting with '#'
735+ if len (line ) > 0 && ! strings .HasPrefix (line , "#" ) {
736+ data := strings .SplitN (line , "=" , 2 )
737+
738+ // trim the front of a variable, but nothing else
739+ variable := strings .TrimLeft (data [0 ], whiteSpaces )
740+ if strings .ContainsAny (variable , whiteSpaces ) {
741+ return []string {}, ErrBadEnvVariable {fmt .Sprintf ("variable '%s' has white spaces" , variable )}
742+ }
743+
744+ if len (data ) > 1 {
745+
746+ // pass the value through, no trimming
747+ lines = append (lines , fmt .Sprintf ("%s=%s" , variable , data [1 ]))
748+ } else {
749+ // if only a pass-through variable is given, clean it up.
750+ lines = append (lines , fmt .Sprintf ("%s=%s" , strings .TrimSpace (line ), os .Getenv (line )))
751+ }
752+ }
753+ }
754+ return lines , scanner .Err ()
755+ }
756+
757+ var whiteSpaces = " \t "
758+
759+ // ErrBadEnvVariable typed error for bad environment variable
760+ type ErrBadEnvVariable struct {
761+ msg string
762+ }
763+
764+ func (e ErrBadEnvVariable ) Error () string {
765+ return fmt .Sprintf ("poorly formatted environment: %s" , e .msg )
766+ }
0 commit comments