@@ -37,15 +37,16 @@ var (
3737 ErrSignalFailed = errRun .Code ("signal_failed" ).ErrorPref ("error while propagating signal to process: %s" )
3838 ErrReadEnvDir = errRun .Code ("env_dir_read_error" ).ErrorPref ("could not read the environment directory: %s" )
3939 ErrReadEnvFile = errRun .Code ("env_file_read_error" ).ErrorPref ("could not read the environment file %s: %s" )
40- ErrEnvDirNotFound = errRun .Code ("env_dir_not_found " ).Error ( fmt . Sprintf ( "could not find specified environment. Make sure you have executed `%s set`." , ApplicationName ) )
40+ ErrReadDefaultEnvFile = errRun .Code ("default_env_file_read_error " ).ErrorPref ( "could not read default run env-file %s: %s" )
4141 ErrTemplate = errRun .Code ("invalid_template" ).ErrorPref ("could not parse template at line %d: %s" )
4242 ErrParsingTemplate = errRun .Code ("template_parsing_failed" ).ErrorPref ("error while processing template file '%s': %s" )
4343 ErrInvalidTemplateVar = errRun .Code ("invalid_template_var" ).ErrorPref ("template variable '%s' is invalid: template variables may only contain uppercase letters, digits, and the '_' (underscore) and are not allowed to start with a number" )
4444 ErrSecretsNotAllowedInKey = errRun .Code ("secret_in_key" ).Error ("secrets are not allowed in run template keys" )
4545)
4646
4747const (
48- maskString = "<redacted by SecretHub>"
48+ defaultEnvFile = "secrethub.env"
49+ maskString = "<redacted by SecretHub>"
4950 // templateVarEnvVarPrefix is used to prefix environment variables
5051 // that should be used as template variables.
5152 templateVarEnvVarPrefix = "SECRETHUB_VAR_"
@@ -58,9 +59,11 @@ const (
5859// defined with --envar or --env-file flags and secrets.yml files.
5960// The yml files write to .secretsenv/<env-name> when running the set command.
6061type RunCommand struct {
61- command []string
6262 io ui.IO
6363 osEnv []string
64+ readFile func (filename string ) ([]byte , error )
65+ osStat func (filename string ) (os.FileInfo , error )
66+ command []string
6467 envar map [string ]string
6568 envFile string
6669 templateVars map [string ]string
@@ -78,6 +81,8 @@ func NewRunCommand(io ui.IO, newClient newClientFunc) *RunCommand {
7881 return & RunCommand {
7982 io : io ,
8083 osEnv : os .Environ (),
84+ readFile : ioutil .ReadFile ,
85+ osStat : os .Stat ,
8186 envar : make (map [string ]string ),
8287 templateVars : make (map [string ]string ),
8388 newClient : newClient ,
@@ -112,66 +117,155 @@ func (cmd *RunCommand) Register(r command.Registerer) {
112117// Run reads files from the .secretsenv/<env-name> directory, sets them as environment variables and runs the given command.
113118// Note that the environment variables are only passed to the child process and not exported globally, which is nice.
114119func (cmd * RunCommand ) Run () error {
115- // Parse
116- envSources := []EnvSource {}
120+ environment , secrets , err := cmd .sourceEnvironment ()
121+ if err != nil {
122+ return err
123+ }
124+
125+ // This makes sure commands encapsulated in quotes also work.
126+ if len (cmd .command ) == 1 {
127+ cmd .command = strings .Split (cmd .command [0 ], " " )
128+ }
129+
130+ valuesToMask := make ([][]byte , 0 , len (secrets ))
131+ for _ , val := range secrets {
132+ if val != "" {
133+ valuesToMask = append (valuesToMask , []byte (val ))
134+ }
135+ }
136+
137+ maskedStdout := masker .NewMaskedWriter (cmd .io .Stdout (), valuesToMask , maskString , cmd .maskingTimeout )
138+ maskedStderr := masker .NewMaskedWriter (os .Stderr , valuesToMask , maskString , cmd .maskingTimeout )
139+
140+ command := exec .Command (cmd .command [0 ], cmd .command [1 :]... )
141+ command .Env = environment
142+ command .Stdin = os .Stdin
143+ if cmd .noMasking {
144+ command .Stdout = cmd .io .Stdout ()
145+ command .Stderr = os .Stderr
146+ } else {
147+ command .Stdout = maskedStdout
148+ command .Stderr = maskedStderr
149+
150+ go maskedStdout .Run ()
151+ go maskedStderr .Run ()
152+ }
153+
154+ err = command .Start ()
155+ if err != nil {
156+ return ErrStartFailed (err )
157+ }
158+
159+ done := make (chan bool , 1 )
160+
161+ // Pass all signals to child process
162+ signals := make (chan os.Signal , 1 )
163+ signal .Notify (signals )
164+
165+ go func () {
166+ select {
167+ case s := <- signals :
168+ err := command .Process .Signal (s )
169+ if err != nil && ! strings .Contains (err .Error (), "process already finished" ) {
170+ fmt .Fprintln (os .Stderr , ErrSignalFailed (err ))
171+ }
172+ case <- done :
173+ signal .Stop (signals )
174+ return
175+ }
176+ }()
117177
178+ commandErr := command .Wait ()
179+ done <- true
180+
181+ if ! cmd .noMasking {
182+ err = maskedStdout .Flush ()
183+ if err != nil {
184+ fmt .Fprintln (os .Stderr , err )
185+ }
186+ err = maskedStderr .Flush ()
187+ if err != nil {
188+ fmt .Fprintln (os .Stderr , err )
189+ }
190+ }
191+
192+ if commandErr != nil {
193+ // Check if the program exited with an error
194+ exitErr , ok := commandErr .(* exec.ExitError )
195+ if ok {
196+ waitStatus , ok := exitErr .Sys ().(syscall.WaitStatus )
197+ if ok {
198+ // Return the status code returned by the process
199+ os .Exit (waitStatus .ExitStatus ())
200+ return nil
201+ }
202+
203+ }
204+ return commandErr
205+ }
206+
207+ return nil
208+ }
209+
210+ // sourceEnvironment returns the environment of the subcommand, with all the secrets sourced
211+ // and the secret values that need to be masked.
212+ func (cmd * RunCommand ) sourceEnvironment () ([]string , []string , error ) {
118213 osEnv , passthroughEnv := parseKeyValueStringsToMap (cmd .osEnv )
119214
215+ envSources := []EnvSource {}
216+
120217 referenceEnv := newReferenceEnv (osEnv )
121218 envSources = append (envSources , referenceEnv )
122219
123220 // TODO: Validate the flags when parsing by implementing the Flag interface for EnvFlags.
124221 flagSource , err := NewEnvFlags (cmd .envar )
125222 if err != nil {
126- return err
223+ return nil , nil , err
127224 }
128225 envSources = append (envSources , flagSource )
129226
130227 if cmd .envFile == "" {
131- const defaultEnvFile = "secrethub.env"
132- _ , err := os .Stat (defaultEnvFile )
133- if err != nil {
134- if ! os .IsNotExist (err ) {
135- return fmt .Errorf ("could not read default run env-file %s: %s" , defaultEnvFile , err )
136- }
137- } else {
228+ _ , err := cmd .osStat (defaultEnvFile )
229+ if err == nil {
138230 cmd .envFile = defaultEnvFile
231+ } else if ! os .IsNotExist (err ) {
232+ return nil , nil , ErrReadDefaultEnvFile (defaultEnvFile , err )
139233 }
140234 }
141235
142236 if cmd .envFile != "" {
143237 templateVariableReader , err := newVariableReader (osEnv , cmd .templateVars )
144238 if err != nil {
145- return err
239+ return nil , nil , err
146240 }
147241
148242 if ! cmd .dontPromptMissingTemplateVar {
149243 templateVariableReader = newPromptMissingVariableReader (templateVariableReader , cmd .io )
150244 }
151245
152- raw , err := ioutil . ReadFile (cmd .envFile )
246+ raw , err := cmd . readFile (cmd .envFile )
153247 if err != nil {
154- return ErrCannotReadFile (cmd .envFile , err )
248+ return nil , nil , ErrCannotReadFile (cmd .envFile , err )
155249 }
156250
157251 parser , err := getTemplateParser (raw , cmd .templateVersion )
158252 if err != nil {
159- return err
253+ return nil , nil , err
160254 }
161255
162- envFile , err := ReadEnvFile (cmd .envFile , templateVariableReader , parser )
256+ envFile , err := ReadEnvFile (cmd .envFile , bytes . NewReader ( raw ), templateVariableReader , parser )
163257 if err != nil {
164- return err
258+ return nil , nil , err
165259 }
166260 envSources = append (envSources , envFile )
167261 }
168262
169263 envDir := filepath .Join (secretspec .SecretEnvPath , cmd .env )
170- _ , err = os . Stat (envDir )
264+ _ , err = cmd . osStat (envDir )
171265 if err == nil {
172266 dirSource , err := NewEnvDir (envDir )
173267 if err != nil {
174- return err
268+ return nil , nil , err
175269 }
176270 envSources = append (envSources , dirSource )
177271 }
@@ -193,7 +287,7 @@ func (cmd *RunCommand) Run() error {
193287 for path := range secrets {
194288 secret , err := secretReader .ReadSecret (path )
195289 if err != nil {
196- return err
290+ return nil , nil , err
197291 }
198292 secrets [path ] = secret
199293 }
@@ -203,7 +297,7 @@ func (cmd *RunCommand) Run() error {
203297 for _ , source := range envSources {
204298 pairs , err := source .Env (secrets , secretReader )
205299 if err != nil {
206- return err
300+ return nil , nil , err
207301 }
208302
209303 for key , value := range pairs {
@@ -215,7 +309,7 @@ func (cmd *RunCommand) Run() error {
215309 }
216310 }
217311
218- // Finally, source the remaining envars from the OS environment.
312+ // Source the remaining envars from the OS environment.
219313 for key , value := range osEnv {
220314 // Only set a variable if it wasn't set by a configured source.
221315 _ , found := environment [key ]
@@ -224,91 +318,10 @@ func (cmd *RunCommand) Run() error {
224318 }
225319 }
226320
227- // This makes sure commands encapsulated in quotes also work.
228- if len (cmd .command ) == 1 {
229- cmd .command = strings .Split (cmd .command [0 ], " " )
230- }
231-
232- values := secretReader .Values ()
321+ // Finally add the unparsed variables
322+ processedOsEnv := append (passthroughEnv , mapToKeyValueStrings (environment )... )
233323
234- valuesToMask := make ([][]byte , 0 , len (values ))
235- for _ , val := range values {
236- if val != "" {
237- valuesToMask = append (valuesToMask , []byte (val ))
238- }
239- }
240-
241- maskedStdout := masker .NewMaskedWriter (cmd .io .Stdout (), valuesToMask , maskString , cmd .maskingTimeout )
242- maskedStderr := masker .NewMaskedWriter (os .Stderr , valuesToMask , maskString , cmd .maskingTimeout )
243-
244- command := exec .Command (cmd .command [0 ], cmd .command [1 :]... )
245- command .Env = append (passthroughEnv , mapToKeyValueStrings (environment )... )
246- command .Stdin = os .Stdin
247- if cmd .noMasking {
248- command .Stdout = cmd .io .Stdout ()
249- command .Stderr = os .Stderr
250- } else {
251- command .Stdout = maskedStdout
252- command .Stderr = maskedStderr
253-
254- go maskedStdout .Run ()
255- go maskedStderr .Run ()
256- }
257-
258- err = command .Start ()
259- if err != nil {
260- return ErrStartFailed (err )
261- }
262-
263- done := make (chan bool , 1 )
264-
265- // Pass all signals to child process
266- signals := make (chan os.Signal , 1 )
267- signal .Notify (signals )
268-
269- go func () {
270- select {
271- case s := <- signals :
272- err := command .Process .Signal (s )
273- if err != nil && ! strings .Contains (err .Error (), "process already finished" ) {
274- fmt .Fprintln (os .Stderr , ErrSignalFailed (err ))
275- }
276- case <- done :
277- signal .Stop (signals )
278- return
279- }
280- }()
281-
282- commandErr := command .Wait ()
283- done <- true
284-
285- if ! cmd .noMasking {
286- err = maskedStdout .Flush ()
287- if err != nil {
288- fmt .Fprintln (os .Stderr , err )
289- }
290- err = maskedStderr .Flush ()
291- if err != nil {
292- fmt .Fprintln (os .Stderr , err )
293- }
294- }
295-
296- if commandErr != nil {
297- // Check if the program exited with an error
298- exitErr , ok := commandErr .(* exec.ExitError )
299- if ok {
300- waitStatus , ok := exitErr .Sys ().(syscall.WaitStatus )
301- if ok {
302- // Return the status code returned by the process
303- os .Exit (waitStatus .ExitStatus ())
304- return nil
305- }
306-
307- }
308- return commandErr
309- }
310-
311- return nil
324+ return processedOsEnv , secretReader .Values (), nil
312325}
313326
314327// mapToKeyValueStrings converts a map to a slice of key=value pairs.
@@ -412,12 +425,8 @@ func (t envTemplate) Secrets() []string {
412425}
413426
414427// ReadEnvFile reads and parses a .env file.
415- func ReadEnvFile (filepath string , varReader tpl.VariableReader , parser tpl.Parser ) (EnvFile , error ) {
416- r , err := os .Open (filepath )
417- if err != nil {
418- return EnvFile {}, ErrCannotReadFile (filepath , err )
419- }
420- env , err := NewEnv (r , varReader , parser )
428+ func ReadEnvFile (filepath string , reader io.Reader , varReader tpl.VariableReader , parser tpl.Parser ) (EnvFile , error ) {
429+ env , err := NewEnv (reader , varReader , parser )
421430 if err != nil {
422431 return EnvFile {}, ErrParsingTemplate (filepath , err )
423432 }
@@ -667,13 +676,6 @@ type EnvDir map[string]string
667676// NewEnvDir sources environment variables from files in a given directory,
668677// using the file name as key and contents as value.
669678func NewEnvDir (path string ) (EnvDir , error ) {
670- _ , err := os .Stat (path )
671- if os .IsNotExist (err ) {
672- return nil , ErrEnvDirNotFound
673- } else if err != nil {
674- return nil , ErrReadEnvDir (err )
675- }
676-
677679 files , err := ioutil .ReadDir (path )
678680 if err != nil {
679681 return nil , ErrReadEnvDir (err )
0 commit comments