diff --git a/api/contexts/contexts.go b/api/contexts/contexts.go index ae40db3f8..ea4ea1309 100644 --- a/api/contexts/contexts.go +++ b/api/contexts/contexts.go @@ -4,14 +4,15 @@ package contexts type Context string const ( - Allocs Context = "allocs" - Deployments Context = "deployment" - Evals Context = "evals" - Jobs Context = "jobs" - Nodes Context = "nodes" - Namespaces Context = "namespaces" - Quotas Context = "quotas" - Plugins Context = "plugins" - Volumes Context = "volumes" - All Context = "all" + Allocs Context = "allocs" + Deployments Context = "deployment" + Evals Context = "evals" + Jobs Context = "jobs" + Nodes Context = "nodes" + Namespaces Context = "namespaces" + Quotas Context = "quotas" + Recommendations Context = "recommendations" + Plugins Context = "plugins" + Volumes Context = "volumes" + All Context = "all" ) diff --git a/command/recommendation_apply.go b/command/recommendation_apply.go index 980746b4d..ee6b31674 100644 --- a/command/recommendation_apply.go +++ b/command/recommendation_apply.go @@ -5,6 +5,8 @@ import ( "strings" "github.com/hashicorp/nomad/api" + "github.com/hashicorp/nomad/api/contexts" + "github.com/mitchellh/cli" "github.com/posener/complete" ) @@ -60,6 +62,21 @@ func (r *RecommendationApplyCommand) AutocompleteFlags() complete.Flags { }) } +func (r *RecommendationApplyCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictFunc(func(a complete.Args) []string { + client, err := r.Meta.Client() + if err != nil { + return nil + } + + resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Recommendations, nil) + if err != nil { + return []string{} + } + return resp.Matches[contexts.Recommendations] + }) +} + // Name returns the name of this command. func (r *RecommendationApplyCommand) Name() string { return "recommendation apply" } diff --git a/command/recommendation_apply_test.go b/command/recommendation_apply_test.go index cc8f9e19c..e8656b3d5 100644 --- a/command/recommendation_apply_test.go +++ b/command/recommendation_apply_test.go @@ -7,6 +7,8 @@ import ( "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/testutil" "github.com/mitchellh/cli" + "github.com/posener/complete" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -83,3 +85,46 @@ func TestRecommendationApplyCommand_Run(t *testing.T) { require.NoError(err) require.Equal(1, *jobResp.TaskGroups[0].Tasks[0].Resources.CPU) } + +func TestRecommendationApplyCommand_AutocompleteArgs(t *testing.T) { + assert := assert.New(t) + t.Parallel() + + srv, client, url := testServer(t, true, nil) + defer srv.Shutdown() + + // Register a test job to write a recommendation against. + ui := cli.NewMockUi() + testJob := testJob("recommendation_list") + regResp, _, err := client.Jobs().Register(testJob, nil) + require.NoError(t, err) + registerCode := waitForSuccess(ui, client, fullId, t, regResp.EvalID) + require.Equal(t, 0, registerCode) + + // Write a recommendation. + rec := &api.Recommendation{ + JobID: *testJob.ID, + Group: *testJob.TaskGroups[0].Name, + Task: testJob.TaskGroups[0].Tasks[0].Name, + Resource: "CPU", + Value: 1050, + Meta: map[string]interface{}{"test-meta-entry": "test-meta-value"}, + Stats: map[string]float64{"p13": 1.13}, + } + rec, _, err = client.Recommendations().Upsert(rec, nil) + if srv.Enterprise { + require.NoError(t, err) + } else { + require.Error(t, err, "Nomad Enterprise only endpoint") + return + } + + cmd := &RecommendationApplyCommand{Meta: Meta{Ui: ui, flagAddress: url}} + prefix := rec.ID[:5] + args := complete.Args{Last: prefix} + predictor := cmd.AutocompleteArgs() + + res := predictor.Predict(args) + assert.Equal(1, len(res)) + assert.Equal(rec.ID, res[0]) +} diff --git a/command/recommendation_dismiss.go b/command/recommendation_dismiss.go index bb362cd8b..636ce898f 100644 --- a/command/recommendation_dismiss.go +++ b/command/recommendation_dismiss.go @@ -6,6 +6,8 @@ import ( "github.com/mitchellh/cli" "github.com/posener/complete" + + "github.com/hashicorp/nomad/api/contexts" ) // Ensure RecommendationDismissCommand satisfies the cli.Command interface. @@ -39,6 +41,21 @@ func (r *RecommendationDismissCommand) AutocompleteFlags() complete.Flags { complete.Flags{}) } +func (r *RecommendationDismissCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictFunc(func(a complete.Args) []string { + client, err := r.Meta.Client() + if err != nil { + return nil + } + + resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Recommendations, nil) + if err != nil { + return []string{} + } + return resp.Matches[contexts.Recommendations] + }) +} + // Name returns the name of this command. func (r *RecommendationDismissCommand) Name() string { return "recommendation dismiss" } diff --git a/command/recommendation_dismiss_test.go b/command/recommendation_dismiss_test.go index 77e8f3ba3..ea4a5bc15 100644 --- a/command/recommendation_dismiss_test.go +++ b/command/recommendation_dismiss_test.go @@ -7,6 +7,8 @@ import ( "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/testutil" "github.com/mitchellh/cli" + "github.com/posener/complete" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -80,3 +82,46 @@ func TestRecommendationDismissCommand_Run(t *testing.T) { require.Error(err, "not found") require.Nil(recInfo) } + +func TestRecommendationDismissCommand_AutocompleteArgs(t *testing.T) { + assert := assert.New(t) + t.Parallel() + + srv, client, url := testServer(t, true, nil) + defer srv.Shutdown() + + // Register a test job to write a recommendation against. + ui := cli.NewMockUi() + testJob := testJob("recommendation_list") + regResp, _, err := client.Jobs().Register(testJob, nil) + require.NoError(t, err) + registerCode := waitForSuccess(ui, client, fullId, t, regResp.EvalID) + require.Equal(t, 0, registerCode) + + // Write a recommendation. + rec := &api.Recommendation{ + JobID: *testJob.ID, + Group: *testJob.TaskGroups[0].Name, + Task: testJob.TaskGroups[0].Tasks[0].Name, + Resource: "CPU", + Value: 1050, + Meta: map[string]interface{}{"test-meta-entry": "test-meta-value"}, + Stats: map[string]float64{"p13": 1.13}, + } + rec, _, err = client.Recommendations().Upsert(rec, nil) + if srv.Enterprise { + require.NoError(t, err) + } else { + require.Error(t, err, "Nomad Enterprise only endpoint") + return + } + + cmd := &RecommendationDismissCommand{Meta: Meta{Ui: ui, flagAddress: url}} + prefix := rec.ID[:5] + args := complete.Args{Last: prefix} + predictor := cmd.AutocompleteArgs() + + res := predictor.Predict(args) + assert.Equal(1, len(res)) + assert.Equal(rec.ID, res[0]) +} diff --git a/command/recommendation_info.go b/command/recommendation_info.go index 42c822c02..9045445ab 100644 --- a/command/recommendation_info.go +++ b/command/recommendation_info.go @@ -7,6 +7,8 @@ import ( "github.com/mitchellh/cli" "github.com/posener/complete" + + "github.com/hashicorp/nomad/api/contexts" ) // Ensure RecommendationInfoCommand satisfies the cli.Command interface. @@ -52,6 +54,21 @@ func (r *RecommendationInfoCommand) AutocompleteFlags() complete.Flags { }) } +func (r *RecommendationInfoCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictFunc(func(a complete.Args) []string { + client, err := r.Meta.Client() + if err != nil { + return nil + } + + resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Recommendations, nil) + if err != nil { + return []string{} + } + return resp.Matches[contexts.Recommendations] + }) +} + // Name returns the name of this command. func (r *RecommendationInfoCommand) Name() string { return "recommendation info" } diff --git a/command/recommendation_info_test.go b/command/recommendation_info_test.go index b56bea86f..614d7ef87 100644 --- a/command/recommendation_info_test.go +++ b/command/recommendation_info_test.go @@ -7,6 +7,8 @@ import ( "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/testutil" "github.com/mitchellh/cli" + "github.com/posener/complete" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -81,3 +83,46 @@ func TestRecommendationInfoCommand_Run(t *testing.T) { require.Contains(out, recResp.ID) } } + +func TestRecommendationInfoCommand_AutocompleteArgs(t *testing.T) { + assert := assert.New(t) + t.Parallel() + + srv, client, url := testServer(t, true, nil) + defer srv.Shutdown() + + // Register a test job to write a recommendation against. + ui := cli.NewMockUi() + testJob := testJob("recommendation_list") + regResp, _, err := client.Jobs().Register(testJob, nil) + require.NoError(t, err) + registerCode := waitForSuccess(ui, client, fullId, t, regResp.EvalID) + require.Equal(t, 0, registerCode) + + // Write a recommendation. + rec := &api.Recommendation{ + JobID: *testJob.ID, + Group: *testJob.TaskGroups[0].Name, + Task: testJob.TaskGroups[0].Tasks[0].Name, + Resource: "CPU", + Value: 1050, + Meta: map[string]interface{}{"test-meta-entry": "test-meta-value"}, + Stats: map[string]float64{"p13": 1.13}, + } + rec, _, err = client.Recommendations().Upsert(rec, nil) + if srv.Enterprise { + require.NoError(t, err) + } else { + require.Error(t, err, "Nomad Enterprise only endpoint") + return + } + + cmd := &RecommendationInfoCommand{Meta: Meta{Ui: ui, flagAddress: url}} + prefix := rec.ID[:5] + args := complete.Args{Last: prefix} + predictor := cmd.AutocompleteArgs() + + res := predictor.Predict(args) + assert.Equal(1, len(res)) + assert.Equal(rec.ID, res[0]) +} diff --git a/command/recommendation_list_test.go b/command/recommendation_list_test.go index d09c67eb8..c7234bd7c 100644 --- a/command/recommendation_list_test.go +++ b/command/recommendation_list_test.go @@ -1,15 +1,14 @@ package command import ( - "fmt" "sort" "testing" - "github.com/hashicorp/nomad/api" - "github.com/hashicorp/nomad/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/hashicorp/nomad/api" ) func TestRecommendationListCommand_Run(t *testing.T) { @@ -17,21 +16,6 @@ func TestRecommendationListCommand_Run(t *testing.T) { t.Parallel() srv, client, url := testServer(t, true, nil) defer srv.Shutdown() - testutil.WaitForResult(func() (bool, error) { - nodes, _, err := client.Nodes().List(nil) - if err != nil { - return false, err - } - if len(nodes) == 0 { - return false, fmt.Errorf("missing node") - } - if _, ok := nodes[0].Drivers["mock_driver"]; !ok { - return false, fmt.Errorf("mock_driver not ready") - } - return true, nil - }, func(err error) { - t.Fatalf("err: %s", err) - }) ui := cli.NewMockUi() cmd := &RecommendationListCommand{Meta: Meta{Ui: ui}} @@ -89,7 +73,7 @@ func TestRecommendationListCommand_Run(t *testing.T) { } } -func TestRecommendationList_Sort(t *testing.T) { +func TestRecommendationListCommand_Sort(t *testing.T) { testCases := []struct { inputRecommendationList []*api.Recommendation expectedOutputList []*api.Recommendation diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 58049ef70..95869e8cc 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -193,16 +193,17 @@ var ( type Context string const ( - Allocs Context = "allocs" - Deployments Context = "deployment" - Evals Context = "evals" - Jobs Context = "jobs" - Nodes Context = "nodes" - Namespaces Context = "namespaces" - Quotas Context = "quotas" - All Context = "all" - Plugins Context = "plugins" - Volumes Context = "volumes" + Allocs Context = "allocs" + Deployments Context = "deployment" + Evals Context = "evals" + Jobs Context = "jobs" + Nodes Context = "nodes" + Namespaces Context = "namespaces" + Quotas Context = "quotas" + Recommendations Context = "recommendations" + All Context = "all" + Plugins Context = "plugins" + Volumes Context = "volumes" ) // NamespacedID is a tuple of an ID and a namespace diff --git a/vendor/github.com/hashicorp/nomad/api/contexts/contexts.go b/vendor/github.com/hashicorp/nomad/api/contexts/contexts.go index ae40db3f8..ea4ea1309 100644 --- a/vendor/github.com/hashicorp/nomad/api/contexts/contexts.go +++ b/vendor/github.com/hashicorp/nomad/api/contexts/contexts.go @@ -4,14 +4,15 @@ package contexts type Context string const ( - Allocs Context = "allocs" - Deployments Context = "deployment" - Evals Context = "evals" - Jobs Context = "jobs" - Nodes Context = "nodes" - Namespaces Context = "namespaces" - Quotas Context = "quotas" - Plugins Context = "plugins" - Volumes Context = "volumes" - All Context = "all" + Allocs Context = "allocs" + Deployments Context = "deployment" + Evals Context = "evals" + Jobs Context = "jobs" + Nodes Context = "nodes" + Namespaces Context = "namespaces" + Quotas Context = "quotas" + Recommendations Context = "recommendations" + Plugins Context = "plugins" + Volumes Context = "volumes" + All Context = "all" )