diff --git a/cli/packages/cmd/export.go b/cli/packages/cmd/export.go index dbabb24e2..d0db485b3 100644 --- a/cli/packages/cmd/export.go +++ b/cli/packages/cmd/export.go @@ -74,7 +74,7 @@ var exportCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, WorkspaceId: projectId, SecretsPath: secretsPath}) + secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, WorkspaceId: projectId, SecretsPath: secretsPath}, "") if err != nil { util.HandleError(err, "Unable to fetch secrets") } @@ -87,7 +87,7 @@ var exportCmd = &cobra.Command{ var output string if shouldExpandSecrets { - substitutions := util.ExpandSecrets(secrets, infisicalToken) + substitutions := util.ExpandSecrets(secrets, infisicalToken, "") output, err = formatEnvs(substitutions, format) if err != nil { util.HandleError(err) diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go index 8aa5306d9..2bb043c26 100644 --- a/cli/packages/cmd/run.go +++ b/cli/packages/cmd/run.go @@ -67,6 +67,11 @@ var runCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } + projectConfigDir, err := cmd.Flags().GetString("project-config-dir") + if err != nil { + util.HandleError(err, "Unable to parse flag") + } + secretOverriding, err := cmd.Flags().GetBool("secret-overriding") if err != nil { util.HandleError(err, "Unable to parse flag") @@ -92,7 +97,7 @@ var runCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports}) + secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports}, projectConfigDir) if err != nil { util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid") @@ -105,7 +110,7 @@ var runCmd = &cobra.Command{ } if shouldExpandSecrets { - secrets = util.ExpandSecrets(secrets, infisicalToken) + secrets = util.ExpandSecrets(secrets, infisicalToken, projectConfigDir) } secretsByKey := getSecretsByKeys(secrets) @@ -198,6 +203,7 @@ func init() { 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 diff --git a/cli/packages/cmd/secrets.go b/cli/packages/cmd/secrets.go index 3aa808cf4..778ac574e 100644 --- a/cli/packages/cmd/secrets.go +++ b/cli/packages/cmd/secrets.go @@ -68,7 +68,7 @@ var secretsCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports}) + secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports}, "") if err != nil { util.HandleError(err) } @@ -80,7 +80,7 @@ var secretsCmd = &cobra.Command{ } if shouldExpandSecrets { - secrets = util.ExpandSecrets(secrets, infisicalToken) + secrets = util.ExpandSecrets(secrets, infisicalToken, "") } visualize.PrintAllSecretDetails(secrets) @@ -169,7 +169,7 @@ var secretsSetCmd = &cobra.Command{ plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey) // pull current secrets - secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath}) + secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath}, "") if err != nil { util.HandleError(err, "unable to retrieve secrets") } @@ -406,7 +406,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse path flag") } - secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}) + secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}, "") if err != nil { util.HandleError(err, "To fetch all secrets") } @@ -455,7 +455,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } - secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}) + secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}, "") if err != nil { util.HandleError(err, "To fetch all secrets") } diff --git a/cli/packages/util/config.go b/cli/packages/util/config.go index 842544d77..221609852 100644 --- a/cli/packages/util/config.go +++ b/cli/packages/util/config.go @@ -113,6 +113,28 @@ func GetWorkSpaceFromFile() (models.WorkspaceConfigFile, error) { return workspaceConfigFile, nil } +func GetWorkSpaceFromFilePath(configFileDir string) (models.WorkspaceConfigFile, error) { + configFilePath := filepath.Join(configFileDir, ".infisical.json") + + _, configFileStatusError := os.Stat(configFilePath) + if os.IsNotExist(configFileStatusError) { + return models.WorkspaceConfigFile{}, fmt.Errorf("file %s does not exist", configFilePath) + } + + configFileAsBytes, err := os.ReadFile(configFilePath) + if err != nil { + return models.WorkspaceConfigFile{}, err + } + + var workspaceConfigFile models.WorkspaceConfigFile + err = json.Unmarshal(configFileAsBytes, &workspaceConfigFile) + if err != nil { + return models.WorkspaceConfigFile{}, err + } + + return workspaceConfigFile, nil +} + // FindWorkspaceConfigFile searches for a .infisical.json file in the current directory and all parent directories. func FindWorkspaceConfigFile() (string, error) { dir, err := os.Getwd() diff --git a/cli/packages/util/helper.go b/cli/packages/util/helper.go index d96ac3899..043db468e 100644 --- a/cli/packages/util/helper.go +++ b/cli/packages/util/helper.go @@ -105,6 +105,17 @@ func RequireLocalWorkspaceFile() { } } +func ValidateWorkspaceFile(projectConfigFilePath string) { + workspaceFilePath, err := GetWorkSpaceFromFilePath(projectConfigFilePath) + if err != nil { + PrintErrorMessageAndExit(fmt.Sprintf("error reading your project config %v", err)) + } + + if workspaceFilePath.WorkspaceId == "" { + PrintErrorMessageAndExit("Your project id is missing in your local config file. Please add it or run again [infisical init]") + } +} + func GetHashFromStringList(list []string) string { hash := sha256.New() diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index 2eec803d1..a260aaaec 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -220,7 +220,7 @@ func InjectImportedSecret(plainTextWorkspaceKey []byte, secrets []models.SingleE return secrets, nil } -func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models.SingleEnvironmentVariable, error) { +func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectConfigFilePath string) ([]models.SingleEnvironmentVariable, error) { var infisicalToken string if params.InfisicalToken == "" { infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME) @@ -236,7 +236,13 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models if infisicalToken == "" { if isConnected { log.Debug().Msg("GetAllEnvironmentVariables: Connected to internet, checking logged in creds") - RequireLocalWorkspaceFile() + + if projectConfigFilePath == "" { + RequireLocalWorkspaceFile() + } else { + ValidateWorkspaceFile(projectConfigFilePath) + } + RequireLogin() } @@ -251,13 +257,26 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again") } - workspaceFile, err := GetWorkSpaceFromFile() - if err != nil { - return nil, err + var infisicalDotJson models.WorkspaceConfigFile + + if projectConfigFilePath == "" { + projectConfig, err := GetWorkSpaceFromFile() + if err != nil { + return nil, err + } + + infisicalDotJson = projectConfig + } else { + projectConfig, err := GetWorkSpaceFromFilePath(projectConfigFilePath) + if err != nil { + return nil, err + } + + infisicalDotJson = projectConfig } if params.WorkspaceId != "" { - workspaceFile.WorkspaceId = params.WorkspaceId + infisicalDotJson.WorkspaceId = params.WorkspaceId } // // Verify environment @@ -266,18 +285,18 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models // return nil, fmt.Errorf("unable to validate environment name because [err=%s]", err) // } - secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, + secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, infisicalDotJson.WorkspaceId, params.Environment, params.TagSlugs, params.SecretsPath, params.IncludeImport) log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn) backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32] if errorToReturn == nil { - WriteBackupSecrets(workspaceFile.WorkspaceId, params.Environment, backupSecretsEncryptionKey, secretsToReturn) + WriteBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, backupSecretsEncryptionKey, secretsToReturn) } // only attempt to serve cached secrets if no internet connection and if at least one secret cached if !isConnected { - backedSecrets, err := ReadBackupSecrets(workspaceFile.WorkspaceId, params.Environment, backupSecretsEncryptionKey) + backedSecrets, err := ReadBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, backupSecretsEncryptionKey) if len(backedSecrets) > 0 { PrintWarning("Unable to fetch latest secret(s) due to connection error, serving secrets from last successful fetch. For more info, run with --debug") secretsToReturn = backedSecrets @@ -421,7 +440,7 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod return secretMapByName } -func ExpandSecrets(secrets []models.SingleEnvironmentVariable, infisicalToken string) []models.SingleEnvironmentVariable { +func ExpandSecrets(secrets []models.SingleEnvironmentVariable, infisicalToken string, projectConfigPathDir string) []models.SingleEnvironmentVariable { expandedSecs := make(map[string]string) interpolatedSecs := make(map[string]string) // map[env.secret-path][keyname]Secret @@ -454,7 +473,7 @@ func ExpandSecrets(secrets []models.SingleEnvironmentVariable, infisicalToken st if crossRefSec, ok := crossEnvRefSecs[uniqKey]; !ok { // if not in cross reference cache, fetch it from server - refSecs, err := GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: env, InfisicalToken: infisicalToken, SecretsPath: secPath}) + refSecs, err := GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: env, InfisicalToken: infisicalToken, SecretsPath: secPath}, projectConfigPathDir) if err != nil { HandleError(err, fmt.Sprintf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath), "If you are using a service token to fetch secrets, please ensure it is valid") } diff --git a/docs/cli/commands/run.mdx b/docs/cli/commands/run.mdx index 62d0c5104..8078342a5 100644 --- a/docs/cli/commands/run.mdx +++ b/docs/cli/commands/run.mdx @@ -62,6 +62,16 @@ Inject secrets from Infisical into your application process. ### Flags + + + Explicitly set the directory where the .infisical.json resides. This is useful for some monorepo setups. + + ```bash + # Example + infisical run --project-config-dir=/some-dir -- printenv + ``` + + Pass secrets into multiple commands at once