Skip to content

Commit

Permalink
Merge pull request #139 from Infisical/cli-multi-command
Browse files Browse the repository at this point in the history
Cli multi command support
  • Loading branch information
maidul98 authored Dec 20, 2022
2 parents 3ba62d1 + b09ae05 commit 2bff7bb
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 18 deletions.
97 changes: 83 additions & 14 deletions cli/packages/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"os/signal"
"runtime"
"strings"
"syscall"

Expand All @@ -19,12 +20,38 @@ import (

// runCmd represents the run command
var runCmd = &cobra.Command{
Example: `
infisical run --env=dev -- npm run dev
infisical run --command "first-command && second-command; more-commands..."
`,
Use: "run [any infisical run command flags] -- [your application start command]",
Short: "Used to inject environments variables into your application process",
DisableFlagsInUseLine: true,
Example: "infisical run --env=prod -- npm run dev",
Args: cobra.MinimumNArgs(1),
PreRun: toggleDebug,
Args: func(cmd *cobra.Command, args []string) error {
// Check if the --command flag has been set
commandFlagSet := cmd.Flags().Changed("command")

// If the --command flag has been set, check if a value was provided
if commandFlagSet {
command := cmd.Flag("command").Value.String()
if command == "" {
return fmt.Errorf("you need to provide a command after the flag --command")
}

// If the --command flag has been set, args should not be provided
if len(args) > 0 {
return fmt.Errorf("you cannot set any arguments after --command flag. --command only takes a string command")
}
} else {
// If the --command flag has not been set, at least one arg should be provided
if len(args) == 0 {
return fmt.Errorf("at least one argument is required after the run command, received %d", len(args))
}
}

return nil
},
Run: func(cmd *cobra.Command, args []string) {
envName, err := cmd.Flags().GetString("env")
if err != nil {
Expand Down Expand Up @@ -54,10 +81,23 @@ var runCmd = &cobra.Command{
}

if shouldExpandSecrets {
secretsWithSubstitutions := util.SubstituteSecrets(secrets)
execCmd(args[0], args[1:], secretsWithSubstitutions)
secrets = util.SubstituteSecrets(secrets)
}

if cmd.Flags().Changed("command") {
command := cmd.Flag("command").Value.String()
err = executeMultipleCommandWithEnvs(command, secrets)
if err != nil {
log.Errorf("Something went wrong when executing your command [error=%s]", err)
return
}
} else {
execCmd(args[0], args[1:], secrets)
err = executeSingleCommandWithEnvs(args, secrets)
if err != nil {
log.Errorf("Something went wrong when executing your command [error=%s]", err)
return
}
return
}

},
Expand All @@ -68,22 +108,51 @@ func init() {
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from")
runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")")
}

// Credit: inspired by AWS Valut
func execCmd(command string, args []string, envs []models.SingleEnvironmentVariable) error {
numberOfSecretsInjected := fmt.Sprintf("\u2713 Injected %v Infisical secrets into your application process successfully", len(envs))

// Will execute a single command and pass in the given secrets into the process
func executeSingleCommandWithEnvs(args []string, secrets []models.SingleEnvironmentVariable) error {
command := args[0]
argsForCommand := args[1:]
numberOfSecretsInjected := fmt.Sprintf("\u2713 Injected %v Infisical secrets into your application process successfully", len(secrets))
log.Infof("\x1b[%dm%s\x1b[0m", 32, numberOfSecretsInjected)
log.Debugf("executing command: %s %s \n", command, strings.Join(args, " "))
log.Debugln("Secrets injected:", envs)
log.Debugf("executing command: %s %s \n", command, strings.Join(argsForCommand, " "))
log.Debugln("Secrets injected:", secrets)

cmd := exec.Command(command, argsForCommand...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = getAllEnvs(secrets)

return execCmd(cmd)
}

func executeMultipleCommandWithEnvs(fullCommand string, secrets []models.SingleEnvironmentVariable) error {
shell := [2]string{"sh", "-c"}
if runtime.GOOS == "windows" {
shell = [2]string{"cmd", "/C"}
} else {
shell[0] = os.Getenv("SHELL")
}

cmd := exec.Command(command, args...)
cmd := exec.Command(shell[0], shell[1], fullCommand)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = getAllEnvs(envs)
cmd.Env = getAllEnvs(secrets)

numberOfSecretsInjected := fmt.Sprintf("\u2713 Injected %v Infisical secrets into your application process successfully", len(secrets))
log.Infof("\x1b[%dm%s\x1b[0m", 32, numberOfSecretsInjected)
log.Debugf("executing command: %s %s %s \n", shell[0], shell[1], fullCommand)
log.Debugln("Secrets injected:", secrets)

return execCmd(cmd)
}

// Credit: inspired by AWS Valut
func execCmd(cmd *exec.Cmd) error {
sigChannel := make(chan os.Signal, 1)
signal.Notify(sigChannel)

Expand All @@ -100,7 +169,7 @@ func execCmd(command string, args []string, envs []models.SingleEnvironmentVaria

if err := cmd.Wait(); err != nil {
_ = cmd.Process.Signal(os.Kill)
return fmt.Errorf("Failed to wait for command termination: %v", err)
return fmt.Errorf("failed to wait for command termination: %v", err)
}

waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus)
Expand Down
25 changes: 21 additions & 4 deletions docs/cli/commands/run.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,25 @@
title: "infisical run"
---

```bash
infisical run [options] -- [your application start command]
```
<Tabs>
<Tab title="Single command">
```bash
infisical run [options] -- [your application start command]

# Example
infisical run [options] -- npm run dev
```
</Tab>

<Tab title="Chained commands">
```bash
infisical run [options] --command [string command]
# Example
infisical run [options] --command "npm run bootstrap && npm run dev start; other-bash-command"
```
</Tab>
</Tabs>

## Description

Expand All @@ -15,5 +31,6 @@ Inject environment variables from the platform into an application process.
| Option | Description | Default value |
| -------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | `None` |
| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | None |
| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` |
| `--command` | Pass secrets into chained commands (e.g., `"first-command && second-command; more-commands..."`) | None |

0 comments on commit 2bff7bb

Please sign in to comment.