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" },
@@ -196,8 +201,12 @@ func setupSpec(g *generate.Generator, context *cli.Context) error {
196201 g .SetProcessArgs (context .StringSlice ("args" ))
197202 }
198203
199- if context .IsSet ("env" ) {
200- envs := context .StringSlice ("env" )
204+ {
205+ envs , err := readKVStrings (context .StringSlice ("env-file" ), context .StringSlice ("env" ))
206+ if err != nil {
207+ return err
208+ }
209+
201210 for _ , env := range envs {
202211 g .AddProcessEnv (env )
203212 }
@@ -711,3 +720,94 @@ func seccompSet(context *cli.Context, seccompFlag string, g *generate.Generator)
711720 }
712721 return nil
713722}
723+
724+ // readKVStrings reads a file of line terminated key=value pairs, and overrides any keys
725+ // present in the file with additional pairs specified in the override parameter
726+ //
727+ // This function is copied from github.com/docker/docker/runconfig/opts/parse.go
728+ func readKVStrings (files []string , override []string ) ([]string , error ) {
729+ envVariables := []string {}
730+ for _ , ef := range files {
731+ parsedVars , err := parseEnvFile (ef )
732+ if err != nil {
733+ return nil , err
734+ }
735+ envVariables = append (envVariables , parsedVars ... )
736+ }
737+ // parse the '-e' and '--env' after, to allow override
738+ envVariables = append (envVariables , override ... )
739+
740+ return envVariables , nil
741+ }
742+
743+ // parseEnvFile reads a file with environment variables enumerated by lines
744+ //
745+ // ``Environment variable names used by the utilities in the Shell and
746+ // Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
747+ // letters, digits, and the '_' (underscore) from the characters defined in
748+ // Portable Character Set and do not begin with a digit. *But*, other
749+ // characters may be permitted by an implementation; applications shall
750+ // tolerate the presence of such names.''
751+ // -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
752+ //
753+ // As of #16585, it's up to application inside docker to validate or not
754+ // environment variables, that's why we just strip leading whitespace and
755+ // nothing more.
756+ //
757+ // This function is copied from github.com/docker/docker/runconfig/opts/envfile.go
758+ func parseEnvFile (filename string ) ([]string , error ) {
759+ fh , err := os .Open (filename )
760+ if err != nil {
761+ return []string {}, err
762+ }
763+ defer fh .Close ()
764+
765+ lines := []string {}
766+ scanner := bufio .NewScanner (fh )
767+ currentLine := 0
768+ utf8bom := []byte {0xEF , 0xBB , 0xBF }
769+ for scanner .Scan () {
770+ scannedBytes := scanner .Bytes ()
771+ if ! utf8 .Valid (scannedBytes ) {
772+ return []string {}, fmt .Errorf ("env file %s contains invalid utf8 bytes at line %d: %v" , filename , currentLine + 1 , scannedBytes )
773+ }
774+ // We trim UTF8 BOM
775+ if currentLine == 0 {
776+ scannedBytes = bytes .TrimPrefix (scannedBytes , utf8bom )
777+ }
778+ // trim the line from all leading whitespace first
779+ line := strings .TrimLeftFunc (string (scannedBytes ), unicode .IsSpace )
780+ currentLine ++
781+ // line is not empty, and not starting with '#'
782+ if len (line ) > 0 && ! strings .HasPrefix (line , "#" ) {
783+ data := strings .SplitN (line , "=" , 2 )
784+
785+ // trim the front of a variable, but nothing else
786+ variable := strings .TrimLeft (data [0 ], whiteSpaces )
787+ if strings .ContainsAny (variable , whiteSpaces ) {
788+ return []string {}, ErrBadEnvVariable {fmt .Sprintf ("variable '%s' has white spaces" , variable )}
789+ }
790+
791+ if len (data ) > 1 {
792+
793+ // pass the value through, no trimming
794+ lines = append (lines , fmt .Sprintf ("%s=%s" , variable , data [1 ]))
795+ } else {
796+ // if only a pass-through variable is given, clean it up.
797+ lines = append (lines , fmt .Sprintf ("%s=%s" , strings .TrimSpace (line ), os .Getenv (line )))
798+ }
799+ }
800+ }
801+ return lines , scanner .Err ()
802+ }
803+
804+ var whiteSpaces = " \t "
805+
806+ // ErrBadEnvVariable typed error for bad environment variable
807+ type ErrBadEnvVariable struct {
808+ msg string
809+ }
810+
811+ func (e ErrBadEnvVariable ) Error () string {
812+ return fmt .Sprintf ("poorly formatted environment: %s" , e .msg )
813+ }
0 commit comments