Skip to content

Commit

Permalink
Resolves #415 - Add support for header groups
Browse files Browse the repository at this point in the history
  • Loading branch information
steve-r-west committed Jan 1, 2024
1 parent b33b2bd commit 9d081a0
Show file tree
Hide file tree
Showing 14 changed files with 849 additions and 53 deletions.
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ The following is a summary of the main commands, in general you can type `epcc h
| `epcc test-json [KEY] [VAL] [KEY] [VAL] ...` | Render a JSON document based on the supplied key and value pairs |

#### Power User Commands

| Command | Description |
|-----------------------------------------|----------------------------------------------------------------------------|
| `epcc reset-store <STORE_ID>` | Reset the store to an initial state (on a best effort basis) |
Expand All @@ -67,7 +68,19 @@ The following is a summary of the main commands, in general you can type `epcc h
3. `--max-concurrency` will control the maximum number of concurrent commands that can run simultaneously.
* This differs from the rate limit in that if a request takes 2 seconds, a rate limit of 3 will allow 6 requests in flight at a time, whereas `--max-concurrency` would limit you to 3. A higher value will slow down initial start time.

#### Headers

Headers can be set in one of three ways, depending on what is most convenient

1. Via the `-H` argument.
* This header will be one time only.
2. Via the `EPCC_CLI_HTTP_HEADER_0` environment variable.
* This header will be always be set.
3. Via the `epcc header set`
* These headers will be set in the current profile and will stay until unset. You can see what headers are set with `epcc headers status`
* Headers set this way support aliases.
* You can also additionally group headers into groups with `--group` and then clear all headers with `epcc headers clear <GROUP>`

### Configuration

#### Via Prompts
Expand All @@ -78,16 +91,16 @@ Run the `epcc configure` and it will prompt you for the required settings, when

The following environment variables can be set up to control which environment and store to use with the EPCC CLI.

| Environment Variable | Description |
|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| EPCC_API_BASE_URL | This is the API base URL which can be retrieved via CM. |
| EPCC_BETA_API_FEATURES | This variable allows you to set [Beta Headers](https://documentation.elasticpath.com/commerce-cloud/docs/api/basics/api-contract.html#beta-apis) for all API calls. |
| EPCC_CLI_HTTP_HEADER_**N** | Setting any environment variable like this (where N is a number) will cause it's value to be parsed and added to all HTTP headers (e.g., `EPCC_CLI_HTTP_HEADER_0=Cache-Control: no-cache` will add `Cache-Control: no-cache` as a header). FYI, the surprising syntax is due to different encoding rules. |
| EPCC_CLI_SUPPRESS_NO_AUTH_MESSAGES | This will supress warning messages about not being authenticated or logged out |
| EPCC_CLIENT_ID | This is the Client ID which can be retrieved via CM. |
| EPCC_CLIENT_SECRET | This is the Client Secret which can be retrieved via CM. |
| EPCC_PROFILE | A profile name that allows for an independent session and isolation (e.g., distinct histories) |
| EPCC_RUNBOOK_DIRECTORY | A directory that will be scanned for runbook, a runbook ends with `.epcc.yml` |
| Environment Variable | Description |
|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| EPCC_API_BASE_URL | This is the API base URL which can be retrieved via CM. |
| EPCC_BETA_API_FEATURES | This variable allows you to set [Beta Headers](https://documentation.elasticpath.com/commerce-cloud/docs/api/basics/api-contract.html#beta-apis) for all API calls. |
| EPCC_CLI_HTTP_HEADER_**N** | Setting any environment variable like this (where N is a number) will cause it's value to be parsed and added to all HTTP headers (e.g., `EPCC_CLI_HTTP_HEADER_0=Cache-Control: no-cache` will add `Cache-Control: no-cache` as a header). FYI, the surprising syntax is due to different encoding rules. You can also specify headers using `-H` or `epcc headers` |
| EPCC_CLI_SUPPRESS_NO_AUTH_MESSAGES | This will supress warning messages about not being authenticated or logged out |
| EPCC_CLIENT_ID | This is the Client ID which can be retrieved via CM. |
| EPCC_CLIENT_SECRET | This is the Client Secret which can be retrieved via CM. |
| EPCC_PROFILE | A profile name that allows for an independent session and isolation (e.g., distinct histories) |
| EPCC_RUNBOOK_DIRECTORY | A directory that will be scanned for runbook, a runbook ends with `.epcc.yml` |

It is recommended to set EPCC_API_BASE_URL, EPCC_CLIENT_ID, and EPCC_CLIENT_SECRET to be able to interact with most things in the CLI.

Expand Down
130 changes: 130 additions & 0 deletions cmd/headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package cmd

import (
"fmt"
"github.com/elasticpath/epcc-cli/external/completion"
"github.com/elasticpath/epcc-cli/external/headergroups"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func NewHeadersCommand(parentCmd *cobra.Command) func() {

var headersCmd = &cobra.Command{
Use: "headers",
Short: "Set headers that should be used on all subsequent calls",
SilenceUsage: true,
}

parentCmd.AddCommand(headersCmd)

var setGroup = "default"
var delGroup = "default"
resetFunc := func() {
setGroup = "default"
delGroup = "default"
}

var setHeaderCmd = &cobra.Command{
Use: "set [HEADER_KEY] [HEADER_VALUE] ...",
Short: "Set a header to be used on all subsequent requests",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

if len(args)%2 == 0 {
return completion.Complete(completion.Request{
Type: completion.CompleteHeaderKey,
ToComplete: toComplete,
})
} else {
return completion.Complete(completion.Request{
Type: completion.CompleteHeaderValue,
// Get the second last value
Header: args[len(args)-1],
ToComplete: toComplete,
})
}

},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args)%2 != 0 {
return fmt.Errorf("Invalid number of arguments received, should be an even number of key and values: %d", len(args))
}

for i := 0; i < len(args); i += 2 {
headergroups.AddHeaderToGroup(setGroup, args[i], args[i+1])
}

return nil
},
}

var delHeaderCmd = &cobra.Command{
Use: "delete HEADER_KEY...",
Short: "Deletes a header from a group",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
results, compDir := completion.Complete(completion.Request{
Type: completion.CompleteHeaderKey,
ToComplete: toComplete,
})

for h := range headergroups.GetAllHeaders() {
results = append(results, h)
}

return results, compDir
},

RunE: func(cmd *cobra.Command, args []string) error {
for _, headerKey := range args {
headergroups.RemoveHeaderFromGroup(delGroup, headerKey)
}
return nil
},
}

var statusCmd = &cobra.Command{
Use: "status",
Short: "Displays the current headers that are set, and the header groups that are in use.",
RunE: func(cmd *cobra.Command, args []string) error {
hgs := headergroups.GetAllHeaderGroups()

for _, hg := range hgs {
log.Infof("We are using a header group: %s", hg)
}

for k, v := range headergroups.GetAllHeaders() {
log.Infof("Using header %s: %s", k, v)
}

log.Infof("Header information stored in %v", headergroups.GetHeaderGroupPath())
return nil
},
}

var clearGroupCmd = &cobra.Command{
Use: "clear [GROUP_NAME]",
Short: "Clears a header group",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return headergroups.GetAllHeaderGroups(), cobra.ShellCompDirectiveNoFileComp
},

RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("expected exactly one argument, the group name, got %d", len(args))
}

headergroups.RemoveHeaderGroup(args[0])
return nil
},
}

setHeaderCmd.PersistentFlags().StringVar(&setGroup, "group", "default", "Stores the header with a group (so that you can easily clear them)")
delHeaderCmd.PersistentFlags().StringVar(&delGroup, "group", "default", "Removes the header from within a group")
headersCmd.AddCommand(setHeaderCmd)
headersCmd.AddCommand(delHeaderCmd)
headersCmd.AddCommand(statusCmd)
headersCmd.AddCommand(clearGroupCmd)

return resetFunc

}
17 changes: 14 additions & 3 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/elasticpath/epcc-cli/external/authentication"
"github.com/elasticpath/epcc-cli/external/browser"
"github.com/elasticpath/epcc-cli/external/completion"
"github.com/elasticpath/epcc-cli/external/headergroups"
"github.com/elasticpath/epcc-cli/external/httpclient"
"github.com/elasticpath/epcc-cli/external/json"
"github.com/elasticpath/epcc-cli/external/resources"
Expand All @@ -24,7 +25,7 @@ const (
ClientSecret = "client_secret"
)

var loginCmd = &cobra.Command{
var LoginCmd = &cobra.Command{
Use: "login",
Short: "Login to the API via client_credentials, implicit, customer or account management tokens.",
SilenceUsage: false,
Expand Down Expand Up @@ -105,6 +106,16 @@ var loginInfo = &cobra.Command{

}

hgs := headergroups.GetAllHeaderGroups()

for _, hg := range hgs {
log.Infof("We are using a header group: %s", hg)
}

for k, v := range headergroups.GetAllHeaders() {
log.Infof("Using header %s: %s", k, v)
}

log.Infof("All tokens are stored in %s", authentication.GetAuthenticationCacheDirectory())

return nil
Expand Down Expand Up @@ -208,7 +219,7 @@ var loginClientCredentials = &cobra.Command{
}
}

token, err := authentication.GetAuthenticationToken(false, &values)
token, err := authentication.GetAuthenticationToken(false, &values, true)

if err != nil {
return err
Expand Down Expand Up @@ -259,7 +270,7 @@ var loginImplicit = &cobra.Command{
values.Set(k, args[i+1])
}

token, err := authentication.GetAuthenticationToken(false, &values)
token, err := authentication.GetAuthenticationToken(false, &values, true)

if err != nil {
return err
Expand Down
14 changes: 14 additions & 0 deletions cmd/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"github.com/elasticpath/epcc-cli/external/authentication"
"github.com/elasticpath/epcc-cli/external/headergroups"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -73,3 +74,16 @@ var logoutAccountManagement = &cobra.Command{
return nil
},
}

var LogoutHeaders = &cobra.Command{
Use: "headers",
Short: "Clear all headers that are persisted in the profile",
RunE: func(cmd *cobra.Command, args []string) error {
for k, v := range headergroups.GetAllHeaders() {
log.Infof("Unsetting: %s = %s", k, v)
}

headergroups.ClearAllHeaderGroups()
return nil
},
}
19 changes: 12 additions & 7 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/elasticpath/epcc-cli/config"
"github.com/elasticpath/epcc-cli/external/aliases"
"github.com/elasticpath/epcc-cli/external/clictx"
"github.com/elasticpath/epcc-cli/external/headergroups"
"github.com/elasticpath/epcc-cli/external/httpclient"
"github.com/elasticpath/epcc-cli/external/logger"
"github.com/elasticpath/epcc-cli/external/misc"
Expand Down Expand Up @@ -84,7 +85,7 @@ func InitializeCmd() {
resourceListCommand,
aliasesCmd,
configure,
loginCmd,
LoginCmd,
logoutCmd,
ResetStore,
runbookGlobalCmd,
Expand Down Expand Up @@ -120,16 +121,19 @@ func InitializeCmd() {

aliasesCmd.AddCommand(aliasListCmd, aliasClearCmd)

loginCmd.AddCommand(loginClientCredentials)
loginCmd.AddCommand(loginImplicit)
loginCmd.AddCommand(loginInfo)
loginCmd.AddCommand(loginDocs)
loginCmd.AddCommand(loginCustomer)
loginCmd.AddCommand(loginAccountManagement)
LoginCmd.AddCommand(loginClientCredentials)
LoginCmd.AddCommand(loginImplicit)
LoginCmd.AddCommand(loginInfo)
LoginCmd.AddCommand(loginDocs)
LoginCmd.AddCommand(loginCustomer)
LoginCmd.AddCommand(loginAccountManagement)

logoutCmd.AddCommand(logoutBearer)
logoutCmd.AddCommand(logoutCustomer)
logoutCmd.AddCommand(logoutAccountManagement)
logoutCmd.AddCommand(LogoutHeaders)

NewHeadersCommand(RootCmd)
}

// If there is a log level argument, we will set it much earlier on a dummy command
Expand Down Expand Up @@ -251,6 +255,7 @@ func Execute() {

httpclient.LogStats()
aliases.FlushAliases()
headergroups.FlushHeaderGroups()

if exit {
os.Exit(3)
Expand Down
4 changes: 2 additions & 2 deletions external/authentication/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func AddPostAuthHook(f func(r *http.Request, s *http.Response)) {
defer getTokenMutex.Unlock()
postAuthHook = append(postAuthHook, f)
}
func GetAuthenticationToken(useTokenFromProfileDir bool, valuesOverride *url.Values) (*ApiTokenResponse, error) {
func GetAuthenticationToken(useTokenFromProfileDir bool, valuesOverride *url.Values, warnOnNoAuthentication bool) (*ApiTokenResponse, error) {

if useTokenFromProfileDir {
bearerToken.Store(GetApiToken())
Expand Down Expand Up @@ -98,7 +98,7 @@ func GetAuthenticationToken(useTokenFromProfileDir bool, valuesOverride *url.Val
defer noTokenWarningMutex.Unlock()
if noTokenWarningMessageLogged == false {
noTokenWarningMessageLogged = true
if !env.EPCC_CLI_SUPPRESS_NO_AUTH_MESSAGES {
if !env.EPCC_CLI_SUPPRESS_NO_AUTH_MESSAGES && warnOnNoAuthentication {
log.Warn("No client id set in profile or env var, no authentication will be used for API request. To get started, set the EPCC_CLIENT_ID and (optionally) EPCC_CLIENT_SECRET environment variables")
}

Expand Down
Loading

0 comments on commit 9d081a0

Please sign in to comment.