cli: remove hard requirement on `list-jobs` (#16380)
Most job subcommands allow for job ID prefix match as a convenience functionality so users don't have to type the full job ID. But this introduces a hard ACL requirement that the token used to run these commands have the `list-jobs` permission, even if the token has enough permission to execute the basic command action and the user passed an exact job ID. This change softens this requirement by not failing the prefix match in case the request results in a permission denied error and instead using the information passed by the user directly.
This commit is contained in:
parent
3239539526
commit
1aceff7806
|
@ -0,0 +1,3 @@
|
||||||
|
``release-note:improvement
|
||||||
|
cli: Remove requirement for `list-jobs` capability on several job subcommands that didn't strictly needed it
|
||||||
|
```
|
|
@ -19,8 +19,9 @@ Usage: nomad job allocs [options] <job>
|
||||||
|
|
||||||
Display allocations for a particular job.
|
Display allocations for a particular job.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'read-job' and
|
When ACLs are enabled, this command requires a token with the 'read-job'
|
||||||
'list-jobs' capabilities for the job's namespace.
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,14 @@ package command
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -172,3 +175,116 @@ func TestJobAllocsCommand_AutocompleteArgs(t *testing.T) {
|
||||||
require.Equal(t, 1, len(res))
|
require.Equal(t, 1, len(res))
|
||||||
require.Equal(t, j.ID, res[0])
|
require.Equal(t, j.ID, res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobAllocsCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, _, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
// Create a job with an alloc.
|
||||||
|
job := mock.Job()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, 100, job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
a := mock.Alloc()
|
||||||
|
a.Job = job
|
||||||
|
a.JobID = job.ID
|
||||||
|
a.TaskGroup = job.TaskGroups[0].Name
|
||||||
|
a.Metrics = &structs.AllocMetric{}
|
||||||
|
a.DesiredStatus = structs.AllocDesiredStatusRun
|
||||||
|
a.ClientStatus = structs.AllocClientStatusRunning
|
||||||
|
err = state.UpsertAllocs(structs.MsgTypeTestSetup, 200, []*structs.Allocation{a})
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
expectedOut string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["alloc-lifecycle"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedOut: "No allocations",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobAllocsCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
if tc.expectedOut != "" {
|
||||||
|
must.StrContains(t, ui.OutputWriter.String(), tc.expectedOut)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,8 +19,9 @@ Usage: nomad job deployments [options] <job>
|
||||||
|
|
||||||
Deployments is used to display the deployments for a particular job.
|
Deployments is used to display the deployments for a particular job.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'read-job' and
|
When ACLs are enabled, this command requires a token with the 'read-job'
|
||||||
'list-jobs' capabilities for the job's namespace.
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -152,3 +155,112 @@ func TestJobDeploymentsCommand_AutocompleteArgs(t *testing.T) {
|
||||||
assert.Equal(1, len(res))
|
assert.Equal(1, len(res))
|
||||||
assert.Equal(j.ID, res[0])
|
assert.Equal(j.ID, res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobDeploymentsCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, _, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
// Create a job with a deployment.
|
||||||
|
job := mock.Job()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, 100, job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
d := mock.Deployment()
|
||||||
|
d.JobID = job.ID
|
||||||
|
d.JobCreateIndex = job.CreateIndex
|
||||||
|
err = state.UpsertDeployment(101, d)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
expectedOut string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["alloc-lifecycle"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedOut: "No deployments",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobDeploymentsCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
if tc.expectedOut != "" {
|
||||||
|
must.StrContains(t, ui.OutputWriter.String(), tc.expectedOut)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,10 @@ Usage: nomad job dispatch [options] <parameterized job> [input source]
|
||||||
detach flag.
|
detach flag.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'dispatch-job'
|
When ACLs are enabled, this command requires a token with the 'dispatch-job'
|
||||||
capability for the job's namespace.
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID. The 'read-job'
|
||||||
|
capability is required to monitor the resulting evaluation when -detach is
|
||||||
|
not used.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -85,3 +88,123 @@ func TestJobDispatchCommand_AutocompleteArgs(t *testing.T) {
|
||||||
require.Equal(t, 1, len(res))
|
require.Equal(t, 1, len(res))
|
||||||
require.Equal(t, j1.ID, res[0])
|
require.Equal(t, j1.ID, res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobDispatchCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, _, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
// Create a parameterized job.
|
||||||
|
job := mock.MinJob()
|
||||||
|
job.Type = "batch"
|
||||||
|
job.ParameterizedJob = &structs.ParameterizedJobConfig{}
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, 100, job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing dispatch-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dispatch-job allowed but can't monitor eval without read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["dispatch-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "No evaluation with id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dispatch-job allowed and can monitor eval with read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["dispatch-job", "read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["dispatch-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "job not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job but can't monitor eval without read-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["dispatch-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "No evaluation with id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job and can monitor eval with read-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "dispatch-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobDispatchCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,8 +23,9 @@ Usage: nomad job eval [options] <job_id>
|
||||||
operators to force the scheduler to create new allocations under certain
|
operators to force the scheduler to create new allocations under certain
|
||||||
scenarios.
|
scenarios.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'submit-job'
|
When ACLs are enabled, this command requires a token with the 'read-job'
|
||||||
capability for the job's namespace.
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/hashicorp/nomad/testutil"
|
"github.com/hashicorp/nomad/testutil"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -125,3 +128,102 @@ func TestJobEvalCommand_AutocompleteArgs(t *testing.T) {
|
||||||
assert.Equal(1, len(res))
|
assert.Equal(1, len(res))
|
||||||
assert.Equal(j.ID, res[0])
|
assert.Equal(j.ID, res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobEvalCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, _, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
// Create a job.
|
||||||
|
job := mock.MinJob()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, 100, job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "job not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobEvalCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,8 +26,9 @@ Usage: nomad job history [options] <job>
|
||||||
the changes that occurred to the job as well as deciding job versions to revert
|
the changes that occurred to the job as well as deciding job versions to revert
|
||||||
to.
|
to.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'read-job' and
|
When ACLs are enabled, this command requires a token with the 'read-job'
|
||||||
'list-jobs' capabilities for the job's namespace.
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,3 +66,102 @@ func TestJobHistoryCommand_AutocompleteArgs(t *testing.T) {
|
||||||
assert.Equal(1, len(res))
|
assert.Equal(1, len(res))
|
||||||
assert.Equal(j.ID, res[0])
|
assert.Equal(j.ID, res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobHistoryCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, _, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
// Create a job.
|
||||||
|
job := mock.MinJob()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, 100, job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "job versions not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobHistoryCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,8 +20,9 @@ Alias: nomad inspect
|
||||||
|
|
||||||
Inspect is used to see the specification of a submitted job.
|
Inspect is used to see the specification of a submitted job.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'read-job' and
|
When ACLs are enabled, this command requires a token with the 'read-job'
|
||||||
'list-jobs' capabilities for the job's namespace.
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -83,3 +86,102 @@ func TestInspectCommand_AutocompleteArgs(t *testing.T) {
|
||||||
assert.Equal(1, len(res))
|
assert.Equal(1, len(res))
|
||||||
assert.Equal(j.ID, res[0])
|
assert.Equal(j.ID, res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobInspectCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, _, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
// Create a job
|
||||||
|
job := mock.MinJob()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, 100, job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "job not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobInspectCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,10 @@ Usage: nomad job periodic force <job id>
|
||||||
prohibit_overlap setting.
|
prohibit_overlap setting.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'submit-job'
|
When ACLs are enabled, this command requires a token with the 'submit-job'
|
||||||
and 'list-jobs' capabilities for the job's namespace.
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID. The 'read-job'
|
||||||
|
capability is required to monitor the resulting evaluation when -detach is
|
||||||
|
not used.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,14 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/nomad/api"
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/helper/pointer"
|
"github.com/hashicorp/nomad/helper/pointer"
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/hashicorp/nomad/testutil"
|
"github.com/hashicorp/nomad/testutil"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -246,3 +248,132 @@ func TestJobPeriodicForceCommand_SuccessfulIfJobIDEqualsPrefix(t *testing.T) {
|
||||||
require.Contains(t, out, "Monitoring evaluation")
|
require.Contains(t, out, "Monitoring evaluation")
|
||||||
require.Contains(t, out, "finished with status \"complete\"")
|
require.Contains(t, out, "finished with status \"complete\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobPeriodicForceCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, client, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
client.SetSecretID(srv.RootToken.SecretID)
|
||||||
|
|
||||||
|
// Create a periodic job.
|
||||||
|
jobID := "test_job_periodic_force_acl"
|
||||||
|
job := testJob(jobID)
|
||||||
|
job.Periodic = &api.PeriodicConfig{
|
||||||
|
SpecType: pointer.Of(api.PeriodicSpecCron),
|
||||||
|
Spec: pointer.Of("*/15 * * * * *"),
|
||||||
|
}
|
||||||
|
|
||||||
|
rootTokenOpts := &api.WriteOptions{
|
||||||
|
AuthToken: srv.RootToken.SecretID,
|
||||||
|
}
|
||||||
|
_, _, err := client.Jobs().Register(job, rootTokenOpts)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing submit-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "submit-job allowed but can't monitor eval without read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "No evaluation with id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "submit-job allowed and can monitor eval with read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job", "read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "job not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job but can't monitor eval without read-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "No evaluation with id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job and can monitor eval with read-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "submit-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobPeriodicForceCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, jobID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, jobID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,9 @@ Usage: nomad job promote [options] <job id>
|
||||||
"nomad job revert" command.
|
"nomad job revert" command.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'submit-job',
|
When ACLs are enabled, this command requires a token with the 'submit-job',
|
||||||
'list-jobs', and 'read-job' capabilities for the job's namespace.
|
and 'read-job' capabilities for the job's namespace. The 'list-jobs'
|
||||||
|
capability is required to run the command with a job prefix instead of the
|
||||||
|
exact job ID.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
|
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
@ -64,3 +67,119 @@ func TestJobPromoteCommand_AutocompleteArgs(t *testing.T) {
|
||||||
assert.Equal(1, len(res))
|
assert.Equal(1, len(res))
|
||||||
assert.Equal(j.ID, res[0])
|
assert.Equal(j.ID, res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobPromoteCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, _, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
// Create a job.
|
||||||
|
job := mock.MinJob()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, 100, job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing submit-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job and submit-job allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "submit-job",]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "no deployment to promote",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "submit-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobPromoteCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
"-detach",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create deployment to promote.
|
||||||
|
d := mock.Deployment()
|
||||||
|
d.JobID = job.ID
|
||||||
|
d.JobCreateIndex = job.CreateIndex
|
||||||
|
err = state.UpsertDeployment(uint64(301+i), d)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,10 @@ Usage: nomad job revert [options] <job> <version>
|
||||||
versions to revert to can be found using "nomad job history" command.
|
versions to revert to can be found using "nomad job history" command.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'submit-job'
|
When ACLs are enabled, this command requires a token with the 'submit-job'
|
||||||
and 'list-jobs' capabilities for the job's namespace.
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID. The 'read-job'
|
||||||
|
capability is required to monitor the resulting evaluation when -detach is
|
||||||
|
not used.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
structs "github.com/hashicorp/nomad/nomad/structs"
|
structs "github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,3 +66,136 @@ func TestJobRevertCommand_AutocompleteArgs(t *testing.T) {
|
||||||
assert.Equal(1, len(res))
|
assert.Equal(1, len(res))
|
||||||
assert.Equal(j.ID, res[0])
|
assert.Equal(j.ID, res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobRevertCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, client, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing submit-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "submit-job allowed but can't monitor eval without read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "No evaluation with id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "submit-job allowed and can monitor eval with read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job but can't monitor eval without read-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "No evaluation with id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job and can monitor eval with read-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "submit-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobRevertCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a job.
|
||||||
|
job := mock.MinJob()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, uint64(300+i), job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
client.Jobs().Deregister(job.ID, true, &api.WriteOptions{
|
||||||
|
AuthToken: srv.RootToken.SecretID,
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Modify job to create new version.
|
||||||
|
newJob := job.Copy()
|
||||||
|
newJob.Meta = map[string]string{
|
||||||
|
"test": tc.name,
|
||||||
|
}
|
||||||
|
newJob.Version = uint64(i)
|
||||||
|
err = state.UpsertJob(structs.MsgTypeTestSetup, uint64(301+i), newJob)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command reverting job to version 0.
|
||||||
|
args = append(args, "0")
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,8 +32,12 @@ Usage: nomad job scale [options] <job> [<group>] <count>
|
||||||
onto nodes. The monitor will end once job placement is done. It
|
onto nodes. The monitor will end once job placement is done. It
|
||||||
is safe to exit the monitor early using ctrl+c.
|
is safe to exit the monitor early using ctrl+c.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'scale-job'
|
When ACLs are enabled, this command requires a token with the
|
||||||
capability for the job's namespace.
|
'read-job-scaling' and either the 'scale-job' or 'submit-job' capabilities
|
||||||
|
for the job's namespace. The 'list-jobs' capability is required to run the
|
||||||
|
command with a job prefix instead of the exact job ID. The 'read-job'
|
||||||
|
capability is required to monitor the resulting evaluation when -detach is
|
||||||
|
not used.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,13 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/nomad/api"
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/helper/pointer"
|
"github.com/hashicorp/nomad/helper/pointer"
|
||||||
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/hashicorp/nomad/testutil"
|
"github.com/hashicorp/nomad/testutil"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestJobScaleCommand_SingleGroup(t *testing.T) {
|
func TestJobScaleCommand_SingleGroup(t *testing.T) {
|
||||||
|
@ -122,3 +126,153 @@ func TestJobScaleCommand_MultiGroup(t *testing.T) {
|
||||||
t.Fatalf("Expected Evaluation ID within output: %v", out)
|
t.Fatalf("Expected Evaluation ID within output: %v", out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobScaleCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, client, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing scale-job or job-submit",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job-scaling"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job-scaling",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["scale-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job-scaling and scale-job allowed but can't monitor eval without read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job-scaling", "scale-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "No evaluation with id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job-scaling and submit-job allowed but can't monitor eval without read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job-scaling", "submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "No evaluation with id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job-scaling and scale-job allowed and can monitor eval with read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "read-job-scaling", "scale-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job-scaling and submit-job allowed and can monitor eval with read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "read-job-scaling", "submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job-scaling", "scale-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "job not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job but can't monitor eval without read-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job-scaling", "scale-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "No evaluation with id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job and can monitor eval with read-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "read-job-scaling", "scale-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobScaleCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a job.
|
||||||
|
job := mock.MinJob()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, uint64(300+i), job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
client.Jobs().Deregister(job.ID, true, &api.WriteOptions{
|
||||||
|
AuthToken: srv.RootToken.SecretID,
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command scaling job to 2.
|
||||||
|
args = append(args, "2")
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,8 +27,10 @@ Usage: nomad job scaling-events [options] <args>
|
||||||
|
|
||||||
List the scaling events for the specified job.
|
List the scaling events for the specified job.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the
|
When ACLs are enabled, this command requires a token with either the
|
||||||
'read-job-scaling' capability for the job's namespace.
|
'read-job' or 'read-job-scaling' capability for the job's namespace. The
|
||||||
|
'list-jobs' capability is required to run the command with a job prefix
|
||||||
|
instead of the exact job ID.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/helper/pointer"
|
"github.com/hashicorp/nomad/helper/pointer"
|
||||||
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/hashicorp/nomad/testutil"
|
"github.com/hashicorp/nomad/testutil"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestJobScalingEventsCommand_Run(t *testing.T) {
|
func TestJobScalingEventsCommand_Run(t *testing.T) {
|
||||||
|
@ -89,3 +94,110 @@ func TestJobScalingEventsCommand_Run(t *testing.T) {
|
||||||
t.Fatalf("Expected to verbose table headers: %v", out)
|
t.Fatalf("Expected to verbose table headers: %v", out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobScalingEventsCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, _, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
// Create a job.
|
||||||
|
job := mock.MinJob()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, 100, job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job or read-job-scaling",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job-scaling allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job-scaling"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job-scaling"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "job not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job-scaling","list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobScalingEventsCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,8 +32,9 @@ Usage: nomad status [options] <job>
|
||||||
Display status information about a job. If no job ID is given, a list of all
|
Display status information about a job. If no job ID is given, a list of all
|
||||||
known jobs will be displayed.
|
known jobs will be displayed.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'read-job' and
|
When ACLs are enabled, this command requires a token with the 'read-job'
|
||||||
'list-jobs' capabilities for the job's namespace.
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/hashicorp/nomad/testutil"
|
"github.com/hashicorp/nomad/testutil"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -392,6 +393,105 @@ func TestJobStatusCommand_RescheduleEvals(t *testing.T) {
|
||||||
require.Contains(out, e.ID[:8])
|
require.Contains(out, e.ID[:8])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobStatusCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, _, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
// Create a job.
|
||||||
|
job := mock.MinJob()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, 100, job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "job not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "list-jobs"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobStatusCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
code := cmd.Run(args)
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func waitForSuccess(ui cli.Ui, client *api.Client, length int, t *testing.T, evalId string) int {
|
func waitForSuccess(ui cli.Ui, client *api.Client, length int, t *testing.T, evalId string) int {
|
||||||
mon := newMonitor(ui, client, length)
|
mon := newMonitor(ui, client, length)
|
||||||
monErr := mon.monitor(evalId)
|
monErr := mon.monitor(evalId)
|
||||||
|
|
|
@ -25,8 +25,10 @@ Alias: nomad stop
|
||||||
allocations and completes shutting down. It is safe to exit the monitor
|
allocations and completes shutting down. It is safe to exit the monitor
|
||||||
early using ctrl+c.
|
early using ctrl+c.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the 'submit-job',
|
When ACLs are enabled, this command requires a token with the 'submit-job'
|
||||||
'read-job', and 'list-jobs' capabilities for the job's namespace.
|
and 'read-job' capabilities for the job's namespace. The 'list-jobs'
|
||||||
|
capability is required to run the command with job prefixes instead of exact
|
||||||
|
job IDs.
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
"github.com/hashicorp/nomad/command/agent"
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/hashicorp/nomad/helper/pointer"
|
"github.com/hashicorp/nomad/helper/pointer"
|
||||||
|
@ -148,3 +149,117 @@ func TestStopCommand_AutocompleteArgs(t *testing.T) {
|
||||||
must.Len(t, 1, res)
|
must.Len(t, 1, res)
|
||||||
must.Eq(t, j.ID, res[0])
|
must.Eq(t, j.ID, res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJobStopCommand_ACL(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
// Start server with ACL enabled.
|
||||||
|
srv, client, url := testServer(t, true, func(c *agent.Config) {
|
||||||
|
c.ACL.Enabled = true
|
||||||
|
})
|
||||||
|
defer srv.Shutdown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jobPrefix bool
|
||||||
|
aclPolicy string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no token",
|
||||||
|
aclPolicy: "",
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing submit-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing read-job",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: api.PermissionDeniedErrorContent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read-job and submit-job allowed",
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix requires list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["read-job", "submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expectedErr: "job not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job prefix works with list-job",
|
||||||
|
jobPrefix: true,
|
||||||
|
aclPolicy: `
|
||||||
|
namespace "default" {
|
||||||
|
capabilities = ["list-jobs", "read-job", "submit-job"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobStopCommand{Meta: Meta{Ui: ui}}
|
||||||
|
args := []string{
|
||||||
|
"-address", url,
|
||||||
|
"-yes",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a job.
|
||||||
|
job := mock.MinJob()
|
||||||
|
state := srv.Agent.Server().State()
|
||||||
|
err := state.UpsertJob(structs.MsgTypeTestSetup, uint64(300+i), job)
|
||||||
|
must.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
client.Jobs().Deregister(job.ID, true, &api.WriteOptions{
|
||||||
|
AuthToken: srv.RootToken.SecretID,
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
if tc.aclPolicy != "" {
|
||||||
|
// Create ACL token with test case policy and add it to the
|
||||||
|
// command.
|
||||||
|
policyName := nonAlphaNum.ReplaceAllString(tc.name, "-")
|
||||||
|
token := mock.CreatePolicyAndToken(t, state, uint64(302+i), policyName, tc.aclPolicy)
|
||||||
|
args = append(args, "-token", token.SecretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add job ID or job ID prefix to the command.
|
||||||
|
if tc.jobPrefix {
|
||||||
|
args = append(args, job.ID[:3])
|
||||||
|
} else {
|
||||||
|
args = append(args, job.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run command.
|
||||||
|
code := cmd.Run(args)
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
must.Zero(t, code)
|
||||||
|
} else {
|
||||||
|
must.One(t, code)
|
||||||
|
must.StrContains(t, ui.ErrorWriter.String(), tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -260,13 +260,17 @@ func (m *Meta) JobByPrefix(client *api.Client, prefix string, filter JobByPrefix
|
||||||
return job, nil
|
return job, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobIDByPrefix returns the job that best matches the given prefix and its
|
// JobIDByPrefix provides best effort match for the given job prefix.
|
||||||
// namespace. Returns an error if there are no matches or if there are more
|
// Returns the prefix itself if job prefix search is not allowed and an error
|
||||||
// than one exact match across namespaces.
|
// if there are no matches or if there are more than one exact match across
|
||||||
|
// namespaces.
|
||||||
func (m *Meta) JobIDByPrefix(client *api.Client, prefix string, filter JobByPrefixFilterFunc) (string, string, error) {
|
func (m *Meta) JobIDByPrefix(client *api.Client, prefix string, filter JobByPrefixFilterFunc) (string, string, error) {
|
||||||
// Search job by prefix. Return an error if there is not an exact match.
|
// Search job by prefix. Return an error if there is not an exact match.
|
||||||
jobs, _, err := client.Jobs().PrefixList(prefix)
|
jobs, _, err := client.Jobs().PrefixList(prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), api.PermissionDeniedErrorContent) {
|
||||||
|
return prefix, "", nil
|
||||||
|
}
|
||||||
return "", "", fmt.Errorf("Error querying job prefix %q: %s", prefix, err)
|
return "", "", fmt.Errorf("Error querying job prefix %q: %s", prefix, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package command
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -14,6 +15,8 @@ import (
|
||||||
"github.com/shoenig/test/must"
|
"github.com/shoenig/test/must"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var nonAlphaNum = regexp.MustCompile(`[^a-zA-Z0-9]+`)
|
||||||
|
|
||||||
func testServer(t *testing.T, runClient bool, cb func(*agent.Config)) (*agent.TestAgent, *api.Client, string) {
|
func testServer(t *testing.T, runClient bool, cb func(*agent.Config)) (*agent.TestAgent, *api.Client, string) {
|
||||||
// Make a new test server
|
// Make a new test server
|
||||||
a := agent.NewTestAgent(t, t.Name(), func(config *agent.Config) {
|
a := agent.NewTestAgent(t, t.Name(), func(config *agent.Config) {
|
||||||
|
|
|
@ -19,8 +19,9 @@ nomad job allocs [options] <job>
|
||||||
The `job allocs` command requires a single argument, the job ID or an ID
|
The `job allocs` command requires a single argument, the job ID or an ID
|
||||||
prefix of a job to display the list of allocations for.
|
prefix of a job to display the list of allocations for.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `read-job` and
|
When ACLs are enabled, this command requires a token with the `read-job`
|
||||||
`list-jobs` capabilities for the job's namespace.
|
capability for the job's namespace. The `list-jobs` capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,9 @@ nomad job deployments [options] <job>
|
||||||
The `job deployments` command requires a single argument, the job ID or an ID
|
The `job deployments` command requires a single argument, the job ID or an ID
|
||||||
prefix of a job to display the list of deployments for.
|
prefix of a job to display the list of deployments for.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `read-job` and
|
When ACLs are enabled, this command requires a token with the `read-job`
|
||||||
`list-jobs` capabilities for the job's namespace.
|
capability for the job's namespace. The `list-jobs` capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,10 @@ exhaustion, etc), then the exit code will be 2. Any other errors, including
|
||||||
client connection issues or internal errors, are indicated by exit code 1.
|
client connection issues or internal errors, are indicated by exit code 1.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `dispatch-job`
|
When ACLs are enabled, this command requires a token with the `dispatch-job`
|
||||||
capability for the job's namespace.
|
capability for the job's namespace. The `list-jobs` capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID. The `read-job`
|
||||||
|
capability is required to monitor the resulting evaluation when `-detach` is
|
||||||
|
not used.
|
||||||
|
|
||||||
See the [multiregion] documentation for additional considerations when
|
See the [multiregion] documentation for additional considerations when
|
||||||
dispatching parameterized jobs.
|
dispatching parameterized jobs.
|
||||||
|
|
|
@ -20,8 +20,9 @@ The `job eval` command requires a single argument, specifying the job ID to
|
||||||
evaluate. If there is an exact match based on the provided job ID, then the job
|
evaluate. If there is an exact match based on the provided job ID, then the job
|
||||||
will be evaluated, forcing a scheduler run.
|
will be evaluated, forcing a scheduler run.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `submit-job`
|
When ACLs are enabled, this command requires a token with the `read-job`
|
||||||
capability for the job's namespace.
|
capability for the job's namespace. The `list-jobs` capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,9 @@ nomad job history [options] <job>
|
||||||
The `job history` command requires a single argument, the job ID or an ID prefix
|
The `job history` command requires a single argument, the job ID or an ID prefix
|
||||||
of a job to display the history for.
|
of a job to display the history for.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `read-job` and
|
When ACLs are enabled, this command requires a token with the `read-job`
|
||||||
`list-jobs` capabilities for the job's namespace.
|
capability for the job's namespace. The `list-jobs` capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,9 @@ will retrieve the JSON version of the job. This JSON is valid to be submitted to
|
||||||
the [Job HTTP API]. This command is useful to inspect what version of a job
|
the [Job HTTP API]. This command is useful to inspect what version of a job
|
||||||
Nomad is running.
|
Nomad is running.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `read-job` and
|
When ACLs are enabled, this command requires a token with the `read-job`
|
||||||
`list-jobs` capabilities for the job's namespace.
|
capability for the job's namespace. The `list-jobs` capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,10 @@ placement information for the forced evaluation. The monitor will exit after
|
||||||
scheduling has finished or failed.
|
scheduling has finished or failed.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `submit-job`
|
When ACLs are enabled, this command requires a token with the `submit-job`
|
||||||
and `list-jobs` capabilities for the job's namespace.
|
capability for the job's namespace. The `list-jobs` capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID. The `read-job`
|
||||||
|
capability is required to monitor the resulting evaluation when `-detach` is
|
||||||
|
not used.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,9 @@ promotes all task groups. The group flag can be specified multiple times to
|
||||||
select particular groups to promote.
|
select particular groups to promote.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `submit-job`,
|
When ACLs are enabled, this command requires a token with the `submit-job`,
|
||||||
`list-jobs`, and `read-job` capabilities for the job's namespace.
|
and `read-job` capabilities for the job's namespace. The `list-jobs`
|
||||||
|
capability is required to run the command with a job prefix instead of the
|
||||||
|
exact job ID.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,10 @@ The `job revert` command requires two inputs, the job ID and the version of that
|
||||||
job to revert to.
|
job to revert to.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `submit-job`
|
When ACLs are enabled, this command requires a token with the `submit-job`
|
||||||
and `list-jobs` capabilities for the job's namespace.
|
capability for the job's namespace. The `list-jobs` capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID. The `read-job`
|
||||||
|
capability is required to monitor the resulting evaluation when `-detach` is
|
||||||
|
not used.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,12 @@ Scale will issue a request to update the matched job and then invoke an interact
|
||||||
monitor that exits automatically once the scheduler has processed the request.
|
monitor that exits automatically once the scheduler has processed the request.
|
||||||
It is safe to exit the monitor early using ctrl+c.
|
It is safe to exit the monitor early using ctrl+c.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `scale-job`
|
When ACLs are enabled, this command requires a token with the
|
||||||
capability for the job's namespace.
|
`read-job-scaling` and either the `scale-job` or `submit-job` capabilities
|
||||||
|
for the job's namespace. The `list-jobs` capability is required to run the
|
||||||
|
command with a job prefix instead of the exact job ID. The `read-job`
|
||||||
|
capability is required to monitor the resulting evaluation when `-detach` is
|
||||||
|
not used.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,10 @@ nomad job scaling-events [options] <job>
|
||||||
The `job scaling-events` command requires a single argument, a submitted job's
|
The `job scaling-events` command requires a single argument, a submitted job's
|
||||||
ID, and will output the stored scaling events for the job if there are any.
|
ID, and will output the stored scaling events for the job if there are any.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the
|
When ACLs are enabled, this command requires a token with either the
|
||||||
`read-job-scaling` capability for the job's namespace.
|
`read-job` or `read-job-scaling` capability for the job's namespace. The
|
||||||
|
`list-jobs` capability is required to run the command with a job prefix
|
||||||
|
instead of the exact job ID.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,9 @@ shows allocation modification time in addition to create time. When the
|
||||||
`-verbose` flag is not set, allocation creation and modify times are shown in a
|
`-verbose` flag is not set, allocation creation and modify times are shown in a
|
||||||
shortened relative time format like `5m ago`.
|
shortened relative time format like `5m ago`.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `read-job` and
|
When ACLs are enabled, this command requires a token with the `read-job`
|
||||||
`list-jobs` capabilities for the job's namespace.
|
capability for the job's namespace. The `list-jobs` capability is required to
|
||||||
|
run the command with a job prefix instead of the exact job ID.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,10 @@ Stop will issue a request to deregister the matched jobs and then invoke an
|
||||||
interactive monitor that exits automatically once the scheduler has processed
|
interactive monitor that exits automatically once the scheduler has processed
|
||||||
the requests. It is safe to exit the monitor early using ctrl+c.
|
the requests. It is safe to exit the monitor early using ctrl+c.
|
||||||
|
|
||||||
When ACLs are enabled, this command requires a token with the `submit-job`,
|
When ACLs are enabled, this command requires a token with the `submit-job`
|
||||||
`read-job`, and `list-jobs` capabilities for the job's namespace.
|
and `read-job` capabilities for the job's namespace. The `list-jobs`
|
||||||
|
capability is required to run the command with job prefixes instead of exact
|
||||||
|
job IDs.
|
||||||
|
|
||||||
## General Options
|
## General Options
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue