mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
stateful deployments: task group host volume claims CLI (#25116)
CLI for interacting with task group host volume claims.
This commit is contained in:
committed by
GitHub
parent
6ae1444cf4
commit
73a193f6d9
@@ -13,7 +13,7 @@ import (
|
||||
func (s *HTTPServer) TaskGroupHostVolumeClaimRequest(resp http.ResponseWriter, req *http.Request) (any, error) {
|
||||
// Tokenize the suffix of the path to get the volume id, tolerating a
|
||||
// present or missing trailing slash
|
||||
reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/volume/claim/")
|
||||
reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/volumes/claim/")
|
||||
tokens := strings.FieldsFunc(reqSuffix, func(c rune) bool { return c == '/' })
|
||||
|
||||
if len(tokens) == 0 {
|
||||
|
||||
@@ -1269,6 +1269,21 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"volume claim": func() (cli.Command, error) {
|
||||
return &VolumeClaimCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"volume claim list": func() (cli.Command, error) {
|
||||
return &VolumeClaimListCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"volume claim delete": func() (cli.Command, error) {
|
||||
return &VolumeClaimDeleteCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
deprecated := map[string]cli.CommandFactory{
|
||||
|
||||
@@ -120,6 +120,30 @@ func (m *Meta) AutocompleteFlags(fs FlagSetFlags) complete.Flags {
|
||||
}
|
||||
}
|
||||
|
||||
// askQuestion asks question to user until they provide a valid response.
|
||||
func (m *Meta) askQuestion(question string) bool {
|
||||
for {
|
||||
answer, err := m.Ui.Ask(m.Colorize().Color(fmt.Sprintf("[?] %s", question)))
|
||||
if err != nil {
|
||||
if err.Error() != "interrupted" {
|
||||
m.Ui.Output(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(strings.ToLower(answer)) {
|
||||
case "", "y", "yes":
|
||||
return true
|
||||
case "n", "no":
|
||||
return false
|
||||
default:
|
||||
m.Ui.Output(fmt.Sprintf(`%q is not a valid response, please answer "yes" or "no".`, answer))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ApiClientFactory is the signature of a API client factory
|
||||
type ApiClientFactory func() (*api.Client, error)
|
||||
|
||||
|
||||
@@ -583,30 +583,6 @@ func (s *SetupConsulCommand) createPolicy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// askQuestion asks question to user until they provide a valid response.
|
||||
func (s *SetupConsulCommand) askQuestion(question string) bool {
|
||||
for {
|
||||
answer, err := s.Ui.Ask(s.Colorize().Color(fmt.Sprintf("[?] %s", question)))
|
||||
if err != nil {
|
||||
if err.Error() != "interrupted" {
|
||||
s.Ui.Output(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(strings.ToLower(answer)) {
|
||||
case "", "y", "yes":
|
||||
return true
|
||||
case "n", "no":
|
||||
return false
|
||||
default:
|
||||
s.Ui.Output(fmt.Sprintf(`%q is not a valid response, please answer "yes" or "no".`, answer))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SetupConsulCommand) handleNo() {
|
||||
s.Ui.Warn(`
|
||||
By answering "no" to any of these questions, you are risking an incorrect Consul
|
||||
|
||||
@@ -564,30 +564,6 @@ func (s *SetupVaultCommand) createNamespace(ns string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// askQuestion asks question to user until they provide a valid response.
|
||||
func (s *SetupVaultCommand) askQuestion(question string) bool {
|
||||
for {
|
||||
answer, err := s.Ui.Ask(s.Colorize().Color(fmt.Sprintf("[?] %s", question)))
|
||||
if err != nil {
|
||||
if err.Error() != "interrupted" {
|
||||
s.Ui.Output(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(strings.ToLower(answer)) {
|
||||
case "", "y", "yes":
|
||||
return true
|
||||
case "n", "no":
|
||||
return false
|
||||
default:
|
||||
s.Ui.Output(fmt.Sprintf(`%q is not a valid response, please answer "yes" or "no".`, answer))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SetupVaultCommand) handleNo() {
|
||||
s.Ui.Warn(`
|
||||
By answering "no" to any of these questions, you are risking an incorrect Vault
|
||||
|
||||
46
command/volume_claim.go
Normal file
46
command/volume_claim.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
)
|
||||
|
||||
// ensure interface satisfaction
|
||||
var _ cli.Command = &VolumeClaimCommand{}
|
||||
|
||||
type VolumeClaimCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *VolumeClaimCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad volume claim <subcommand> [options]
|
||||
|
||||
volume claim groups commands that interact with volumes claims.
|
||||
|
||||
List existing volume claims:
|
||||
$ nomad volume claim list
|
||||
|
||||
Delete an existing volume claim:
|
||||
$ nomad volume claim delete <id>
|
||||
|
||||
Please see the individual subcommand help for detailed usage information.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *VolumeClaimCommand) Name() string {
|
||||
return "volume claim"
|
||||
}
|
||||
|
||||
func (c *VolumeClaimCommand) Synopsis() string {
|
||||
return "Interact with volume claims"
|
||||
}
|
||||
|
||||
func (c *VolumeClaimCommand) Run(args []string) int {
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
124
command/volume_claim_delete.go
Normal file
124
command/volume_claim_delete.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
"github.com/hashicorp/nomad/api"
|
||||
)
|
||||
|
||||
// ensure interface satisfaction
|
||||
var _ cli.Command = &VolumeClaimDeleteCommand{}
|
||||
|
||||
var warning string = `
|
||||
If you delete a volume claim, the allocation that uses this claim to "stick"
|
||||
to a particular volume ID will no longer use it upon its next reschedule or
|
||||
migration. The deployment of the task group the allocation runs will still
|
||||
claim another feasible volume ID during reschedule or replacement.
|
||||
`
|
||||
|
||||
type VolumeClaimDeleteCommand struct {
|
||||
Meta
|
||||
|
||||
autoYes bool
|
||||
}
|
||||
|
||||
func (c *VolumeClaimDeleteCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad volume claim delete <id>
|
||||
|
||||
volume claim delete is used to delete existing host volume claim by claim ID.
|
||||
` + warning + `
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
|
||||
|
||||
Delete options:
|
||||
|
||||
-y
|
||||
Automatically answers "yes" to all the questions, making the deletion
|
||||
non-interactive. Defaults to "false".
|
||||
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *VolumeClaimDeleteCommand) Name() string {
|
||||
return "volume claim delete"
|
||||
}
|
||||
|
||||
func (c *VolumeClaimDeleteCommand) Synopsis() string {
|
||||
return "Delete existing volume claim"
|
||||
}
|
||||
|
||||
func (c *VolumeClaimDeleteCommand) Run(args []string) int {
|
||||
flags := c.FlagSet(c.Name(), FlagSetClient)
|
||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||
flags.BoolVar(&c.autoYes, "y", false, "")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check that the last argument is the claim ID to delete
|
||||
if len(flags.Args()) != 1 {
|
||||
c.Ui.Error("This command takes one argument: <claim_id>")
|
||||
c.Ui.Error(commandErrorText(c))
|
||||
return 1
|
||||
}
|
||||
|
||||
if !isTty() && !c.autoYes {
|
||||
c.Ui.Error("This command requires -y option when running in non-interactive mode")
|
||||
return 1
|
||||
}
|
||||
|
||||
claimID := flags.Args()[0]
|
||||
|
||||
if !c.autoYes {
|
||||
c.Ui.Warn(warning)
|
||||
if !c.askQuestion(fmt.Sprintf("Are you sure you want to delete task group host volume claim %s? [Y/n]", claimID)) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Get the HTTP client
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(claimID) == shortId {
|
||||
claimID = sanitizeUUIDPrefix(claimID)
|
||||
claims, _, err := client.TaskGroupHostVolumeClaims().List(nil, &api.QueryOptions{Prefix: claimID})
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error querying claims: %s", err))
|
||||
return 1
|
||||
}
|
||||
// Return error if no claims are found
|
||||
if len(claims) == 0 {
|
||||
c.Ui.Error(fmt.Sprintf("No claim(s) with prefix %q found", claimID))
|
||||
return 1
|
||||
}
|
||||
if len(claims) > 1 {
|
||||
// Dump the output
|
||||
c.Ui.Error(fmt.Sprintf("Prefix matched multiple claims\n\n%s", formatClaims(claims, fullId)))
|
||||
return 1
|
||||
}
|
||||
claimID = claims[0].ID
|
||||
}
|
||||
|
||||
// Delete the specified claim
|
||||
_, err = client.TaskGroupHostVolumeClaims().Delete(claimID, nil)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error deleting claim: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Give some feedback to indicate the deletion was successful.
|
||||
c.Ui.Output(fmt.Sprintf("Task group host volume claim %s successfully deleted", claimID))
|
||||
return 0
|
||||
}
|
||||
107
command/volume_claim_delete_test.go
Normal file
107
command/volume_claim_delete_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/hashicorp/nomad/command/agent"
|
||||
"github.com/hashicorp/nomad/helper/uuid"
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestVolumeClaimDeleteCommand_Run(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
config := func(c *agent.Config) {
|
||||
c.ACL.Enabled = true
|
||||
}
|
||||
|
||||
srv, _, url := testServer(t, true, config)
|
||||
state := srv.Agent.Server().State()
|
||||
defer srv.Shutdown()
|
||||
|
||||
// get an ACL token
|
||||
token := mock.CreatePolicyAndToken(t, state, 999, "good",
|
||||
`namespace "*" { capabilities = ["host-volume-write"] }
|
||||
node { policy = "write" }`)
|
||||
must.NotNil(t, token)
|
||||
|
||||
longID := uuid.Generate()
|
||||
shortID := longID[0:8]
|
||||
longID2 := uuid.Generate()
|
||||
longID2 = shortID + longID2[8:]
|
||||
|
||||
// Create some test claims
|
||||
existingClaims := []*structs.TaskGroupHostVolumeClaim{
|
||||
{
|
||||
ID: longID,
|
||||
Namespace: structs.DefaultNamespace,
|
||||
JobID: "foo",
|
||||
TaskGroupName: "foo",
|
||||
VolumeID: uuid.Generate(),
|
||||
VolumeName: "bar",
|
||||
},
|
||||
// different NS
|
||||
{
|
||||
ID: uuid.Generate(),
|
||||
Namespace: "foo",
|
||||
JobID: "foo",
|
||||
TaskGroupName: "foo",
|
||||
VolumeID: uuid.Generate(),
|
||||
VolumeName: "foo",
|
||||
},
|
||||
{
|
||||
ID: longID2, // same prefix as the longID
|
||||
Namespace: structs.DefaultNamespace,
|
||||
JobID: "bar",
|
||||
TaskGroupName: "foo",
|
||||
VolumeID: uuid.Generate(),
|
||||
VolumeName: "foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, claim := range existingClaims {
|
||||
must.NoError(t, state.UpsertTaskGroupHostVolumeClaim(structs.MsgTypeTestSetup, 1000, claim))
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &VolumeClaimDeleteCommand{Meta: Meta{Ui: ui, flagAddress: url}}
|
||||
|
||||
// Delete with an invalid token fails
|
||||
invalidToken := mock.ACLToken()
|
||||
must.One(t, cmd.Run([]string{"-address=" + url, "-token=" + invalidToken.SecretID, "-y", existingClaims[0].ID}))
|
||||
out := ui.ErrorWriter.String()
|
||||
must.StrContains(t, out, "Permission denied")
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
// Delete with a valid token, but short ID that matches multiple claims
|
||||
must.One(t, cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID, "-y", shortID}))
|
||||
out = ui.ErrorWriter.String()
|
||||
must.StrContains(t, out, "matched multiple claims")
|
||||
ui.ErrorWriter.Reset()
|
||||
|
||||
// Delete with a valid token
|
||||
must.Zero(t, cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID, "-y", existingClaims[0].ID}))
|
||||
out = ui.OutputWriter.String()
|
||||
must.StrContains(t, out, "successfully deleted")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
|
||||
// List and make sure there is just 1 claim left (we have no permissions to read foo ns)
|
||||
listCmd := &VolumeClaimListCommand{Meta: Meta{Ui: ui, flagAddress: url}}
|
||||
must.Zero(t, listCmd.Run([]string{
|
||||
"-address=" + url,
|
||||
"-token=" + token.SecretID,
|
||||
}))
|
||||
out = ui.OutputWriter.String()
|
||||
|
||||
must.StrContains(t, out, shortID)
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
}
|
||||
160
command/volume_claim_list.go
Normal file
160
command/volume_claim_list.go
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
"github.com/hashicorp/nomad/api"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
// ensure interface satisfaction
|
||||
var _ cli.Command = &VolumeClaimListCommand{}
|
||||
|
||||
type VolumeClaimListCommand struct {
|
||||
Meta
|
||||
|
||||
job string
|
||||
taskGroup string
|
||||
volumeName string
|
||||
|
||||
length int
|
||||
verbose bool
|
||||
json bool
|
||||
tmpl string
|
||||
}
|
||||
|
||||
func (c *VolumeClaimListCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad volume claim list [options]
|
||||
|
||||
volume claim list is used to list existing host volume claims.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
|
||||
|
||||
List Options:
|
||||
|
||||
-job <id>
|
||||
Filter volume claims by job ID.
|
||||
|
||||
-group <name>
|
||||
Filter volumes claims by task-group name.
|
||||
|
||||
-volume-name <name>
|
||||
Filter volumes claims by volume name.
|
||||
|
||||
-verbose
|
||||
Display full information.
|
||||
|
||||
-json
|
||||
Output the host volume claims in a JSON format.
|
||||
-t
|
||||
Format and display the host volume claims using a Go template.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *VolumeClaimListCommand) AutocompleteFlags() complete.Flags {
|
||||
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
||||
complete.Flags{
|
||||
"-job": complete.PredictNothing,
|
||||
"-group": complete.PredictNothing,
|
||||
"-volume-name": complete.PredictNothing,
|
||||
"-verbose": complete.PredictNothing,
|
||||
"-json": complete.PredictNothing,
|
||||
"-t": complete.PredictAnything,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *VolumeClaimListCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictNothing
|
||||
}
|
||||
|
||||
func (c *VolumeClaimListCommand) Name() string {
|
||||
return "volume claim list"
|
||||
}
|
||||
|
||||
func (c *VolumeClaimListCommand) Synopsis() string {
|
||||
return "List existing host volume claims"
|
||||
}
|
||||
|
||||
func (c *VolumeClaimListCommand) Run(args []string) int {
|
||||
flags := c.FlagSet(c.Name(), FlagSetClient)
|
||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||
flags.StringVar(&c.job, "job", "", "")
|
||||
flags.StringVar(&c.taskGroup, "group", "", "")
|
||||
flags.StringVar(&c.volumeName, "volume-name", "", "")
|
||||
flags.BoolVar(&c.json, "json", false, "")
|
||||
flags.BoolVar(&c.verbose, "verbose", false, "")
|
||||
flags.StringVar(&c.tmpl, "t", "", "")
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check that we got no arguments
|
||||
if len(flags.Args()) != 0 {
|
||||
c.Ui.Error("This command takes no arguments")
|
||||
c.Ui.Error(commandErrorText(c))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Truncate the id unless full length is requested
|
||||
c.length = shortId
|
||||
if c.verbose {
|
||||
c.length = fullId
|
||||
}
|
||||
|
||||
// Get the HTTP client
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
claims, _, err := client.TaskGroupHostVolumeClaims().List(&api.TaskGroupHostVolumeClaimsListRequest{
|
||||
JobID: c.job,
|
||||
TaskGroup: c.taskGroup,
|
||||
VolumeName: c.volumeName,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error listing task group host volume claims: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
if c.json || len(c.tmpl) > 0 {
|
||||
out, err := Format(c.json, c.tmpl, claims)
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output(out)
|
||||
return 0
|
||||
}
|
||||
|
||||
c.Ui.Output(formatClaims(claims, c.length))
|
||||
return 0
|
||||
}
|
||||
|
||||
func formatClaims(claims []*api.TaskGroupHostVolumeClaim, length int) string {
|
||||
if len(claims) == 0 {
|
||||
return "No task group host volume claims found"
|
||||
}
|
||||
|
||||
output := make([]string, 0, len(claims)+1)
|
||||
output = append(output, "ID|Namespace|Job ID|Volume ID|Volume Name")
|
||||
for _, claim := range claims {
|
||||
output = append(output, fmt.Sprintf(
|
||||
"%s|%s|%s|%s|%s",
|
||||
limit(claim.ID, length), claim.Namespace, claim.JobID, limit(claim.VolumeID, length), claim.VolumeName))
|
||||
}
|
||||
|
||||
return formatList(output)
|
||||
}
|
||||
130
command/volume_claim_list_test.go
Normal file
130
command/volume_claim_list_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
"github.com/shoenig/test/must"
|
||||
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/hashicorp/nomad/command/agent"
|
||||
"github.com/hashicorp/nomad/helper/uuid"
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
|
||||
func TestVolumeClaimListCommand_Run(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
config := func(c *agent.Config) {
|
||||
c.ACL.Enabled = true
|
||||
}
|
||||
|
||||
srv, _, url := testServer(t, true, config)
|
||||
state := srv.Agent.Server().State()
|
||||
defer srv.Shutdown()
|
||||
|
||||
// get an ACL token
|
||||
token := mock.CreatePolicyAndToken(t, state, 999, "good",
|
||||
`namespace "*" { capabilities = ["host-volume-read"] }
|
||||
node { policy = "read" }`)
|
||||
must.NotNil(t, token)
|
||||
|
||||
// Create some test claims
|
||||
existingClaims := []*structs.TaskGroupHostVolumeClaim{
|
||||
{
|
||||
ID: uuid.Generate(),
|
||||
Namespace: structs.DefaultNamespace,
|
||||
JobID: "foo",
|
||||
TaskGroupName: "foo",
|
||||
VolumeID: uuid.Generate(),
|
||||
VolumeName: "bar",
|
||||
},
|
||||
// different NS
|
||||
{
|
||||
ID: uuid.Generate(),
|
||||
Namespace: "foo",
|
||||
JobID: "foo",
|
||||
TaskGroupName: "foo",
|
||||
VolumeID: uuid.Generate(),
|
||||
VolumeName: "foo",
|
||||
},
|
||||
// different Job
|
||||
{
|
||||
ID: uuid.Generate(),
|
||||
Namespace: structs.DefaultNamespace,
|
||||
JobID: "bar",
|
||||
TaskGroupName: "foo",
|
||||
VolumeID: uuid.Generate(),
|
||||
VolumeName: "foo",
|
||||
},
|
||||
// different tg
|
||||
{
|
||||
ID: uuid.Generate(),
|
||||
Namespace: structs.DefaultNamespace,
|
||||
JobID: "foo",
|
||||
TaskGroupName: "bar",
|
||||
VolumeID: uuid.Generate(),
|
||||
VolumeName: "foo",
|
||||
},
|
||||
// different volume name
|
||||
{
|
||||
ID: uuid.Generate(),
|
||||
Namespace: structs.DefaultNamespace,
|
||||
JobID: "foo",
|
||||
TaskGroupName: "bar",
|
||||
VolumeID: uuid.Generate(),
|
||||
VolumeName: "bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, claim := range existingClaims {
|
||||
must.NoError(t, state.UpsertTaskGroupHostVolumeClaim(structs.MsgTypeTestSetup, 1000, claim))
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &VolumeClaimListCommand{Meta: Meta{Ui: ui, flagAddress: url}}
|
||||
|
||||
// List with an invalid token fails
|
||||
invalidToken := mock.ACLToken()
|
||||
code := cmd.Run([]string{"-address=" + url, "-token=" + invalidToken.SecretID})
|
||||
must.One(t, code)
|
||||
|
||||
// List with no token at all
|
||||
code = cmd.Run([]string{"-address=" + url})
|
||||
must.One(t, code)
|
||||
|
||||
// List with a valid token
|
||||
code = cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID, "-verbose"})
|
||||
must.Zero(t, code)
|
||||
out := ui.OutputWriter.String()
|
||||
must.StrContains(t, out, existingClaims[0].ID)
|
||||
|
||||
// List json
|
||||
must.Zero(t, cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID, "-json"}))
|
||||
out = ui.OutputWriter.String()
|
||||
must.StrContains(t, out, "CreateIndex")
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
|
||||
// Filter by job "foo" and volume name "foo"
|
||||
must.Zero(t, cmd.Run([]string{
|
||||
"-address=" + url,
|
||||
"-token=" + token.SecretID,
|
||||
"-job=" + "foo",
|
||||
"-volume-name=" + "foo",
|
||||
"-verbose",
|
||||
}))
|
||||
out = ui.OutputWriter.String()
|
||||
|
||||
// only existingClaims[3] matches this filter
|
||||
must.StrContains(t, out, existingClaims[3].ID)
|
||||
for _, id := range []string{existingClaims[0].ID, existingClaims[1].ID, existingClaims[2].ID, existingClaims[4].ID} {
|
||||
must.StrNotContains(t, out, id, must.Sprintf("did not expect to find %s in %s", id, out))
|
||||
}
|
||||
|
||||
ui.OutputWriter.Reset()
|
||||
}
|
||||
@@ -390,7 +390,7 @@ func (n *nomadFSM) Apply(log *raft.Log) interface{} {
|
||||
case structs.HostVolumeDeleteRequestType:
|
||||
return n.applyHostVolumeDelete(msgType, buf[1:], log.Index)
|
||||
case structs.TaskGroupHostVolumeClaimDeleteRequestType:
|
||||
return n.applyTaskGroupHostVolumeClaimDelete(msgType, buf[1:], log.Index)
|
||||
return n.applyTaskGroupHostVolumeClaimDelete(buf[1:], log.Index)
|
||||
}
|
||||
|
||||
// Check enterprise only message types.
|
||||
@@ -2452,7 +2452,7 @@ func (n *nomadFSM) applyHostVolumeDelete(msgType structs.MessageType, buf []byte
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nomadFSM) applyTaskGroupHostVolumeClaimDelete(msgType structs.MessageType, buf []byte, index uint64) interface{} {
|
||||
func (n *nomadFSM) applyTaskGroupHostVolumeClaimDelete(buf []byte, index uint64) interface{} {
|
||||
defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_task_group_host_volume_claim_delete"}, time.Now())
|
||||
|
||||
var req structs.TaskGroupVolumeClaimDeleteRequest
|
||||
|
||||
Reference in New Issue
Block a user