From c46a2ddaee1c85ed459bf1b3e6d0579f7fc42592 Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Tue, 21 Nov 2023 13:36:12 +0000 Subject: [PATCH] feat: improve environment support Improve support for configuring the tool from the environment as is commonly used for CI use cases. This adds the ability to read auth tokens from the DROPBOX_TOKENS environment variable instead of from the configuration file. Also add the ability to configured the personal app key which wasn't named and now configurable using DROPBOX_PERSONAL_APP_KEY environment variable to match the other options. Constants for config location and environment variable names are now stored in consts.go to make them more visible. Some additional improvement in error checking and extracting the common configFile helper, including using stdlib os.UserHomeDir instead of the custom library version. Switch from deprecated ioutil.ReadFile to supported os.ReadFile. --- cmd/consts.go | 17 ++++++++++ cmd/helpers.go | 43 +++++++++++++++++++++++++ cmd/logout.go | 8 ++--- cmd/put.go | 3 +- cmd/root.go | 85 +++++++++++++++++++++----------------------------- go.mod | 1 - go.sum | 2 -- 7 files changed, 99 insertions(+), 60 deletions(-) create mode 100644 cmd/consts.go create mode 100644 cmd/helpers.go diff --git a/cmd/consts.go b/cmd/consts.go new file mode 100644 index 0000000..3735762 --- /dev/null +++ b/cmd/consts.go @@ -0,0 +1,17 @@ +package cmd + +const ( + // Config file name and location. + configFileName = "auth.json" + appName = "dbxcli" + configBase = ".config" + + // Environment variable names. + tokensEnv = "DROPBOX_TOKENS" + personalAppKeyEnv = "DROPBOX_PERSONAL_APP_KEY" + personalAppSecretEnv = "DROPBOX_PERSONAL_APP_SECRET" + teamAccessAppKeyEnv = "DROPBOX_TEAM_APP_KEY" + teamAccessAppSecretEnv = "DROPBOX_TEAM_APP_SECRET" + teamManageAppKeyEnv = "DROPBOX_MANAGE_APP_KEY" + teamManageAppSecretEnv = "DROPBOX_MANAGE_APP_SECRET" +) diff --git a/cmd/helpers.go b/cmd/helpers.go new file mode 100644 index 0000000..7140aba --- /dev/null +++ b/cmd/helpers.go @@ -0,0 +1,43 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "os" + "path" +) + +// configFile returns the path to the config file. +func configFile() (string, error) { + dir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("user homedir: %w", err) + } + + return path.Join(dir, configBase, appName, configFileName), nil +} + +// readTokens returns a token map read from either the +// DROPBOX_TOKENS environment variable or the filePath, in that +// order. +func readTokens(filePath string) (TokenMap, error) { + var data []byte + if envTokens := os.Getenv(tokensEnv); envTokens != "" { + data = []byte(envTokens) + } else { + var err error + if data, err = os.ReadFile(filePath); err != nil { + if os.IsNotExist(err) { + return make(TokenMap), nil + } + return nil, fmt.Errorf("read tokens: %w", err) + } + } + + var tokens TokenMap + if err := json.Unmarshal(data, &tokens); err != nil { + return nil, fmt.Errorf("decode tokens: %w", err) + } + + return tokens, nil +} diff --git a/cmd/logout.go b/cmd/logout.go index d48dedd..4bf4288 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -15,22 +15,20 @@ package cmd import ( + "fmt" "os" - "path" "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox" "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/auth" - "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" ) // Command logout revokes all saved API tokens and deletes auth.json. func logout(cmd *cobra.Command, args []string) error { - dir, err := homedir.Dir() + filePath, err := configFile() if err != nil { - return err + return fmt.Errorf("config file: %w", err) } - filePath := path.Join(dir, ".config", "dbxcli", configFileName) tokMap, err := readTokens(filePath) if err != nil { diff --git a/cmd/put.go b/cmd/put.go index ac3988f..a98a2bf 100644 --- a/cmd/put.go +++ b/cmd/put.go @@ -19,7 +19,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "log" "os" "path" @@ -96,7 +95,7 @@ func uploadChunked(dbx files.Client, r io.Reader, commitInfo *files.CommitInfo, written := int64(0) for written < sizeTotal { - data, err := ioutil.ReadAll(&io.LimitedReader{R: r, N: chunkSize}) + data, err := io.ReadAll(&io.LimitedReader{R: r, N: chunkSize}) if err != nil { return err } diff --git a/cmd/root.go b/cmd/root.go index 18da322..634e9b9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,9 +17,7 @@ package cmd import ( "encoding/json" "fmt" - "io/ioutil" "os" - "path" "path/filepath" "strings" @@ -27,13 +25,11 @@ import ( "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox" "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" - "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "golang.org/x/net/context" ) const ( - configFileName = "auth.json" tokenPersonal = "personal" tokenTeamAccess = "teamAccess" tokenTeamManage = "teamManage" @@ -107,38 +103,22 @@ func makeRelocationArg(s string, d string) (arg *files.RelocationArg, err error) return } -func readTokens(filePath string) (TokenMap, error) { - b, err := ioutil.ReadFile(filePath) - if err != nil { - return nil, err - } - - var tokens TokenMap - if json.Unmarshal(b, &tokens) != nil { - return nil, err - } - - return tokens, nil -} - -func writeTokens(filePath string, tokens TokenMap) { - // Check if file exists - if _, err := os.Stat(filePath); os.IsNotExist(err) { - // Doesn't exist; lets create it - err = os.MkdirAll(filepath.Dir(filePath), 0700) - if err != nil { - return - } +func writeTokens(filePath string, tokens TokenMap) error { + // Ensure config directory exists. + if err := os.MkdirAll(filepath.Dir(filePath), 0700); err != nil { + return fmt.Errorf("create config directory: %w", err) } - // At this point, file must exist. Lets (over)write it. b, err := json.Marshal(tokens) if err != nil { - return + return fmt.Errorf("encode tokens: %w", err) } - if err = ioutil.WriteFile(filePath, b, 0600); err != nil { - return + + if err = os.WriteFile(filePath, b, 0600); err != nil { + return fmt.Errorf("write config: %w", err) } + + return nil } func tokenType(cmd *cobra.Command) string { @@ -151,29 +131,30 @@ func tokenType(cmd *cobra.Command) string { return tokenPersonal } -func initDbx(cmd *cobra.Command, args []string) (err error) { +func initDbx(cmd *cobra.Command, args []string) error { verbose, _ := cmd.Flags().GetBool("verbose") asMember, _ := cmd.Flags().GetString("as-member") domain, _ := cmd.Flags().GetString("domain") - dir, err := homedir.Dir() - if err != nil { - return - } - filePath := path.Join(dir, ".config", "dbxcli", configFileName) tokType := tokenType(cmd) conf := oauthConfig(tokType, domain) + filePath, err := configFile() + if err != nil { + return fmt.Errorf("config file: %w", err) + } + tokenMap, err := readTokens(filePath) - if tokenMap == nil { - tokenMap = make(TokenMap) + if err != nil { + return fmt.Errorf("read tokens: %w", err) } + if tokenMap[domain] == nil { tokenMap[domain] = make(map[string]string) } - tokens := tokenMap[domain] - if err != nil || tokens[tokType] == "" { + tokens := tokenMap[domain] + if tokens[tokType] == "" { fmt.Printf("1. Go to %v\n", conf.AuthCodeURL("state")) fmt.Printf("2. Click \"Allow\" (you might have to log in first).\n") fmt.Printf("3. Copy the authorization code.\n") @@ -181,16 +162,20 @@ func initDbx(cmd *cobra.Command, args []string) (err error) { var code string if _, err = fmt.Scan(&code); err != nil { - return + return fmt.Errorf("read code: %w", err) } + var token *oauth2.Token ctx := context.Background() token, err = conf.Exchange(ctx, code) if err != nil { - return + return fmt.Errorf("token exchange: %w", err) } + tokens[tokType] = token.AccessToken - writeTokens(filePath, tokenMap) + if err := writeTokens(filePath, tokenMap); err != nil { + return err + } } logLevel := dropbox.LogOff @@ -208,7 +193,7 @@ func initDbx(cmd *cobra.Command, args []string) (err error) { URLGenerator: nil, } - return + return nil } // RootCmd represents the base command when called without any subcommands @@ -236,10 +221,10 @@ func init() { RootCmd.PersistentFlags().String("domain", "", "Override default Dropbox domain, useful for testing") RootCmd.PersistentFlags().MarkHidden("domain") - personalAppKey = getEnv("DROPBOX_PERSONAL_APP_KEY", personalAppKey) - personalAppSecret = getEnv("DROPBOX_PERSONAL_APP_SECRET", personalAppSecret) - teamAccessAppKey = getEnv("DROPBOX_TEAM_APP_KEY", teamAccessAppKey) - teamAccessAppSecret = getEnv("DROPBOX_TEAM_APP_SECRET", teamAccessAppSecret) - teamManageAppKey = getEnv("DROPBOX_MANAGE_APP_KEY", teamManageAppKey) - teamManageAppSecret = getEnv("DROPBOX_MANAGE_APP_SECRET", teamAccessAppSecret) + personalAppKey = getEnv(personalAppKeyEnv, personalAppKey) + personalAppSecret = getEnv(personalAppSecretEnv, personalAppSecret) + teamAccessAppKey = getEnv(teamAccessAppKeyEnv, teamAccessAppKey) + teamAccessAppSecret = getEnv(teamAccessAppSecretEnv, teamAccessAppSecret) + teamManageAppKey = getEnv(teamManageAppKeyEnv, teamManageAppKey) + teamManageAppSecret = getEnv(teamManageAppSecretEnv, teamAccessAppSecret) } diff --git a/go.mod b/go.mod index 68ffcf0..2706704 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.1 github.com/dustin/go-humanize v1.0.0 github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e github.com/spf13/cobra v0.0.4-0.20190109003409-7547e83b2d85 github.com/spf13/pflag v1.0.3 // indirect diff --git a/go.sum b/go.sum index 3183f3c..28a43c2 100644 --- a/go.sum +++ b/go.sum @@ -109,8 +109,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e h1:Qa6dnn8DlasdXRnacluu8HzPts0S1I9zvvUPDbBnXFI= github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e/go.mod h1:waEya8ee1Ro/lgxpVhkJI4BVASzkm3UZqkx/cFJiYHM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=