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

add prefix-search and auto-completion for scaling policy info command #9964

Merged
merged 6 commits into from
Feb 4, 2021
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ FEATURES:
* **Terminating Gateways**: Adds built-in support for running Consul Connect terminating gateways [[GH-9829](https://github.com/hashicorp/nomad/pull/9829)]

IMPROVEMENTS:
* cli: Improved `scaling policy` commands with -verbose, auto-completion, and prefix-matching [[GH-9964](https://github.com/hashicorp/nomad/issues/9964)]
* consul/connect: Made handling of sidecar task container image URLs consistent with the `docker` task driver. [[GH-9580](https://github.com/hashicorp/nomad/issues/9580)]


BUG FIXES:
* consul: Fixed a bug where failing tasks with group services would only cause the allocation to restart once instead of respecting the `restart` field. [[GH-9869](https://github.com/hashicorp/nomad/issues/9869)]
* consul/connect: Fixed a bug where gateway proxy connection default timeout not set [[GH-9851](https://github.com/hashicorp/nomad/pull/9851)]
Expand Down
1 change: 1 addition & 0 deletions api/contexts/contexts.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
Namespaces Context = "namespaces"
Quotas Context = "quotas"
Recommendations Context = "recommendations"
ScalingPolicies Context = "scaling_policy"
Plugins Context = "plugins"
Volumes Context = "volumes"
All Context = "all"
Expand Down
2 changes: 1 addition & 1 deletion command/alloc_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (c *AllocStatusCommand) Run(args []string) int {
}

// If args not specified but output format is specified, format and output the allocations data list
if len(args) == 0 && json || len(tmpl) > 0 {
if len(args) == 0 && (json || len(tmpl) > 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only want to do this if there were no args... previous parsing would go into list mode with args if template was specified, which i think was not the intended behavior. same below for eval status and job inspect.

allocs, _, err := client.Allocations().List(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error querying allocations: %v", err))
Expand Down
2 changes: 1 addition & 1 deletion command/eval_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (c *EvalStatusCommand) Run(args []string) int {
}

// If args not specified but output format is specified, format and output the evaluations data list
if len(args) == 0 && json || len(tmpl) > 0 {
if len(args) == 0 && (json || len(tmpl) > 0) {
evals, _, err := client.Evaluations().List(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error querying evaluations: %v", err))
Expand Down
2 changes: 1 addition & 1 deletion command/job_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (c *JobInspectCommand) Run(args []string) int {
}

// If args not specified but output format is specified, format and output the jobs data list
if len(args) == 0 && json || len(tmpl) > 0 {
if len(args) == 0 && (json || len(tmpl) > 0) {
jobs, _, err := client.Jobs().List(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error querying jobs: %v", err))
Expand Down
110 changes: 90 additions & 20 deletions command/scaling_policy_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (

"github.com/mitchellh/cli"
"github.com/posener/complete"

"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/api/contexts"
)

// Ensure ScalingPolicyInfoCommand satisfies the cli.Command interface.
Expand All @@ -32,6 +35,9 @@ General Options:

Policy Info Options:

-verbose
Display full information.

-json
Output the scaling policy in its JSON format.

Expand All @@ -49,58 +55,119 @@ func (s *ScalingPolicyInfoCommand) Synopsis() string {
func (s *ScalingPolicyInfoCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(s.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-json": complete.PredictNothing,
"-t": complete.PredictAnything,
"-verbose": complete.PredictNothing,
"-json": complete.PredictNothing,
"-t": complete.PredictAnything,
})
}

func (c *ScalingPolicyInfoCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictFunc(func(a complete.Args) []string {
client, err := c.Meta.Client()
if err != nil {
return nil
}

resp, _, err := client.Search().PrefixSearch(a.Last, contexts.ScalingPolicies, nil)
if err != nil {
return []string{}
}
return resp.Matches[contexts.ScalingPolicies]
})
}

lgfa29 marked this conversation as resolved.
Show resolved Hide resolved
// Name returns the name of this command.
func (s *ScalingPolicyInfoCommand) Name() string { return "scaling policy info" }

// Run satisfies the cli.Command Run function.
func (s *ScalingPolicyInfoCommand) Run(args []string) int {
var json bool
var json, verbose bool
var tmpl string

flags := s.Meta.FlagSet(s.Name(), FlagSetClient)
flags.Usage = func() { s.Ui.Output(s.Help()) }
flags.BoolVar(&verbose, "verbose", false, "")
flags.BoolVar(&json, "json", false, "")
flags.StringVar(&tmpl, "t", "", "")
if err := flags.Parse(args); err != nil {
return 1
}

if args = flags.Args(); len(args) != 1 {
s.Ui.Error("This command takes one argument: <policy_id>")
s.Ui.Error(commandErrorText(s))
return 1
// Truncate the id unless full length is requested
length := shortId
if verbose {
length = fullId
}

// Get the policy ID.
policyID := args[0]

// Get the HTTP client.
client, err := s.Meta.Client()
if err != nil {
s.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
return 1
}

policy, _, err := client.Scaling().GetPolicy(policyID, nil)
args = flags.Args()

// Formatted list mode if no policy ID
if len(args) == 0 && (json || len(tmpl) > 0) {
policies, _, err := client.Scaling().ListPolicies(nil)
if err != nil {
s.Ui.Error(fmt.Sprintf("Error listing scaling policies: %v", err))
return 1
}
out, err := Format(json, tmpl, policies)
if err != nil {
s.Ui.Error(err.Error())
return 1
}
s.Ui.Output(out)
return 0
}

if len(args) != 1 {
s.Ui.Error("This command takes one of the following argument conditions:")
s.Ui.Error(" * A single <policy_id>")
s.Ui.Error(" * No arguments, with output format specified")
s.Ui.Error(commandErrorText(s))
return 1
}
policyID := args[0]
if len(policyID) == 1 {
s.Ui.Error("Identifier must contain at least two characters.")
return 1
}

policyID = sanitizeUUIDPrefix(policyID)
policies, _, err := client.Scaling().ListPolicies(&api.QueryOptions{
Prefix: policyID,
})
if err != nil {
s.Ui.Error(fmt.Sprintf("Error listing scaling policies: %s", err))
s.Ui.Error(fmt.Sprintf("Error querying scaling policy: %v", err))
return 1
}
if len(policies) == 0 {
s.Ui.Error(fmt.Sprintf("No scaling policies with prefix or id %q found", policyID))
return 1
}
if len(policies) > 1 {
out := formatScalingPolicies(policies, length)
s.Ui.Error(fmt.Sprintf("Prefix matched multiple scaling policies\n\n%s", out))
return 0
}

policy, _, err := client.Scaling().GetPolicy(policies[0].ID, nil)
if err != nil {
s.Ui.Error(fmt.Sprintf("Error querying scaling policy: %s", err))
return 1
}

// If the user has specified to output the policy as JSON or using a
// template then perform this action for the entire object and exit the
// command.
if json || len(tmpl) > 0 {
out, err := Format(json, tmpl, policy)
if err != nil {
s.Ui.Error(err.Error())
return 1
}

s.Ui.Output(out)
return 0
}
Expand All @@ -109,14 +176,17 @@ func (s *ScalingPolicyInfoCommand) Run(args []string) int {
// and therefore can only be made pretty to a certain extent. Do this
// before the rest of the formatting so any errors are clearly passed back
// to the CLI.
out, err := Format(true, "", policy.Policy)
if err != nil {
s.Ui.Error(err.Error())
return 1
out := "<empty>"
if len(policy.Policy) > 0 {
out, err = Format(true, "", policy.Policy)
if err != nil {
s.Ui.Error(err.Error())
return 1
}
}

info := []string{
fmt.Sprintf("ID|%s", policy.ID),
fmt.Sprintf("ID|%s", limit(policy.ID, length)),
fmt.Sprintf("Enabled|%v", *policy.Enabled),
fmt.Sprintf("Target|%s", formatScalingPolicyTarget(policy.Target)),
fmt.Sprintf("Min|%v", *policy.Min),
Expand Down
22 changes: 19 additions & 3 deletions command/scaling_policy_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,24 @@ func TestScalingPolicyInfoCommand_Run(t *testing.T) {
if code := cmd.Run([]string{"-address=" + url}); code != 1 {
t.Fatalf("expected cmd run exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "This command takes one argument: <policy_id>") {
if out := ui.ErrorWriter.String(); !strings.Contains(out, "This command takes one of the following argument conditions") {
t.Fatalf("expected argument error within output: %v", out)
}

// Calling with more than one argument should result in an error.
if code := cmd.Run([]string{"-address=" + url, "first", "second"}); code != 1 {
t.Fatalf("expected cmd run exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "This command takes one of the following argument conditions") {
t.Fatalf("expected argument error within output: %v", out)
}

// Perform an initial info, which should return zero results.
if code := cmd.Run([]string{"-address=" + url, "scaling_policy_info"}); code != 1 {
t.Fatalf("expected cmd run exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "404 (policy not found)") {
t.Fatalf("expected 404 not found within output: %v", out)
if out := ui.ErrorWriter.String(); !strings.Contains(out, `No scaling policies with prefix or id "scaling_policy_inf" found`) {
t.Fatalf("expected 'no policies found' within output: %v", out)
}

// Generate a test job.
Expand Down Expand Up @@ -85,4 +93,12 @@ func TestScalingPolicyInfoCommand_Run(t *testing.T) {
if out := ui.OutputWriter.String(); !strings.Contains(out, "Policy:") {
t.Fatalf("expected policy ID within output: %v", out)
}

prefix := policies[0].ID[:2]
if code := cmd.Run([]string{"-address=" + url, prefix}); code != 0 {
t.Fatalf("expected cmd run exit code 0, got: %d", code)
}
if out := ui.OutputWriter.String(); !strings.Contains(out, "Policy:") {
t.Fatalf("expected policy ID within output: %v", out)
}
}
53 changes: 35 additions & 18 deletions command/scaling_policy_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ Policy Info Options:
-type
Filter scaling policies by type.

-verbose
Display full information.

-json
Output the scaling policy in its JSON format.

Expand All @@ -59,10 +62,11 @@ func (s *ScalingPolicyListCommand) Synopsis() string {
func (s *ScalingPolicyListCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(s.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-job": complete.PredictNothing,
"-type": complete.PredictNothing,
"-json": complete.PredictNothing,
"-t": complete.PredictAnything,
"-verbose": complete.PredictNothing,
"-job": complete.PredictNothing,
"-type": complete.PredictNothing,
"-json": complete.PredictNothing,
"-t": complete.PredictAnything,
})
}

Expand All @@ -71,11 +75,12 @@ func (s *ScalingPolicyListCommand) Name() string { return "scaling policy list"

// Run satisfies the cli.Command Run function.
func (s *ScalingPolicyListCommand) Run(args []string) int {
var json bool
var json, verbose bool
var tmpl, policyType, job string

flags := s.Meta.FlagSet(s.Name(), FlagSetClient)
flags.Usage = func() { s.Ui.Output(s.Help()) }
flags.BoolVar(&verbose, "verbose", false, "")
flags.BoolVar(&json, "json", false, "")
flags.StringVar(&tmpl, "t", "", "")
flags.StringVar(&policyType, "type", "", "")
Expand All @@ -87,6 +92,13 @@ func (s *ScalingPolicyListCommand) Run(args []string) int {
if args = flags.Args(); len(args) > 0 {
s.Ui.Error("This command takes no arguments")
s.Ui.Error(commandErrorText(s))
return 1
}

// Truncate the id unless full length is requested
length := shortId
if verbose {
length = fullId
}

// Get the HTTP client.
Expand All @@ -111,11 +123,6 @@ func (s *ScalingPolicyListCommand) Run(args []string) int {
return 1
}

if len(policies) == 0 {
s.Ui.Output("No policies found")
return 0
}

if json || len(tmpl) > 0 {
out, err := Format(json, tmpl, policies)
if err != nil {
Expand All @@ -126,23 +133,33 @@ func (s *ScalingPolicyListCommand) Run(args []string) int {
return 0
}

output := formatScalingPolicies(policies, length)
s.Ui.Output(output)
return 0
}

func formatScalingPolicies(stubs []*api.ScalingPolicyListStub, uuidLength int) string {
if len(stubs) == 0 {
return "No policies found"
}

// Create the output table header.
output := []string{"ID|Enabled|Type|Target"}
policies := []string{"ID|Enabled|Type|Target"}

// Sort the list of policies based on their target.
sortedPolicies := scalingPolicyStubList{policies: policies}
sortedPolicies := scalingPolicyStubList{policies: stubs}
sort.Sort(sortedPolicies)

// Iterate the policies and add to the output.
for _, policy := range sortedPolicies.policies {
output = append(output, fmt.Sprintf(
policies = append(policies, fmt.Sprintf(
"%s|%v|%s|%s",
policy.ID, policy.Enabled, policy.Type, formatScalingPolicyTarget(policy.Target)))
limit(policy.ID, uuidLength),
policy.Enabled,
policy.Type,
formatScalingPolicyTarget(policy.Target)))
}

// Output.
s.Ui.Output(formatList(output))
return 0
return formatList(policies)
}

// scalingPolicyStubList is a wrapper around []*api.ScalingPolicyListStub that
Expand Down
5 changes: 5 additions & 0 deletions nomad/search_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
structs.Deployments,
structs.Plugins,
structs.Volumes,
structs.ScalingPolicies,
structs.Namespaces,
}
)
Expand Down Expand Up @@ -68,6 +69,8 @@ func (s *Search) getMatches(iter memdb.ResultIterator, prefix string) ([]string,
id = t.ID
case *structs.CSIVolume:
id = t.ID
case *structs.ScalingPolicy:
id = t.ID
case *structs.Namespace:
id = t.Name
default:
Expand Down Expand Up @@ -106,6 +109,8 @@ func getResourceIter(context structs.Context, aclObj *acl.ACL, namespace, prefix
return state.DeploymentsByIDPrefix(ws, namespace, prefix)
case structs.Plugins:
return state.CSIPluginsByIDPrefix(ws, prefix)
case structs.ScalingPolicies:
return state.ScalingPoliciesByIDPrefix(ws, namespace, prefix)
case structs.Volumes:
return state.CSIVolumesByIDPrefix(ws, namespace, prefix)
case structs.Namespaces:
Expand Down
Loading