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

Resolves #415 - Add support for header groups #418

Merged
merged 1 commit into from
Jan 1, 2024
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
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
Loading