From a3739c67df765cd27d5aa62d09f374d17777b35c Mon Sep 17 00:00:00 2001 From: Tyson Cung Date: Sat, 21 Mar 2026 14:21:28 +0000 Subject: [PATCH] feat(cli): support INFISICAL_PROJECT_ID environment variable Add support for the INFISICAL_PROJECT_ID environment variable as a fallback when the --projectId flag is not provided. This enables full CI/CD automation without requiring CLI flags for every command. The environment variable is checked in all commands that accept --projectId: run, export, secrets, folders, dynamic-secrets, tokens, and ssh. Closes Infisical/infisical#2912 --- packages/cmd/dynamic_secrets.go | 20 ++++++++++---------- packages/cmd/export.go | 4 ++-- packages/cmd/folder.go | 6 +++--- packages/cmd/run.go | 4 ++-- packages/cmd/secrets.go | 26 +++++++++++++------------- packages/cmd/ssh.go | 4 ++-- packages/cmd/tokens.go | 4 ++-- packages/util/constants.go | 1 + packages/util/helper.go | 15 +++++++++++++++ 9 files changed, 50 insertions(+), 34 deletions(-) diff --git a/packages/cmd/dynamic_secrets.go b/packages/cmd/dynamic_secrets.go index 542f98f3..8b375c70 100644 --- a/packages/cmd/dynamic_secrets.go +++ b/packages/cmd/dynamic_secrets.go @@ -43,7 +43,7 @@ func getDynamicSecretList(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -171,7 +171,7 @@ func createDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -342,7 +342,7 @@ func renewDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -477,7 +477,7 @@ func revokeDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -605,7 +605,7 @@ func listDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -705,7 +705,7 @@ func listDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { func init() { dynamicSecretLeaseCreateCmd.Flags().StringP("path", "p", "/", "The path from where dynamic secret should be leased from") dynamicSecretLeaseCreateCmd.Flags().String("token", "", "Create dynamic secret leases using machine identity access token") - dynamicSecretLeaseCreateCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth") + dynamicSecretLeaseCreateCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") dynamicSecretLeaseCreateCmd.Flags().String("project-slug", "", "Manually set the project-slug to create lease in") dynamicSecretLeaseCreateCmd.Flags().String("ttl", "", "The lease lifetime TTL. If not provided the default TTL of dynamic secret will be used.") dynamicSecretLeaseCreateCmd.Flags().Bool("plain", false, "Print leased credentials without formatting, one per line") @@ -720,14 +720,14 @@ func init() { dynamicSecretLeaseListCmd.Flags().StringP("path", "p", "/", "The path from where dynamic secret should be leased from") dynamicSecretLeaseListCmd.Flags().String("token", "", "Fetch dynamic secret leases machine identity access token") - dynamicSecretLeaseListCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth") + dynamicSecretLeaseListCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") dynamicSecretLeaseListCmd.Flags().String("project-slug", "", "Manually set the project-slug to list leases from") util.AddOutputFlagsToCmd(dynamicSecretLeaseListCmd, "The output to format the dynamic secret leases in.") dynamicSecretLeaseCmd.AddCommand(dynamicSecretLeaseListCmd) dynamicSecretLeaseRenewCmd.Flags().StringP("path", "p", "/", "The path from where dynamic secret should be leased from") dynamicSecretLeaseRenewCmd.Flags().String("token", "", "Renew dynamic secrets machine identity access token") - dynamicSecretLeaseRenewCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth") + dynamicSecretLeaseRenewCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") dynamicSecretLeaseRenewCmd.Flags().String("project-slug", "", "Manually set the project-slug to renew lease in") dynamicSecretLeaseRenewCmd.Flags().String("ttl", "", "The lease lifetime TTL. If not provided the default TTL of dynamic secret will be used.") util.AddOutputFlagsToCmd(dynamicSecretLeaseRenewCmd, "The output to format the dynamic secret lease renewal in.") @@ -735,7 +735,7 @@ func init() { dynamicSecretLeaseRevokeCmd.Flags().StringP("path", "p", "/", "The path from where dynamic secret should be leased from") dynamicSecretLeaseRevokeCmd.Flags().String("token", "", "Delete dynamic secrets using machine identity access token") - dynamicSecretLeaseRevokeCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth") + dynamicSecretLeaseRevokeCmd.Flags().String("projectId", "", "Manually set the projectId to fetch leased from when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") dynamicSecretLeaseRevokeCmd.Flags().String("project-slug", "", "Manually set the project-slug to revoke lease from") util.AddOutputFlagsToCmd(dynamicSecretLeaseRevokeCmd, "The output to format the dynamic secret lease revocation in.") dynamicSecretLeaseCmd.AddCommand(dynamicSecretLeaseRevokeCmd) @@ -743,7 +743,7 @@ func init() { dynamicSecretCmd.AddCommand(dynamicSecretLeaseCmd) dynamicSecretCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") - dynamicSecretCmd.Flags().String("projectId", "", "Manually set the projectId to fetch dynamic-secret when using machine identity based auth") + dynamicSecretCmd.Flags().String("projectId", "", "Manually set the projectId to fetch dynamic-secret when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") dynamicSecretCmd.Flags().String("project-slug", "", "Manually set the project-slug to fetch dynamic-secret from") dynamicSecretCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on") dynamicSecretCmd.Flags().String("path", "/", "get dynamic secret within a folder path") diff --git a/packages/cmd/export.go b/packages/cmd/export.go index a23dd430..cadc2dc3 100644 --- a/packages/cmd/export.go +++ b/packages/cmd/export.go @@ -52,7 +52,7 @@ var exportCmd = &cobra.Command{ util.HandleError(err) } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err) } @@ -271,7 +271,7 @@ func init() { exportCmd.Flags().Bool("include-imports", true, "Imported linked secrets") exportCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") exportCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs") - exportCmd.Flags().String("projectId", "", "manually set the projectId to export secrets from") + exportCmd.Flags().String("projectId", "", "manually set the projectId to export secrets from. Can also be set via INFISICAL_PROJECT_ID env variable") exportCmd.Flags().String("path", "/", "get secrets within a folder path") exportCmd.Flags().String("template", "", "The path to the template file used to render secrets") exportCmd.Flags().StringP("output-file", "o", "", "The path to write the output file to. Can be a full file path, directory, or filename. If not specified, output will be printed to stdout") diff --git a/packages/cmd/folder.go b/packages/cmd/folder.go index bd114759..e533e28c 100644 --- a/packages/cmd/folder.go +++ b/packages/cmd/folder.go @@ -33,7 +33,7 @@ var getCmd = &cobra.Command{ } } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -110,7 +110,7 @@ var createCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -202,7 +202,7 @@ var deleteCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } diff --git a/packages/cmd/run.go b/packages/cmd/run.go index fecbccb1..496e31bf 100644 --- a/packages/cmd/run.go +++ b/packages/cmd/run.go @@ -77,7 +77,7 @@ var runCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -210,7 +210,7 @@ func filterReservedEnvVars(env map[string]models.SingleEnvironmentVariable) { 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().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") 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 ") diff --git a/packages/cmd/secrets.go b/packages/cmd/secrets.go index 904db0d8..3b9e689a 100644 --- a/packages/cmd/secrets.go +++ b/packages/cmd/secrets.go @@ -38,7 +38,7 @@ var secretsCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -191,7 +191,7 @@ var secretsSetCmd = &cobra.Command{ } } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -341,7 +341,7 @@ var secretsDeleteCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -454,7 +454,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -597,7 +597,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse flag") } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -812,12 +812,12 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod func init() { // not doing this one secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") - secretsGenerateExampleEnvCmd.Flags().String("projectId", "", "manually set the projectId when using machine identity based auth") + secretsGenerateExampleEnvCmd.Flags().String("projectId", "", "manually set the projectId when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") secretsGenerateExampleEnvCmd.Flags().String("path", "/", "Fetch secrets from within a folder path") secretsCmd.AddCommand(secretsGenerateExampleEnvCmd) secretsGetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") - secretsGetCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth") + secretsGetCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") secretsGetCmd.Flags().String("path", "/", "get secrets within a folder path") secretsGetCmd.Flags().Bool("plain", false, "print values without formatting, one per line") secretsGetCmd.Flags().Bool("raw-value", false, "deprecated. Returns only the value of secret, only works with one secret. Use --plain instead") @@ -831,7 +831,7 @@ func init() { secretsCmd.AddCommand(secretsSetCmd) secretsSetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") - secretsSetCmd.Flags().String("projectId", "", "manually set the project ID to for setting secrets when using machine identity based auth") + secretsSetCmd.Flags().String("projectId", "", "manually set the project ID to for setting secrets when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") secretsSetCmd.Flags().String("path", "/", "set secrets within a folder path") secretsSetCmd.Flags().String("type", util.SECRET_TYPE_SHARED, "the type of secret to create: personal or shared") secretsSetCmd.Flags().String("file", "", "Load secrets from the specified file. File format: .env or YAML (comments: # or //). This option is mutually exclusive with command-line secrets arguments.") @@ -839,7 +839,7 @@ func init() { secretsDeleteCmd.Flags().String("type", "personal", "the type of secret to delete: personal or shared (default: personal)") secretsDeleteCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") - secretsDeleteCmd.Flags().String("projectId", "", "manually set the projectId to delete secrets from when using machine identity based auth") + secretsDeleteCmd.Flags().String("projectId", "", "manually set the projectId to delete secrets from when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") secretsDeleteCmd.Flags().String("path", "/", "get secrets within a folder path") util.AddOutputFlagsToCmd(secretsDeleteCmd, "The output to format the secrets in.") secretsCmd.AddCommand(secretsDeleteCmd) @@ -850,7 +850,7 @@ func init() { // Add getCmd, createCmd and deleteCmd flags here getCmd.Flags().StringP("path", "p", "/", "The path from where folders should be fetched from") getCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") - getCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from when using machine identity based auth") + getCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") util.AddOutputFlagsToCmd(getCmd, "The output to format the folders in.") folderCmd.AddCommand(getCmd) @@ -858,14 +858,14 @@ func init() { createCmd.Flags().StringP("path", "p", "/", "Path to where the folder should be created") createCmd.Flags().StringP("name", "n", "", "Name of the folder to be created in selected `--path`") createCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") - createCmd.Flags().String("projectId", "", "manually set the project ID for creating folders in when using machine identity based auth") + createCmd.Flags().String("projectId", "", "manually set the project ID for creating folders in when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") util.AddOutputFlagsToCmd(createCmd, "The output to format the folders in.") folderCmd.AddCommand(createCmd) // Add deleteCmd flags here deleteCmd.Flags().StringP("path", "p", "/", "Path to the folder to be deleted") deleteCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") - deleteCmd.Flags().String("projectId", "", "manually set the projectId to delete folders when using machine identity based auth") + deleteCmd.Flags().String("projectId", "", "manually set the projectId to delete folders when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") deleteCmd.Flags().StringP("name", "n", "", "Name of the folder to be deleted within selected `--path`") util.AddOutputFlagsToCmd(deleteCmd, "The output to format the folders in.") folderCmd.AddCommand(deleteCmd) @@ -875,7 +875,7 @@ func init() { // ** End of folders sub command secretsCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") - secretsCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets when using machine identity based auth") + secretsCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets when using machine identity based auth. Can also be set via INFISICAL_PROJECT_ID env variable") secretsCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on") secretsCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets, and process your referenced secrets") secretsCmd.Flags().Bool("include-imports", true, "Imported linked secrets ") diff --git a/packages/cmd/ssh.go b/packages/cmd/ssh.go index 8a7c40c6..45eff66d 100644 --- a/packages/cmd/ssh.go +++ b/packages/cmd/ssh.go @@ -884,7 +884,7 @@ func sshAddHost(cmd *cobra.Command, args []string) { infisicalToken = loggedInUserDetails.UserCredentials.JTWToken } - projectId, err := cmd.Flags().GetString("projectId") + projectId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse --projectId flag") } @@ -1127,7 +1127,7 @@ func init() { sshCmd.AddCommand(sshConnectCmd) sshAddHostCmd.Flags().String("token", "", "Use a machine identity access token") - sshAddHostCmd.Flags().String("projectId", "", "Project ID the host belongs to (required)") + sshAddHostCmd.Flags().String("projectId", "", "Project ID the host belongs to (required). Can also be set via INFISICAL_PROJECT_ID env variable") sshAddHostCmd.Flags().String("hostname", "", "Hostname of the SSH host (required)") sshAddHostCmd.Flags().String("alias", "", "Alias for the SSH host") sshAddHostCmd.Flags().Bool("write-user-ca-to-file", false, "Write User CA public key to /etc/ssh/infisical_user_ca.pub") diff --git a/packages/cmd/tokens.go b/packages/cmd/tokens.go index 28b54ba0..a94f0caa 100644 --- a/packages/cmd/tokens.go +++ b/packages/cmd/tokens.go @@ -53,7 +53,7 @@ var tokensCreateCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - workspaceId, err := cmd.Flags().GetString("projectId") + workspaceId, err := util.GetProjectIdFromFlag(cmd) if err != nil { util.HandleError(err, "Unable to parse flag") } @@ -167,7 +167,7 @@ var tokensCreateCmd = &cobra.Command{ } func init() { - tokensCreateCmd.Flags().String("projectId", "", "The project ID you'd like to create the service token for. Default: will use linked Infisical project in .infisical.json") + tokensCreateCmd.Flags().String("projectId", "", "The project ID you'd like to create the service token for. Default: will use linked Infisical project in .infisical.json. Can also be set via INFISICAL_PROJECT_ID env variable") tokensCreateCmd.Flags().StringSliceP("scope", "s", []string{}, "Environment and secret path. Example format: :") tokensCreateCmd.Flags().StringP("name", "n", "Service token generated via CLI", "Service token name") tokensCreateCmd.Flags().StringSliceP("access-level", "a", []string{}, "The type of access the service token should have. Can be 'read' and or 'write'") diff --git a/packages/util/constants.go b/packages/util/constants.go index 57a1cddb..d778b29d 100644 --- a/packages/util/constants.go +++ b/packages/util/constants.go @@ -43,6 +43,7 @@ const ( INFISICAL_LDAP_USERNAME = "INFISICAL_LDAP_USERNAME" INFISICAL_LDAP_PASSWORD = "INFISICAL_LDAP_PASSWORD" INFISICAL_ORGANIZATION_ID = "INFISICAL_ORGANIZATION_ID" + INFISICAL_PROJECT_ID_NAME = "INFISICAL_PROJECT_ID" INFISICAL_GATEWAY_TOKEN_NAME_LEGACY = "TOKEN" // backwards compatibility with gateway helm chart, where token was the only supported auth method diff --git a/packages/util/helper.go b/packages/util/helper.go index cdaef77c..e5701a9d 100644 --- a/packages/util/helper.go +++ b/packages/util/helper.go @@ -323,6 +323,21 @@ func GetInfisicalToken(cmd *cobra.Command) (token *models.TokenDetails, err erro } +// GetProjectIdFromFlag reads the --projectId flag value and falls back to the +// INFISICAL_PROJECT_ID environment variable when the flag is empty. +func GetProjectIdFromFlag(cmd *cobra.Command) (string, error) { + projectId, err := cmd.Flags().GetString("projectId") + if err != nil { + return "", err + } + + if projectId == "" { + projectId = os.Getenv(INFISICAL_PROJECT_ID_NAME) + } + + return projectId, nil +} + func UniversalAuthLogin(clientId string, clientSecret string) (api.UniversalAuthLoginResponse, error) { httpClient, err := GetRestyClientWithCustomHeaders() if err != nil {