mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 18:27:19 +00:00
Merge pull request #2352 from Infisical/revert-2341-daniel/cli-run-watch-mode
Revert "feat(cli): `run` watch mode"
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@ require (
|
|||||||
github.com/gitleaks/go-gitdiff v0.8.0
|
github.com/gitleaks/go-gitdiff v0.8.0
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/infisical/go-sdk v0.3.3
|
github.com/infisical/go-sdk v0.3.3
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.18
|
||||||
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
||||||
github.com/muesli/mango-cobra v1.2.0
|
github.com/muesli/mango-cobra v1.2.0
|
||||||
github.com/muesli/reflow v0.3.0
|
github.com/muesli/reflow v0.3.0
|
||||||
|
|||||||
@@ -297,8 +297,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
|||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
|||||||
+146
-268
@@ -4,14 +4,13 @@ Copyright (c) 2023 Infisical Inc.
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Infisical/infisical-merge/packages/models"
|
"github.com/Infisical/infisical-merge/packages/models"
|
||||||
"github.com/Infisical/infisical-merge/packages/util"
|
"github.com/Infisical/infisical-merge/packages/util"
|
||||||
@@ -21,9 +20,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrManualSignalInterrupt = errors.New("signal: interrupt")
|
|
||||||
var WaitGroup = new(sync.WaitGroup)
|
|
||||||
|
|
||||||
// runCmd represents the run command
|
// runCmd represents the run command
|
||||||
var runCmd = &cobra.Command{
|
var runCmd = &cobra.Command{
|
||||||
Example: `
|
Example: `
|
||||||
@@ -36,8 +32,6 @@ var runCmd = &cobra.Command{
|
|||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
// Check if the --command flag has been set
|
// Check if the --command flag has been set
|
||||||
commandFlagSet := cmd.Flags().Changed("command")
|
commandFlagSet := cmd.Flags().Changed("command")
|
||||||
watchIntervalFlagSet := cmd.Flags().Changed("watch-interval")
|
|
||||||
watchFlagSet := cmd.Flags().Changed("watch")
|
|
||||||
|
|
||||||
// If the --command flag has been set, check if a value was provided
|
// If the --command flag has been set, check if a value was provided
|
||||||
if commandFlagSet {
|
if commandFlagSet {
|
||||||
@@ -57,20 +51,6 @@ var runCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the --watch flag has been set, the --watch-interval flag should also be set
|
|
||||||
if watchFlagSet && watchIntervalFlagSet {
|
|
||||||
// Ensure that the --watch-interval flag is set to a positive integer, and is at least 5 seconds
|
|
||||||
|
|
||||||
watchInterval, err := cmd.Flags().GetInt("watch-interval")
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Unable to parse flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
if watchInterval < 5 {
|
|
||||||
return fmt.Errorf("watch interval must be at least 5 seconds, you passed %d seconds", watchInterval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
@@ -97,30 +77,11 @@ var runCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
command, err := cmd.Flags().GetString("command")
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Unable to parse flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Unable to parse flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
secretOverriding, err := cmd.Flags().GetBool("secret-overriding")
|
secretOverriding, err := cmd.Flags().GetBool("secret-overriding")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
watchMode, err := cmd.Flags().GetBool("watch")
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Unable to parse flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
watchModeInterval, err := cmd.Flags().GetInt("watch-interval")
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Unable to parse flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldExpandSecrets, err := cmd.Flags().GetBool("expand")
|
shouldExpandSecrets, err := cmd.Flags().GetBool("expand")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
@@ -155,217 +116,16 @@ var runCmd = &cobra.Command{
|
|||||||
Recursive: recursive,
|
Recursive: recursive,
|
||||||
}
|
}
|
||||||
|
|
||||||
injectableEnvironment, err := createInjectableEnvironment(request, projectConfigDir, secretOverriding, shouldExpandSecrets, token)
|
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
|
||||||
if err != nil {
|
request.InfisicalToken = token.Token
|
||||||
util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid")
|
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
|
||||||
|
request.UniversalAuthAccessToken = token.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msgf("injecting the following environment variables into shell: %v", injectableEnvironment.Variables)
|
|
||||||
|
|
||||||
Telemetry.CaptureEvent("cli-command:run",
|
|
||||||
posthog.NewProperties().
|
|
||||||
Set("secretsCount", injectableEnvironment.SecretsCount).
|
|
||||||
Set("environment", environmentName).
|
|
||||||
Set("isUsingServiceToken", token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER).
|
|
||||||
Set("isUsingUniversalAuthToken", token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER).
|
|
||||||
Set("single-command", strings.Join(args, " ")).
|
|
||||||
Set("multi-command", cmd.Flag("command").Value.String()).
|
|
||||||
Set("version", util.CLI_VERSION))
|
|
||||||
|
|
||||||
executeSpecifiedCommand(command, args, watchMode, watchModeInterval, request, projectConfigDir, shouldExpandSecrets, secretOverriding, token)
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func executeSpecifiedCommand(commandFlag string, args []string, watchMode bool, watchModeInterval int, request models.GetAllSecretsParameters, projectConfigDir string, expandSecrets bool, secretOverriding bool, token *models.TokenDetails) {
|
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
var err error
|
|
||||||
var lastSecretsFetch time.Time
|
|
||||||
var lastUpdateEvent time.Time
|
|
||||||
var watchMutex sync.Mutex
|
|
||||||
var processMutex sync.Mutex
|
|
||||||
var beingTerminated = false
|
|
||||||
var currentETag string
|
|
||||||
|
|
||||||
startProcess := func(environment models.InjectableEnvironmentResult) {
|
|
||||||
currentETag = environment.ETag
|
|
||||||
secretsFetchedAt := time.Now()
|
|
||||||
if secretsFetchedAt.After(lastSecretsFetch) {
|
|
||||||
lastSecretsFetch = secretsFetchedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldRestartProcess := cmd != nil
|
|
||||||
// terminate the old process before starting a new one
|
|
||||||
if shouldRestartProcess {
|
|
||||||
beingTerminated = true
|
|
||||||
|
|
||||||
log.Debug().Msgf(color.HiMagentaString("[HOT RELOAD] Sending SIGTERM to PID %d", cmd.Process.Pid))
|
|
||||||
if e := cmd.Process.Signal(syscall.SIGTERM); e != nil {
|
|
||||||
log.Error().Err(e).Msg(color.HiMagentaString("[HOT RELOAD] Failed to send SIGTERM"))
|
|
||||||
}
|
|
||||||
// wait up to 10 sec for the process to exit
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
if !util.IsProcessRunning(cmd.Process) {
|
|
||||||
// process has been killed so we break out
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i == 5 {
|
|
||||||
log.Debug().Msg(color.HiMagentaString("[HOT RELOAD] Still waiting for process exit status"))
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SIGTERM may not work on Windows so we try SIGKILL
|
|
||||||
if util.IsProcessRunning(cmd.Process) {
|
|
||||||
log.Debug().Msg(color.HiMagentaString("[HOT RELOAD] Process still hasn't fully exited, attempting SIGKILL"))
|
|
||||||
if e := cmd.Process.Kill(); e != nil {
|
|
||||||
log.Error().Err(e).Msg(color.HiMagentaString("[HOT RELOAD] Failed to send SIGKILL"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
processMutex.Lock()
|
|
||||||
|
|
||||||
if lastUpdateEvent.After(secretsFetchedAt) {
|
|
||||||
processMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beingTerminated = false
|
|
||||||
WaitGroup.Add(1)
|
|
||||||
|
|
||||||
if shouldRestartProcess {
|
|
||||||
log.Info().Msg(color.HiMagentaString("[HOT RELOAD] Environment changes detected. Reloading process..."))
|
|
||||||
}
|
|
||||||
|
|
||||||
// start the process
|
|
||||||
log.Info().Msgf(color.GreenString("Injecting %v Infisical secrets into your application process", environment.SecretsCount))
|
|
||||||
cmd, err = util.RunCommand(commandFlag, args, environment.Variables)
|
|
||||||
if err != nil {
|
|
||||||
defer WaitGroup.Done()
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer processMutex.Unlock()
|
|
||||||
defer WaitGroup.Done()
|
|
||||||
|
|
||||||
exitCode, err := WaitForExitCommand(cmd)
|
|
||||||
|
|
||||||
// ignore errors if we are being terminated
|
|
||||||
if !beingTerminated {
|
|
||||||
if err != nil {
|
|
||||||
if strings.HasPrefix(err.Error(), "exec") || strings.HasPrefix(err.Error(), "fork/exec") {
|
|
||||||
log.Error().Err(err).Msg("Failed to execute command")
|
|
||||||
}
|
|
||||||
if err.Error() != ErrManualSignalInterrupt.Error() {
|
|
||||||
log.Error().Err(err).Msg("Process exited with error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(exitCode)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
initialEnvironment, err := createInjectableEnvironment(request, projectConfigDir, secretOverriding, expandSecrets, token)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Failed to fetch secrets")
|
|
||||||
}
|
|
||||||
startProcess(initialEnvironment)
|
|
||||||
recheckSecretsChannel := make(chan bool, 1)
|
|
||||||
|
|
||||||
// this is the only logic strictly related to watch mode, the rest is shared with non-watch mode
|
|
||||||
if watchMode {
|
|
||||||
log.Info().Msg(color.HiMagentaString("[HOT RELOAD] Watching for secret changes..."))
|
|
||||||
|
|
||||||
// a simple goroutine that triggers the recheckSecretsChan every watch interval (defaults to 10 seconds)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Duration(watchModeInterval) * time.Second)
|
|
||||||
recheckSecretsChannel <- true
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
<-recheckSecretsChannel
|
|
||||||
watchMutex.Lock()
|
|
||||||
|
|
||||||
newEnvironmentVariables, err := createInjectableEnvironment(request, projectConfigDir, secretOverriding, expandSecrets, token)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("[HOT RELOAD] Failed to fetch secrets")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if newEnvironmentVariables.ETag != currentETag {
|
|
||||||
startProcess(newEnvironmentVariables)
|
|
||||||
} else {
|
|
||||||
log.Debug().Msg("[HOT RELOAD] No changes detected in secrets, not reloading process")
|
|
||||||
}
|
|
||||||
|
|
||||||
watchMutex.Unlock()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterReservedEnvVars(env map[string]models.SingleEnvironmentVariable) {
|
|
||||||
var (
|
|
||||||
reservedEnvVars = []string{
|
|
||||||
"HOME", "PATH", "PS1", "PS2",
|
|
||||||
"PWD", "EDITOR", "XAUTHORITY", "USER",
|
|
||||||
"TERM", "TERMINFO", "SHELL", "MAIL",
|
|
||||||
}
|
|
||||||
|
|
||||||
reservedEnvVarPrefixes = []string{
|
|
||||||
"XDG_",
|
|
||||||
"LC_",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, reservedEnvName := range reservedEnvVars {
|
|
||||||
if _, ok := env[reservedEnvName]; ok {
|
|
||||||
delete(env, reservedEnvName)
|
|
||||||
util.PrintWarning(fmt.Sprintf("Infisical secret named [%v] has been removed because it is a reserved secret name", reservedEnvName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, reservedEnvPrefix := range reservedEnvVarPrefixes {
|
|
||||||
for envName := range env {
|
|
||||||
if strings.HasPrefix(envName, reservedEnvPrefix) {
|
|
||||||
delete(env, envName)
|
|
||||||
util.PrintWarning(fmt.Sprintf("Infisical secret named [%v] has been removed because it contains a reserved prefix", envName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(runCmd)
|
|
||||||
runCmd.Flags().String("token", "", "fetch secrets using service token or machine identity access token")
|
|
||||||
runCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth")
|
|
||||||
runCmd.Flags().StringP("env", "e", "dev", "set the environment (dev, prod, etc.) from which your secrets should be pulled from")
|
|
||||||
runCmd.Flags().Bool("expand", true, "parse shell parameter expansions in your secrets")
|
|
||||||
runCmd.Flags().Bool("include-imports", true, "import linked secrets ")
|
|
||||||
runCmd.Flags().Bool("recursive", false, "fetch secrets from all sub-folders")
|
|
||||||
runCmd.Flags().Bool("secret-overriding", true, "prioritizes personal secrets, if any, with the same name over shared secrets")
|
|
||||||
runCmd.Flags().Bool("watch", false, "enable reload of application when secrets change")
|
|
||||||
runCmd.Flags().Int("watch-interval", 10, "interval in seconds to check for secret changes")
|
|
||||||
runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")")
|
|
||||||
runCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs ")
|
|
||||||
runCmd.Flags().String("path", "/", "get secrets within a folder path")
|
|
||||||
runCmd.Flags().String("project-config-dir", "", "explicitly set the directory where the .infisical.json resides")
|
|
||||||
}
|
|
||||||
|
|
||||||
func createInjectableEnvironment(request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, shouldExpandSecrets bool, token *models.TokenDetails) (models.InjectableEnvironmentResult, error) {
|
|
||||||
|
|
||||||
secrets, err := util.GetAllEnvironmentVariables(request, projectConfigDir)
|
secrets, err := util.GetAllEnvironmentVariables(request, projectConfigDir)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return models.InjectableEnvironmentResult{}, err
|
util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
if secretOverriding {
|
if secretOverriding {
|
||||||
@@ -406,33 +166,151 @@ func createInjectableEnvironment(request models.GetAllSecretsParameters, project
|
|||||||
environmentVariables[k] = v.Value
|
environmentVariables[k] = v.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
env := make([]string, 0, len(environmentVariables))
|
// turn it back into a list of envs
|
||||||
|
var env []string
|
||||||
for key, value := range environmentVariables {
|
for key, value := range environmentVariables {
|
||||||
env = append(env, key+"="+value)
|
s := key + "=" + value
|
||||||
|
env = append(env, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
return models.InjectableEnvironmentResult{
|
log.Debug().Msgf("injecting the following environment variables into shell: %v", env)
|
||||||
Variables: env,
|
|
||||||
ETag: util.GenerateETagFromSecrets(secrets),
|
Telemetry.CaptureEvent("cli-command:run",
|
||||||
SecretsCount: len(secretsByKey),
|
posthog.NewProperties().
|
||||||
}, nil
|
Set("secretsCount", len(secrets)).
|
||||||
|
Set("environment", environmentName).
|
||||||
|
Set("isUsingServiceToken", token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER).
|
||||||
|
Set("isUsingUniversalAuthToken", token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER).
|
||||||
|
Set("single-command", strings.Join(args, " ")).
|
||||||
|
Set("multi-command", cmd.Flag("command").Value.String()).
|
||||||
|
Set("version", util.CLI_VERSION))
|
||||||
|
|
||||||
|
if cmd.Flags().Changed("command") {
|
||||||
|
command := cmd.Flag("command").Value.String()
|
||||||
|
|
||||||
|
err = executeMultipleCommandWithEnvs(command, len(secretsByKey), env)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = executeSingleCommandWithEnvs(args, len(secretsByKey), env)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func WaitForExitCommand(cmd *exec.Cmd) (int, error) {
|
var (
|
||||||
|
reservedEnvVars = []string{
|
||||||
|
"HOME", "PATH", "PS1", "PS2",
|
||||||
|
"PWD", "EDITOR", "XAUTHORITY", "USER",
|
||||||
|
"TERM", "TERMINFO", "SHELL", "MAIL",
|
||||||
|
}
|
||||||
|
|
||||||
|
reservedEnvVarPrefixes = []string{
|
||||||
|
"XDG_",
|
||||||
|
"LC_",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func filterReservedEnvVars(env map[string]models.SingleEnvironmentVariable) {
|
||||||
|
for _, reservedEnvName := range reservedEnvVars {
|
||||||
|
if _, ok := env[reservedEnvName]; ok {
|
||||||
|
delete(env, reservedEnvName)
|
||||||
|
util.PrintWarning(fmt.Sprintf("Infisical secret named [%v] has been removed because it is a reserved secret name", reservedEnvName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, reservedEnvPrefix := range reservedEnvVarPrefixes {
|
||||||
|
for envName := range env {
|
||||||
|
if strings.HasPrefix(envName, reservedEnvPrefix) {
|
||||||
|
delete(env, envName)
|
||||||
|
util.PrintWarning(fmt.Sprintf("Infisical secret named [%v] has been removed because it contains a reserved prefix", envName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(runCmd)
|
||||||
|
runCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
|
runCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth")
|
||||||
|
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
|
||||||
|
runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
||||||
|
runCmd.Flags().Bool("include-imports", true, "Import linked secrets ")
|
||||||
|
runCmd.Flags().Bool("recursive", false, "Fetch secrets from all sub-folders")
|
||||||
|
runCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
||||||
|
runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")")
|
||||||
|
runCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs ")
|
||||||
|
runCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||||
|
runCmd.Flags().String("project-config-dir", "", "explicitly set the directory where the .infisical.json resides")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will execute a single command and pass in the given secrets into the process
|
||||||
|
func executeSingleCommandWithEnvs(args []string, secretsCount int, env []string) error {
|
||||||
|
command := args[0]
|
||||||
|
argsForCommand := args[1:]
|
||||||
|
|
||||||
|
log.Info().Msgf(color.GreenString("Injecting %v Infisical secrets into your application process", secretsCount))
|
||||||
|
|
||||||
|
cmd := exec.Command(command, argsForCommand...)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = env
|
||||||
|
|
||||||
|
return execCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeMultipleCommandWithEnvs(fullCommand string, secretsCount int, env []string) error {
|
||||||
|
shell := [2]string{"sh", "-c"}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
shell = [2]string{"cmd", "/C"}
|
||||||
|
} else {
|
||||||
|
currentShell := os.Getenv("SHELL")
|
||||||
|
if currentShell != "" {
|
||||||
|
shell[0] = currentShell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(shell[0], shell[1], fullCommand)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = env
|
||||||
|
|
||||||
|
log.Info().Msgf(color.GreenString("Injecting %v Infisical secrets into your application process", secretsCount))
|
||||||
|
log.Debug().Msgf("executing command: %s %s %s \n", shell[0], shell[1], fullCommand)
|
||||||
|
|
||||||
|
return execCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credit: inspired by AWS Valut
|
||||||
|
func execCmd(cmd *exec.Cmd) error {
|
||||||
|
sigChannel := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChannel)
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
sig := <-sigChannel
|
||||||
|
_ = cmd.Process.Signal(sig) // process all sigs
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
// ignore errors
|
_ = cmd.Process.Signal(os.Kill)
|
||||||
cmd.Process.Signal(os.Kill) // #nosec G104
|
return fmt.Errorf("failed to wait for command termination: %v", err)
|
||||||
|
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
|
||||||
return exitError.ExitCode(), exitError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 2, err
|
waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus)
|
||||||
}
|
os.Exit(waitStatus.ExitStatus())
|
||||||
|
return nil
|
||||||
waitStatus, ok := cmd.ProcessState.Sys().(syscall.WaitStatus)
|
|
||||||
if !ok {
|
|
||||||
return 2, fmt.Errorf("unexpected ProcessState type, expected syscall.WaitStatus, got %T", waitStatus)
|
|
||||||
}
|
|
||||||
return waitStatus.ExitStatus(), nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,12 +104,6 @@ type GetAllSecretsParameters struct {
|
|||||||
Recursive bool
|
Recursive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type InjectableEnvironmentResult struct {
|
|
||||||
Variables []string
|
|
||||||
ETag string
|
|
||||||
SecretsCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetAllFoldersParameters struct {
|
type GetAllFoldersParameters struct {
|
||||||
WorkspaceId string
|
WorkspaceId string
|
||||||
Environment string
|
Environment string
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RunCommand(singleCommand string, args []string, env []string) (*exec.Cmd, error) {
|
|
||||||
var c *exec.Cmd
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if singleCommand != "" {
|
|
||||||
c, err = RunCommandFromString(singleCommand, env)
|
|
||||||
} else {
|
|
||||||
c, err = RunCommandFromArgs(args, env)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsProcessRunning(p *os.Process) bool {
|
|
||||||
err := p.Signal(syscall.Signal(0))
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// For "infisical run -- COMMAND"
|
|
||||||
func RunCommandFromArgs(command []string, env []string) (*exec.Cmd, error) {
|
|
||||||
cmd := exec.Command(command[0], command[1:]...)
|
|
||||||
cmd.Env = env
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
err := execCommand(cmd)
|
|
||||||
|
|
||||||
return cmd, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func execCommand(cmd *exec.Cmd) error {
|
|
||||||
|
|
||||||
shouldForward := !isatty.IsTerminal(os.Stdout.Fd())
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan)
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle all signals
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
if shouldForward {
|
|
||||||
// forward to process
|
|
||||||
sig := <-sigChan
|
|
||||||
cmd.Process.Signal(sig)
|
|
||||||
} else {
|
|
||||||
<-sigChan
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// For "infisical run --command=COMMAND"
|
|
||||||
func RunCommandFromString(command string, env []string) (*exec.Cmd, error) {
|
|
||||||
shell := [2]string{"sh", "-c"}
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
shell = [2]string{"cmd", "/C"}
|
|
||||||
} else {
|
|
||||||
// these shells all support the same options we use for sh
|
|
||||||
shells := []string{"/bash", "/dash", "/fish", "/zsh", "/ksh", "/csh", "/tcsh"}
|
|
||||||
envShell := os.Getenv("SHELL")
|
|
||||||
for _, s := range shells {
|
|
||||||
if strings.HasSuffix(envShell, s) {
|
|
||||||
shell[0] = envShell
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd := exec.Command(shell[0], shell[1], command) // #nosec G204 nosemgrep: semgrep_configs.prohibit-exec-command
|
|
||||||
cmd.Env = env
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
err := execCommand(cmd)
|
|
||||||
return cmd, err
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
@@ -299,16 +298,3 @@ func GenerateRandomString(length int) string {
|
|||||||
}
|
}
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateETagFromSecrets(secrets []models.SingleEnvironmentVariable) string {
|
|
||||||
sortedSecrets := SortSecretsByKeys(secrets)
|
|
||||||
content := []byte{}
|
|
||||||
|
|
||||||
for _, secret := range sortedSecrets {
|
|
||||||
content = append(content, []byte(secret.Key)...)
|
|
||||||
content = append(content, []byte(secret.Value)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := sha256.Sum256(content)
|
|
||||||
return fmt.Sprintf(`"%s"`, hex.EncodeToString(hash[:]))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ $ infisical run -- npm run dev
|
|||||||
Alternatively, you may use service tokens.
|
Alternatively, you may use service tokens.
|
||||||
|
|
||||||
Please note, however, that service tokens are being deprecated in favor of [machine identities](/documentation/platform/identities/machine-identities). They will be removed in the future in accordance with the deprecation notice and timeline stated [here](https://infisical.com/blog/deprecating-api-keys).
|
Please note, however, that service tokens are being deprecated in favor of [machine identities](/documentation/platform/identities/machine-identities). They will be removed in the future in accordance with the deprecation notice and timeline stated [here](https://infisical.com/blog/deprecating-api-keys).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Example
|
# Example
|
||||||
export INFISICAL_TOKEN=<service-token>
|
export INFISICAL_TOKEN=<service-token>
|
||||||
```
|
```
|
||||||
|
|
||||||
</Info>
|
</Info>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
@@ -72,20 +72,11 @@ $ infisical run -- npm run dev
|
|||||||
# Example
|
# Example
|
||||||
export INFISICAL_DISABLE_UPDATE_CHECK=true
|
export INFISICAL_DISABLE_UPDATE_CHECK=true
|
||||||
```
|
```
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
### Flags
|
### Flags
|
||||||
|
|
||||||
<Accordion title="--watch">
|
|
||||||
By passing the `watch` flag, you are telling the CLI to watch for changes that happen in your Infisical project.
|
|
||||||
If secret changes happen, the command you provided will automatically be restarted with the new environment variables attached.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Example
|
|
||||||
infisical run --watch -- printenv
|
|
||||||
```
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="--project-config-dir">
|
<Accordion title="--project-config-dir">
|
||||||
Explicitly set the directory where the .infisical.json resides. This is useful for some monorepo setups.
|
Explicitly set the directory where the .infisical.json resides. This is useful for some monorepo setups.
|
||||||
|
|
||||||
@@ -93,6 +84,7 @@ $ infisical run -- npm run dev
|
|||||||
# Example
|
# Example
|
||||||
infisical run --project-config-dir=/some-dir -- printenv
|
infisical run --project-config-dir=/some-dir -- printenv
|
||||||
```
|
```
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="--command">
|
<Accordion title="--command">
|
||||||
@@ -180,19 +172,3 @@ $ infisical run -- npm run dev
|
|||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
|
|
||||||
## Automatically reload command when secrets change
|
|
||||||
|
|
||||||
To automatically reload your command when secrets change, use the `--watch` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
infisical run --watch -- npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
This will watch for changes in your secrets and automatically restart your command with the new secrets.
|
|
||||||
When your command restarts, it will have the new environment variables injeceted into it.
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
Please note that this feature is intended for development purposes. It is not recommended to use this in production environments. Generally it's not recommended to automatically reload your application in production when remote changes are made.
|
|
||||||
</Note>
|
|
||||||
Reference in New Issue
Block a user