1d051d834d
Several `nomad job` subcommands had duplicate or slightly similar logic for resolving a job ID from a CLI argument prefix, while others did not have this functionality at all. This commit pulls the shared logic to the command Meta and updates all `nomad job` subcommands to use it.
251 lines
5.3 KiB
Go
251 lines
5.3 KiB
Go
package command
|
|
|
|
import (
|
|
"flag"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/creack/pty"
|
|
"github.com/hashicorp/nomad/api"
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/helper/pointer"
|
|
"github.com/mitchellh/cli"
|
|
"github.com/shoenig/test/must"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMeta_FlagSet(t *testing.T) {
|
|
ci.Parallel(t)
|
|
cases := []struct {
|
|
Flags FlagSetFlags
|
|
Expected []string
|
|
}{
|
|
{
|
|
FlagSetNone,
|
|
[]string{},
|
|
},
|
|
{
|
|
FlagSetClient,
|
|
[]string{
|
|
"address",
|
|
"no-color",
|
|
"force-color",
|
|
"region",
|
|
"namespace",
|
|
"ca-cert",
|
|
"ca-path",
|
|
"client-cert",
|
|
"client-key",
|
|
"insecure",
|
|
"tls-server-name",
|
|
"tls-skip-verify",
|
|
"token",
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
var m Meta
|
|
fs := m.FlagSet("foo", tc.Flags)
|
|
|
|
actual := make([]string, 0, 0)
|
|
fs.VisitAll(func(f *flag.Flag) {
|
|
actual = append(actual, f.Name)
|
|
})
|
|
sort.Strings(actual)
|
|
sort.Strings(tc.Expected)
|
|
|
|
if !reflect.DeepEqual(actual, tc.Expected) {
|
|
t.Fatalf("%d: flags: %#v\n\nExpected: %#v\nGot: %#v",
|
|
i, tc.Flags, tc.Expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMeta_Colorize(t *testing.T) {
|
|
|
|
type testCaseSetupFn func(*testing.T, *Meta)
|
|
|
|
cases := []struct {
|
|
Name string
|
|
SetupFn testCaseSetupFn
|
|
ExpectColor bool
|
|
}{
|
|
{
|
|
Name: "disable colors if UI is not colored",
|
|
ExpectColor: false,
|
|
},
|
|
{
|
|
Name: "colors if UI is colored",
|
|
SetupFn: func(t *testing.T, m *Meta) {
|
|
m.Ui = &cli.ColoredUi{}
|
|
},
|
|
ExpectColor: true,
|
|
},
|
|
{
|
|
Name: "disable colors via CLI flag",
|
|
SetupFn: func(t *testing.T, m *Meta) {
|
|
m.SetupUi([]string{"-no-color"})
|
|
},
|
|
ExpectColor: false,
|
|
},
|
|
{
|
|
Name: "disable colors via env var",
|
|
SetupFn: func(t *testing.T, m *Meta) {
|
|
t.Setenv(EnvNomadCLINoColor, "1")
|
|
m.SetupUi([]string{})
|
|
},
|
|
ExpectColor: false,
|
|
},
|
|
{
|
|
Name: "force colors via CLI flag",
|
|
SetupFn: func(t *testing.T, m *Meta) {
|
|
m.SetupUi([]string{"-force-color"})
|
|
},
|
|
ExpectColor: true,
|
|
},
|
|
{
|
|
Name: "force colors via env var",
|
|
SetupFn: func(t *testing.T, m *Meta) {
|
|
t.Setenv(EnvNomadCLIForceColor, "1")
|
|
m.SetupUi([]string{})
|
|
},
|
|
ExpectColor: true,
|
|
},
|
|
{
|
|
Name: "no color take predecence over force color via CLI flag",
|
|
SetupFn: func(t *testing.T, m *Meta) {
|
|
m.SetupUi([]string{"-no-color", "-force-color"})
|
|
},
|
|
ExpectColor: false,
|
|
},
|
|
{
|
|
Name: "no color take predecence over force color via env var",
|
|
SetupFn: func(t *testing.T, m *Meta) {
|
|
t.Setenv(EnvNomadCLINoColor, "1")
|
|
m.SetupUi([]string{"-force-color"})
|
|
},
|
|
ExpectColor: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
// Create fake test terminal.
|
|
_, tty, err := pty.Open()
|
|
require.NoError(t, err)
|
|
defer tty.Close()
|
|
|
|
oldStdout := os.Stdout
|
|
defer func() { os.Stdout = oldStdout }()
|
|
os.Stdout = tty
|
|
|
|
// Make sure color related environment variables are clean.
|
|
t.Setenv(EnvNomadCLIForceColor, "")
|
|
t.Setenv(EnvNomadCLINoColor, "")
|
|
|
|
// Run test case.
|
|
m := &Meta{}
|
|
if tc.SetupFn != nil {
|
|
tc.SetupFn(t, m)
|
|
}
|
|
|
|
require.Equal(t, !tc.ExpectColor, m.Colorize().Disable)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMeta_JobByPrefix(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
srv, client, _ := testServer(t, true, nil)
|
|
defer srv.Shutdown()
|
|
|
|
// Wait for a node to be ready
|
|
waitForNodes(t, client)
|
|
|
|
ui := cli.NewMockUi()
|
|
meta := &Meta{Ui: ui, namespace: api.AllNamespacesNamespace}
|
|
client.SetNamespace(api.AllNamespacesNamespace)
|
|
|
|
jobs := []struct {
|
|
namespace string
|
|
id string
|
|
}{
|
|
{namespace: "default", id: "example"},
|
|
{namespace: "default", id: "job"},
|
|
{namespace: "default", id: "job-1"},
|
|
{namespace: "default", id: "job-2"},
|
|
{namespace: "prod", id: "job-1"},
|
|
}
|
|
for _, j := range jobs {
|
|
job := testJob(j.id)
|
|
job.Namespace = pointer.Of(j.namespace)
|
|
|
|
_, err := client.Namespaces().Register(&api.Namespace{Name: j.namespace}, nil)
|
|
must.NoError(t, err)
|
|
|
|
w := &api.WriteOptions{Namespace: j.namespace}
|
|
resp, _, err := client.Jobs().Register(job, w)
|
|
must.NoError(t, err)
|
|
|
|
code := waitForSuccess(ui, client, fullId, t, resp.EvalID)
|
|
must.Zero(t, code)
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
prefix string
|
|
filterFunc JobByPrefixFilterFunc
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "exact match",
|
|
prefix: "job",
|
|
},
|
|
{
|
|
name: "partial match",
|
|
prefix: "exam",
|
|
},
|
|
{
|
|
name: "match with filter",
|
|
prefix: "job-",
|
|
filterFunc: func(j *api.JobListStub) bool {
|
|
// Filter out jobs with "job-" so that only "job-2" matches.
|
|
return j.ID == "job-2"
|
|
},
|
|
},
|
|
{
|
|
name: "multiple matches",
|
|
prefix: "job-",
|
|
expectedError: "matched multiple jobs",
|
|
},
|
|
{
|
|
name: "no match",
|
|
prefix: "not-found",
|
|
expectedError: "No job(s) with prefix or ID",
|
|
},
|
|
{
|
|
name: "multiple matches across namespaces",
|
|
prefix: "job-1",
|
|
expectedError: "matched multiple jobs",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
job, err := meta.JobByPrefix(client, tc.prefix, tc.filterFunc)
|
|
if tc.expectedError != "" {
|
|
must.Nil(t, job)
|
|
must.ErrorContains(t, err, tc.expectedError)
|
|
} else {
|
|
must.NoError(t, err)
|
|
must.NotNil(t, job)
|
|
must.StrContains(t, *job.ID, tc.prefix)
|
|
}
|
|
})
|
|
}
|
|
}
|