diff --git a/cli/packages/cmd/export.go b/cli/packages/cmd/export.go new file mode 100644 index 000000000..72d36dd84 --- /dev/null +++ b/cli/packages/cmd/export.go @@ -0,0 +1,137 @@ +/* +Copyright © 2022 NAME HERE +*/ +package cmd + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/Infisical/infisical-merge/packages/models" + "github.com/Infisical/infisical-merge/packages/util" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +const ( + FormatDotenv string = "dotenv" + FormatJson string = "json" + FormatCSV string = "csv" +) + +// exportCmd represents the export command +var exportCmd = &cobra.Command{ + Use: "export", + Short: "Used to export environment variables to a file", + DisableFlagsInUseLine: true, + Example: "infisical export --env=prod --format=json", + Args: cobra.NoArgs, + PreRun: toggleDebug, + Run: func(cmd *cobra.Command, args []string) { + envName, err := cmd.Flags().GetString("env") + if err != nil { + log.Errorln("Unable to parse the environment flag") + log.Debugln(err) + return + } + + shouldExpandSecrets, err := cmd.Flags().GetBool("expand") + if err != nil { + log.Errorln("Unable to parse the substitute flag") + log.Debugln(err) + return + } + + projectId, err := cmd.Flags().GetString("projectId") + if err != nil { + log.Errorln("Unable to parse the project id flag") + log.Debugln(err) + return + } + + format, err := cmd.Flags().GetString("format") + if err != nil { + log.Errorln("Unable to parse the format flag") + log.Debugln(err) + return + } + + envsFromApi, err := util.GetAllEnvironmentVariables(projectId, envName) + if err != nil { + log.Errorln("Something went wrong when pulling secrets using your Infisical token. Double check the token, project id or environment name (dev, prod, ect.)") + log.Debugln(err) + return + } + + var output string + if shouldExpandSecrets { + substitutions := util.SubstituteSecrets(envsFromApi) + output, err = formatEnvs(envName, substitutions, format) + if err != nil { + log.Errorln(err) + return + } + } else { + output, err = formatEnvs(envName, envsFromApi, format) + if err != nil { + log.Errorln(err) + return + } + } + fmt.Print(output) + }, +} + +func init() { + rootCmd.AddCommand(exportCmd) + exportCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from") + exportCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from") + exportCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets") + exportCmd.Flags().StringP("format", "f", "dotenv", "Set the format of the output file (dotenv, json, csv)") +} + +// Format according to the format flag +func formatEnvs(_ string, envs []models.SingleEnvironmentVariable, format string) (string, error) { + switch strings.ToLower(format) { + case FormatDotenv: + return formatAsDotEnv(envs), nil + case FormatJson: + return formatAsJson(envs), nil + case FormatCSV: + return formatAsCSV(envs), nil + default: + return "", fmt.Errorf("invalid format flag: %s", format) + } +} + +// Format environment variables as a CSV file +func formatAsCSV(envs []models.SingleEnvironmentVariable) string { + var csv string + csv += "Key,Value\n" + for _, env := range envs { + csv += fmt.Sprintf("%s,\"%s\"\n", env.Key, env.Value) + } + return csv +} + +// Format environment variables as a dotenv file +func formatAsDotEnv(envs []models.SingleEnvironmentVariable) string { + var dotenv string + for _, env := range envs { + dotenv += fmt.Sprintf("%s='%s'\n", env.Key, env.Value) + } + return dotenv +} + +// Format environment variables as a JSON file +func formatAsJson(envs []models.SingleEnvironmentVariable) string { + // Dump as a json array + json, err := json.Marshal(envs) + if err != nil { + log.Errorln("Unable to marshal environment variables to JSON") + log.Debugln(err) + return "" + } + return string(json) +} diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go index 6b44fa548..75365dead 100644 --- a/cli/packages/cmd/run.go +++ b/cli/packages/cmd/run.go @@ -47,46 +47,11 @@ var runCmd = &cobra.Command{ return } - var envsFromApi []models.SingleEnvironmentVariable - infisicalToken := os.Getenv(util.INFISICAL_TOKEN_NAME) - if infisicalToken == "" { - hasUserLoggedInbefore, loggedInUserEmail, err := util.IsUserLoggedIn() - if err != nil { - log.Info("Unexpected issue occurred while checking login status. To see more details, add flag --debug") - log.Debugln(err) - return - } - - if !hasUserLoggedInbefore { - log.Infoln("No logged in user. To login, please run command [infisical login]") - return - } - - userCreds, err := util.GetUserCredsFromKeyRing(loggedInUserEmail) - if err != nil { - log.Infoln("Unable to get user creds from key ring") - log.Debug(err) - return - } - - if !util.WorkspaceConfigFileExists() { - log.Infoln("Your project is not connected to a project yet. Run command [infisical init]") - return - } - - envsFromApi, err = util.GetSecretsFromAPIUsingCurrentLoggedInUser(envName, userCreds) - if err != nil { - log.Errorln("Something went wrong when pulling secrets using your logged in credentials. If the issue persists, double check your project id/try logging in again.") - log.Debugln(err) - return - } - } else { - envsFromApi, err = util.GetSecretsFromAPIUsingInfisicalToken(infisicalToken, envName, projectId) - if err != nil { - log.Errorln("Something went wrong when pulling secrets using your Infisical token. Double check the token, project id or environment name (dev, prod, ect.)") - log.Debugln(err) - return - } + envsFromApi, err := util.GetAllEnvironmentVariables(projectId, envName) + if err != nil { + log.Errorln("Something went wrong when pulling secrets using your Infisical token. Double check the token, project id or environment name (dev, prod, ect.)") + log.Debugln(err) + return } if shouldExpandSecrets { diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index 5cf76d48e..de5095d75 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "errors" "fmt" + "os" "regexp" "strings" @@ -184,6 +185,52 @@ func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, return listOfEnv, nil } +func GetAllEnvironmentVariables(projectId string, envName string) ([]models.SingleEnvironmentVariable, error) { + var envsFromApi []models.SingleEnvironmentVariable + infisicalToken := os.Getenv(INFISICAL_TOKEN_NAME) + if infisicalToken == "" { + hasUserLoggedInbefore, loggedInUserEmail, err := IsUserLoggedIn() + if err != nil { + log.Info("Unexpected issue occurred while checking login status. To see more details, add flag --debug") + log.Debugln(err) + return envsFromApi, err + } + + if !hasUserLoggedInbefore { + log.Infoln("No logged in user. To login, please run command [infisical login]") + return envsFromApi, fmt.Errorf("user not logged in") + } + + userCreds, err := GetUserCredsFromKeyRing(loggedInUserEmail) + if err != nil { + log.Infoln("Unable to get user creds from key ring") + log.Debug(err) + return envsFromApi, err + } + + if !WorkspaceConfigFileExists() { + log.Infoln("Your project is not connected to a project yet. Run command [infisical init]") + return envsFromApi, fmt.Errorf("project not initialized") + } + + envsFromApi, err = GetSecretsFromAPIUsingCurrentLoggedInUser(envName, userCreds) + if err != nil { + log.Errorln("Something went wrong when pulling secrets using your logged in credentials. If the issue persists, double check your project id/try logging in again.") + log.Debugln(err) + return envsFromApi, err + } + } else { + envsFromApi, err := GetSecretsFromAPIUsingInfisicalToken(infisicalToken, envName, projectId) + if err != nil { + log.Errorln("Something went wrong when pulling secrets using your Infisical token. Double check the token, project id or environment name (dev, prod, ect.)") + log.Debugln(err) + return envsFromApi, err + } + } + + return envsFromApi, nil +} + func GetWorkSpacesFromAPI(userCreds models.UserCredentials) (workspaces []models.Workspace, err error) { // create http client httpClient := resty.New(). diff --git a/docs/cli/export.mdx b/docs/cli/export.mdx new file mode 100644 index 000000000..9e0393336 --- /dev/null +++ b/docs/cli/export.mdx @@ -0,0 +1,33 @@ +--- +title: "infisical export" +--- + +```bash +infisical export [options] +``` + +## Description + +Export environment variables from the platform into a file format. + +## Options + +| 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` | +| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` | +| `--format` | Format of the output file. Accepted values: `dotenv`, `csv` and `json` | `dotenv` | + +## Examples + +```bash +# Export variables to a .env file +infisical export > .env + +# Export variables to a CSV file +infisical export --format=csv > secrets.csv + +# Export variables to a JSON file +infisical export --format=json > secrets.json +```