Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an export command #90

Merged
merged 3 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions cli/packages/cmd/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"encoding/csv"
"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 > secrets.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(substitutions, format)
if err != nil {
log.Errorln(err)
return
}
} else {
output, err = formatEnvs(envsFromApi, format)
if err != nil {
log.Errorln(err)
maidul98 marked this conversation as resolved.
Show resolved Hide resolved
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(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 {
csvString := &strings.Builder{}
writer := csv.NewWriter(csvString)
writer.Write([]string{"Key", "Value"})
for _, env := range envs {
writer.Write([]string{env.Key, env.Value})
}
writer.Flush()
return csvString.String()
}

// 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)
}
45 changes: 5 additions & 40 deletions cli/packages/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
47 changes: 47 additions & 0 deletions cli/packages/util/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/base64"
"errors"
"fmt"
"os"
"regexp"
"strings"

Expand Down Expand Up @@ -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().
Expand Down
33 changes: 33 additions & 0 deletions docs/cli/export.mdx
Original file line number Diff line number Diff line change
@@ -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 determine from which infisical project your secrets will be exported from (only required 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
```