Skip to content

Commit

Permalink
cli: remove fancy eval delete flags
Browse files Browse the repository at this point in the history
  • Loading branch information
jrasell committed Jun 29, 2022
1 parent d54ad19 commit 1ef732e
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 242 deletions.
135 changes: 16 additions & 119 deletions command/eval_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,14 @@ import (

"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/api/contexts"
flaghelper "github.com/hashicorp/nomad/helper/flags"
"github.com/posener/complete"
)

type EvalDeleteCommand struct {
Meta

scheduler flaghelper.StringFlag
status flaghelper.StringFlag
job flaghelper.StringFlag
deployment flaghelper.StringFlag
node flaghelper.StringFlag
filterOp string
yes bool
filter string
yes bool

// originalBrokerPaused indicates whether the broker was in a paused state
// before the command was run. This indicates what action, if any, should
Expand All @@ -47,7 +41,7 @@ func (e *EvalDeleteCommand) Help() string {
Usage: nomad eval delete [options] <evaluation>
Delete an evaluation by ID. If the evaluation ID is omitted, this command
will use the filter options below to delete a set of evaluations. If ACLs
will use the filter flag to identify and delete a set of evaluations. If ACLs
are enabled, this command requires a management ACL token.
This command should be used cautiously and only in outage situations where
Expand All @@ -61,29 +55,10 @@ General Options:
Eval Delete Options:
-scheduler
Filter by scheduler type (one of "batch", "service", "system", or
"sysbatch"). This option may be specified multiple times.
-status
Filter by eval status (one of "blocked", "pending", "complete",
"failed", or "canceled"). This option may be specified multiple times.
-job
Filter by an associated job ID. This option may be specified
multiple times.
-deployment
Filter by an associated deployment ID. This option may be specified
multiple times.
-node
Filter by an associated node ID. This option may be specified
multiple times.
-filter-operator
The filter operator to use when multiple filter options are specified. This
defaults to "and" but also supports "or".
-filter
Specifies an expression used to filter evaluations by for deletion. When
using this flag, it is advisable to ensure the syntax is correct using the
eval list command first.
-yes
Bypass the confirmation prompt if an evaluation ID was not provided.
Expand All @@ -93,24 +68,14 @@ Eval Delete Options:
}

func (e *EvalDeleteCommand) Synopsis() string {
return "Delete evaluations by ID or using filters"
return "Delete evaluations by ID or using a filter"
}

func (e *EvalDeleteCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(e.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-scheduler": complete.PredictSet(
api.JobTypeService,
api.JobTypeBatch,
api.JobTypeSystem,
api.JobTypeSysBatch,
),
"-status": complete.PredictAnything,
"-job": complete.PredictAnything,
"-deployment": complete.PredictAnything,
"-node": complete.PredictAnything,
"-filter-operator": complete.PredictSet("and", "or"),
"-yes": complete.PredictNothing,
"-filter": complete.PredictAnything,
"-yes": complete.PredictNothing,
})
}

Expand All @@ -135,12 +100,7 @@ func (e *EvalDeleteCommand) Run(args []string) int {

flags := e.Meta.FlagSet(e.Name(), FlagSetClient)
flags.Usage = func() { e.Ui.Output(e.Help()) }
flags.Var(&e.scheduler, "scheduler", "")
flags.Var(&e.status, "status", "")
flags.Var(&e.job, "job", "")
flags.Var(&e.deployment, "deployment", "")
flags.Var(&e.node, "node", "")
flags.StringVar(&e.filterOp, "filter-operator", "and", "")
flags.StringVar(&e.filter, "filter", "", "")
flags.BoolVar(&e.yes, "yes", false, "")
if err := flags.Parse(args); err != nil {
return 1
Expand All @@ -153,11 +113,6 @@ func (e *EvalDeleteCommand) Run(args []string) int {
return 1
}

// Pad the filter operator with whitespace, so it can be used as is when
// building the filter. This transforms "and" to " and ".
originalOP := e.filterOp
e.filterOp = " " + originalOP + " "

// Get the HTTP client and store this for use across multiple functions.
client, err := e.Meta.Client()
if err != nil {
Expand All @@ -184,7 +139,7 @@ func (e *EvalDeleteCommand) Run(args []string) int {
e.deleteByArg = true
exitCode, err = e.handleEvalArgDelete(args[0])
default:
exitCode, err = e.handleFlagFilterDelete("", e.buildFilter())
exitCode, err = e.handleFlagFilterDelete("", e.filter)
}

// Do not exit if we got an error as it's possible this was on the
Expand Down Expand Up @@ -220,25 +175,17 @@ func (e *EvalDeleteCommand) Run(args []string) int {
func (e *EvalDeleteCommand) verifyArgsAndFlags(args []string) error {

numArgs := len(args)
numFlags := len(e.scheduler) + len(e.status) + len(e.job) + len(e.deployment) + len(e.node)

// The command takes either an argument or flags, but not both.
if (numFlags < 1 && numArgs < 1) || (numFlags > 0 && numArgs > 0) {
return errors.New("evaluation ID or filter flags required")
// The command takes either an argument or filter, but not both.
if (e.filter == "" && numArgs < 1) || (e.filter != "" && numArgs > 0) {
return errors.New("evaluation ID or filter flag required")
}

// If an argument is supplied, we only accept a single eval ID.
if numArgs > 1 {
return fmt.Errorf("expected 1 argument, got %v", numArgs)
}

// Ensure the filter operator matches supported and expected values.
switch e.filterOp {
case "and", "or":
default:
return fmt.Errorf(`got filter-operator %q, supports "and" "or"`, e.filterOp)
}

return nil
}

Expand Down Expand Up @@ -316,7 +263,7 @@ func (e *EvalDeleteCommand) handleEvalArgDelete(evalID string) (int, error) {
}

// handleFlagFilterDelete handles deletion of evaluations discovered using
// filter flags. It is unknown how many will match the operator criteria so
// the filter. It is unknown how many will match the operator criteria so
// this function batches lookup and delete requests into sensible numbers.
//
// The function is recursive and will run until it has found and deleted all
Expand Down Expand Up @@ -391,56 +338,6 @@ func (e *EvalDeleteCommand) handleFlagFilterDelete(nextToken, filter string) (in
return 0, nil
}

// buildFilter is used to create an API filter string based on the operator
// input flags. This function should be called once, and reused when required
// for some form of efficiency.
func (e *EvalDeleteCommand) buildFilter() string {

var filters []string

if len(e.scheduler) > 0 {
filters = append(filters, joinStringFilter(e.scheduler, "Type ==", e.filterOp))
}
if len(e.status) > 0 {
filters = append(filters, joinStringFilter(e.status, "Status ==", e.filterOp))
}
if len(e.job) > 0 {
filters = append(filters, joinStringFilter(e.job, "JobID ==", e.filterOp))
}
if len(e.deployment) > 0 {
filters = append(filters, joinStringFilter(e.deployment, "DeploymentID ==", e.filterOp))
}
if len(e.node) > 0 {
filters = append(filters, joinStringFilter(e.node, "NodeID ==", e.filterOp))
}

return strings.Join(filters, e.filterOp)
}

// joinStringFilter is a modified version of the strings.Join function which is
// specific to joining the input flags together to create a single go-bexpr
// filter. It does not handle empty inputs and should be performed by the
// caller.
func joinStringFilter(elems []string, selector, sep string) string {
switch len(elems) {
case 1:
return fmt.Sprintf("%s %q", selector, elems[0])
}
n := len(sep) * (len(elems) - 1)
for i := 0; i < len(elems); i++ {
n += len(elems[i])
}

var b strings.Builder
b.Grow(n)
b.WriteString(fmt.Sprintf("%s %q", selector, elems[0]))
for _, s := range elems[1:] {
b.WriteString(sep)
b.WriteString(fmt.Sprintf("%s %q", selector, s))
}
return b.String()
}

// batchDelete is responsible for deleting the passed evaluations and asking
// any confirmation questions along the way. It will ask whether the operator
// want to list the evals before deletion, and optionally ask for confirmation
Expand Down
107 changes: 5 additions & 102 deletions command/eval_delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,6 @@ func TestEvalDeleteCommand_Run(t *testing.T) {
ui.ErrorWriter.Reset()
ui.OutputWriter.Reset()

// Try deleting evals using a filter, which doesn't match any
// evals found in Nomad.
require.Equal(t, 1, cmd.Run([]string{"-address=" + url, "-scheduler=service"}))
require.Contains(t, ui.ErrorWriter.String(), "failed to find any evals that matched filter criteria")
ui.ErrorWriter.Reset()
ui.OutputWriter.Reset()

// Ensure the scheduler config broker is un-paused.
schedulerConfig, _, err := client.Operator().SchedulerGetConfiguration(nil)
require.NoError(t, err)
Expand Down Expand Up @@ -77,53 +70,28 @@ func TestEvalDeleteCommand_verifyArgsAndFlags(t *testing.T) {
}{
{
inputEvalDeleteCommand: &EvalDeleteCommand{
scheduler: []string{"batch", "service"},
status: []string{"pending", "complete"},
job: []string{"example", "another"},
deployment: []string{"fake-dep-1", "fake-dep-2"},
node: []string{"fake-node-1", "fake-node-2"},
filter: `Status == "Pending"`,
},
inputArgs: []string{"fa3a8c37-eac3-00c7-3410-5ba3f7318fd8"},
expectedError: errors.New("evaluation ID or filter flags required"),
expectedError: errors.New("evaluation ID or filter flag required"),
name: "arg and flags",
},
{
inputEvalDeleteCommand: &EvalDeleteCommand{
scheduler: []string{},
status: []string{},
job: []string{},
deployment: []string{},
node: []string{},
filter: "",
},
inputArgs: []string{},
expectedError: errors.New("evaluation ID or filter flags required"),
expectedError: errors.New("evaluation ID or filter flag required"),
name: "no arg or flags",
},
{
inputEvalDeleteCommand: &EvalDeleteCommand{
scheduler: []string{},
status: []string{},
job: []string{},
deployment: []string{},
node: []string{},
filter: "",
},
inputArgs: []string{"fa3a8c37-eac3-00c7-3410-5ba3f7318fd8", "fa3a8c37-eac3-00c7-3410-5ba3f7318fd9"},
expectedError: errors.New("expected 1 argument, got 2"),
name: "multiple args",
},
{
inputEvalDeleteCommand: &EvalDeleteCommand{
scheduler: []string{},
status: []string{},
job: []string{"example"},
deployment: []string{},
node: []string{},
filterOp: "everything",
},
inputArgs: []string{},
expectedError: errors.New(`got filter-operator "everything", supports "and" "or"`),
name: "incorrect filter-operator",
},
}

for _, tc := range testCases {
Expand All @@ -133,68 +101,3 @@ func TestEvalDeleteCommand_verifyArgsAndFlags(t *testing.T) {
})
}
}

func TestEvalDeleteCommand_buildFilter(t *testing.T) {
ci.Parallel(t)

testCases := []struct {
inputEvalDeleteCommand *EvalDeleteCommand
expectedOutput string
name string
}{
{
inputEvalDeleteCommand: &EvalDeleteCommand{
scheduler: []string{"batch", "service"},
status: []string{"pending", "complete"},
job: []string{"example", "another"},
deployment: []string{"fake-dep-1", "fake-dep-2"},
node: []string{"fake-node-1", "fake-node-2"},
filterOp: " or ",
},
expectedOutput: `Type == "batch" or Type == "service" or Status == "pending" or Status == "complete" or JobID == "example" or JobID == "another" or DeploymentID == "fake-dep-1" or DeploymentID == "fake-dep-2" or NodeID == "fake-node-1" or NodeID == "fake-node-2"`,
name: "all filter flags with multiple entries",
},
{
inputEvalDeleteCommand: &EvalDeleteCommand{
scheduler: []string{"batch", "service"},
status: []string{},
job: []string{"example", "another"},
deployment: []string{},
node: []string{"fake-node-1", "fake-node-2"},
filterOp: " or ",
},
expectedOutput: `Type == "batch" or Type == "service" or JobID == "example" or JobID == "another" or NodeID == "fake-node-1" or NodeID == "fake-node-2"`,
name: "some filter flags with multiple entries",
},
{
inputEvalDeleteCommand: &EvalDeleteCommand{
scheduler: []string{},
status: []string{},
job: []string{},
deployment: []string{},
node: []string{},
},
expectedOutput: "",
name: "no filters",
},
{
inputEvalDeleteCommand: &EvalDeleteCommand{
scheduler: []string{},
status: []string{"pending"},
job: []string{"example"},
deployment: []string{},
node: []string{},
filterOp: " and ",
},
expectedOutput: `Status == "pending" and JobID == "example"`,
name: "using and filter operation",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutput := tc.inputEvalDeleteCommand.buildFilter()
require.Equal(t, tc.expectedOutput, actualOutput)
})
}
}
Loading

0 comments on commit 1ef732e

Please sign in to comment.