Skip to content

Commit

Permalink
Resolves #404 - Add template support for attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
steve-r-west committed Aug 11, 2024
1 parent d32a72b commit 03f6784
Show file tree
Hide file tree
Showing 17 changed files with 204 additions and 160 deletions.
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,6 @@ this is based on [GoJQ which has a number of differences](https://github.com/itc
epcc runbooks run misc get-store-info
```

### Retries vs Ignoring Errors

Retries in epcc-cli will retry the _exact_ rendered request so if you are using templated parameters i.e., `auto-fill` and the failure is deterministic (say a unique constraint),
a retry will just get stuck in a loop. In this case if you want to create many different records, you want to `--ignore-errors`.

## Development Tips

### Fast rebuilds
Expand Down
11 changes: 6 additions & 5 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,12 @@ func NewCreateCommand(parentCmd *cobra.Command) func() {
})
} else { // This is an attribute value
return completion.Complete(completion.Request{
Type: completion.CompleteAttributeValue,
Resource: resource,
Verb: completion.Create,
Attribute: args[len(args)-1],
ToComplete: toComplete,
Type: completion.CompleteAttributeValue,
Resource: resource,
Verb: completion.Create,
Attribute: args[len(args)-1],
ToComplete: toComplete,
AllowTemplates: true,
})
}
} else {
Expand Down
11 changes: 6 additions & 5 deletions cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,12 @@ func NewDeleteCommand(parentCmd *cobra.Command) func() {
})
} else { // This is an attribute value
return completion.Complete(completion.Request{
Type: completion.CompleteAttributeValue,
Resource: resource,
Verb: completion.Delete,
Attribute: args[len(args)-1],
ToComplete: toComplete,
Type: completion.CompleteAttributeValue,
Resource: resource,
Verb: completion.Delete,
Attribute: args[len(args)-1],
ToComplete: toComplete,
AllowTemplates: true,
})
}
}
Expand Down
11 changes: 6 additions & 5 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,11 +428,12 @@ var loginAccountManagement = &cobra.Command{
})
} else {
return completion.Complete(completion.Request{
Type: completion.CompleteAttributeValue,
Verb: completion.Create,
Resource: res,
Attributes: usedAttributes,
ToComplete: toComplete,
Type: completion.CompleteAttributeValue,
Verb: completion.Create,
Resource: res,
Attributes: usedAttributes,
ToComplete: toComplete,
AllowTemplates: true,
})
}

Expand Down
3 changes: 0 additions & 3 deletions cmd/repeater.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ func repeater(c func(*cobra.Command, []string) error, repeat, repeatDelay uint32
if ignoreErrors {
log.Debugf("Ignored error %v", ignoreErrors)
} else {
if repeat > 1 && !ignoreErrors {
log.Infof("if you want to continue in the face of errors use the --ignore-errors")
}
return err
}
}
Expand Down
5 changes: 1 addition & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ func InitializeCmd() {
RootCmd.PersistentFlags().StringVarP(&profileNameFromCommandLine, "profile", "P", "", "overrides the current EPCC_PROFILE var to run the command with the chosen profile.")
RootCmd.PersistentFlags().Uint16VarP(&rateLimit, "rate-limit", "", 10, "Request limit per second")
RootCmd.PersistentFlags().BoolVarP(&httpclient.Retry5xx, "retry-5xx", "", false, "Whether we should retry requests with HTTP 5xx response code")
RootCmd.PersistentFlags().BoolVarP(&httpclient.Retry4xx, "retry-4xx", "", false, "Whether we should retry requests with HTTP 4xx response code")
RootCmd.PersistentFlags().BoolVarP(&httpclient.Retry429, "retry-429", "", false, "Whether we should retry requests with HTTP 429 response code")
RootCmd.PersistentFlags().BoolVarP(&httpclient.RetryConnectionErrors, "retry-connection-errors", "", false, "Whether we should retry requests with connection errors")
RootCmd.PersistentFlags().UintVarP(&httpclient.RetryDelay, "retry-delay", "", 500, "When retrying how long should we delay")
Expand Down Expand Up @@ -287,13 +286,11 @@ func Execute() {
<-shutdownHandlerDone

if err != nil {

log.Errorf("Error occurred while processing command: %s", err)

os.Exit(1)
} else {

//os.Exit(0)
os.Exit(0)
}
}

Expand Down
4 changes: 0 additions & 4 deletions cmd/runbooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,7 @@ func initRunbookShowCommands() *cobra.Command {

for _, line := range rawCmdLines {
if len(strings.Trim(line, " \n")) > 0 {
//if i <= 10 {
println(line)
//}
}

}
Expand Down Expand Up @@ -422,9 +420,7 @@ func processRunbookVariablesOnCommand(runbookActionRunActionCommand *cobra.Comma
log.Errorf("Could not set flag as required, this is a bug of some kind %s: %v", key, err)
}
} else {

description := ""

if variable.Description != nil {
description = variable.Description.Short
}
Expand Down
11 changes: 6 additions & 5 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,12 @@ func NewUpdateCommand(parentCmd *cobra.Command) func() {
})
} else { // This is an attribute value
return completion.Complete(completion.Request{
Type: completion.CompleteAttributeValue,
Resource: resource,
Verb: completion.Update,
Attribute: args[len(args)-1],
ToComplete: toComplete,
Type: completion.CompleteAttributeValue,
Resource: resource,
Verb: completion.Update,
Attribute: args[len(args)-1],
ToComplete: toComplete,
AllowTemplates: true,
})
}
} else {
Expand Down
79 changes: 77 additions & 2 deletions external/completion/completion.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package completion

import (
"fmt"
"github.com/elasticpath/epcc-cli/external/aliases"
"github.com/elasticpath/epcc-cli/external/resources"
"github.com/spf13/cobra"
"os"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -48,8 +50,9 @@ type Request struct {
QueryParam string
Header string
// The current string argument being completed
ToComplete string
NoAliases bool
ToComplete string
NoAliases bool
AllowTemplates bool
}

func Complete(c Request) ([]string, cobra.ShellCompDirective) {
Expand Down Expand Up @@ -294,7 +297,79 @@ func Complete(c Request) ([]string, cobra.ShellCompDirective) {
}
results = append(results, supportedFileTypes...)
}
}

if c.AllowTemplates {
lastPipe := strings.LastIndex(c.ToComplete, "|")
prefix := ""
if lastPipe == -1 {
prefix = "{{ "
} else {
prefix = c.ToComplete[0:lastPipe+1] + " "
}

myResults := []string{}
myResults = append(myResults,
prefix+"date",
prefix+"now",
prefix+"randAlphaNum",
prefix+"randAlpha",
prefix+"randAscii",
prefix+"randNumeric",
prefix+"randAlphaNum",
prefix+"randAlpha",
prefix+"randAscii",
prefix+"randNumeric",
prefix+"pseudoRandAlphaNum",
prefix+"pseudoRandAlpha",
prefix+"pseudoRandNumeric",
prefix+"pseudoRandString",
prefix+"pseudoRandInt",
prefix+"uuidv4",
prefix+"duration",
)

if prefix != "{{ " {
// Functions that make sense as continuations
myResults = append(myResults,
prefix+"trim",
prefix+"trimAll",
prefix+"trimSuffix",
prefix+"trimPrefix",
prefix+"upper",
prefix+"lower",
prefix+"title",
prefix+"repeat",
prefix+"substr",
prefix+"nospace",
prefix+"trunc",
prefix+"abbrev",
prefix+"initials",
prefix+"wrap",
prefix+"cat",
prefix+"replace",
prefix+"snakecase",
prefix+"camelcase",
prefix+"kebabcase",
prefix+"swapcase",
prefix+"shufflecase",
)
}

re := regexp.MustCompile(`env\s+[A-Za-z]*\s*$`)
if re.MatchString(c.ToComplete) {
for _, v := range os.Environ() {
myResults = append(myResults,
fmt.Sprintf("%venv \"%v\"", prefix, strings.Split(v, "=")[0]),
)
}
} else {
myResults = append(myResults, prefix+"env")
}
//myResults = append(myResults, strings.TrimSuffix(c.ToComplete, " ")+" }}", strings.TrimSuffix(c.ToComplete, " ")+" |")
for _, r := range myResults {
results = append(results, r+" |", r+" }}")
}
}
}
}
Expand Down
1 change: 0 additions & 1 deletion external/httpclient/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ func Initialize(rateLimit uint16, requestTimeout float32, statisticsFrequency in

var Retry429 = false
var Retry5xx = false
var Retry4xx = false

var RetryConnectionErrors = false

Expand Down
7 changes: 6 additions & 1 deletion external/json/to_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ func formatValue(v string) string {
} else if match, _ := regexp.MatchString("^\\[\\]$", v); match {
return v
} else {
return fmt.Sprintf("\"%s\"", strings.ReplaceAll(v, `"`, `\"`))
v = strings.ReplaceAll(v, "\\", "\\\\")
v = strings.ReplaceAll(v, "\n", "\\n")
v = strings.ReplaceAll(v, `"`, `\"`)

return fmt.Sprintf("\"%s\"", v)
//return fmt.Sprintf("\"%s\"", v)
}
}
45 changes: 45 additions & 0 deletions external/json/to_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,51 @@ func TestToJsonLegacyFormatSimpleKeyStringValue(t *testing.T) {
}
}

func TestToJsonLegacyFormatSimpleKeyStringValueWithQuotes(t *testing.T) {
// Fixture Setup
input := []string{"key", "val\"ue"}
expected := `{"data":{"key":"val\"ue"}}`

// Execute SUT
actual, err := ToJson(input, false, false, map[string]*resources.CrudEntityAttribute{}, true)

// Verification
require.NoError(t, err)
if actual != expected {
t.Fatalf("Testing json conversion of empty value %s did not match expected %s, actually: %s", input, expected, actual)
}
}

func TestToJsonLegacyFormatSimpleKeyStringValueWithNewLines(t *testing.T) {
// Fixture Setup
input := []string{"key", "val\nue"}
expected := `{"data":{"key":"val\nue"}}`

// Execute SUT
actual, err := ToJson(input, false, false, map[string]*resources.CrudEntityAttribute{}, true)

// Verification
require.NoError(t, err)
if actual != expected {
t.Fatalf("Testing json conversion of empty value %s did not match expected %s, actually: %s", input, expected, actual)
}
}

func TestToJsonLegacyFormatSimpleKeyStringValueWithBackslashes(t *testing.T) {
// Fixture Setup
input := []string{"key", "val\\nue"}
expected := `{"data":{"key":"val\\nue"}}`

// Execute SUT
actual, err := ToJson(input, false, false, map[string]*resources.CrudEntityAttribute{}, true)

// Verification
require.NoError(t, err)
if actual != expected {
t.Fatalf("Testing json conversion of empty value %s did not match expected %s, actually: %s", input, expected, actual)
}
}

func TestToJsonLegacyFormatSimpleNestedKeyValue(t *testing.T) {
// Fixture Setup
input := []string{"foo.bar", "val"}
Expand Down
Loading

0 comments on commit 03f6784

Please sign in to comment.