mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
dynamic host volumes: add -id arg for updates of existing volumes (#24996)
If you create a volume via `volume create/register` and want to update it later, you need to change the volume spec to add the ID that was returned. This isn't a very nice UX, so let's add an `-id` argument that allows you to update existing volumes that have that ID. Ref: https://hashicorp.atlassian.net/browse/NET-12083
This commit is contained in:
@@ -42,6 +42,10 @@ Create Options:
|
|||||||
command. If -detach is omitted or false, the command will monitor the state
|
command. If -detach is omitted or false, the command will monitor the state
|
||||||
of the volume until it is ready to be scheduled.
|
of the volume until it is ready to be scheduled.
|
||||||
|
|
||||||
|
-id
|
||||||
|
Update a volume previously created with this ID prefix. Used for dynamic
|
||||||
|
host volumes only.
|
||||||
|
|
||||||
-verbose
|
-verbose
|
||||||
Display full information when monitoring volume state. Used for dynamic host
|
Display full information when monitoring volume state. Used for dynamic host
|
||||||
volumes only.
|
volumes only.
|
||||||
@@ -60,6 +64,7 @@ func (c *VolumeCreateCommand) AutocompleteFlags() complete.Flags {
|
|||||||
"-detach": complete.PredictNothing,
|
"-detach": complete.PredictNothing,
|
||||||
"-verbose": complete.PredictNothing,
|
"-verbose": complete.PredictNothing,
|
||||||
"-policy-override": complete.PredictNothing,
|
"-policy-override": complete.PredictNothing,
|
||||||
|
"-id": complete.PredictNothing,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +80,12 @@ func (c *VolumeCreateCommand) Name() string { return "volume create" }
|
|||||||
|
|
||||||
func (c *VolumeCreateCommand) Run(args []string) int {
|
func (c *VolumeCreateCommand) Run(args []string) int {
|
||||||
var detach, verbose, override bool
|
var detach, verbose, override bool
|
||||||
|
var volID string
|
||||||
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
||||||
flags.BoolVar(&detach, "detach", false, "detach from monitor")
|
flags.BoolVar(&detach, "detach", false, "detach from monitor")
|
||||||
flags.BoolVar(&verbose, "verbose", false, "display full volume IDs")
|
flags.BoolVar(&verbose, "verbose", false, "display full volume IDs")
|
||||||
flags.BoolVar(&override, "policy-override", false, "override soft mandatory Sentinel policies")
|
flags.BoolVar(&override, "policy-override", false, "override soft mandatory Sentinel policies")
|
||||||
|
flags.StringVar(&volID, "id", "", "update an existing dynamic host volume")
|
||||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||||
|
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
@@ -129,7 +136,7 @@ func (c *VolumeCreateCommand) Run(args []string) int {
|
|||||||
case "csi":
|
case "csi":
|
||||||
return c.csiCreate(client, ast)
|
return c.csiCreate(client, ast)
|
||||||
case "host":
|
case "host":
|
||||||
return c.hostVolumeCreate(client, ast, detach, verbose, override)
|
return c.hostVolumeCreate(client, ast, detach, verbose, override, volID)
|
||||||
default:
|
default:
|
||||||
c.Ui.Error(fmt.Sprintf("Error unknown volume type: %s", volType))
|
c.Ui.Error(fmt.Sprintf("Error unknown volume type: %s", volType))
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -19,13 +19,34 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *VolumeCreateCommand) hostVolumeCreate(
|
func (c *VolumeCreateCommand) hostVolumeCreate(
|
||||||
client *api.Client, ast *ast.File, detach, verbose, override bool) int {
|
client *api.Client, ast *ast.File, detach, verbose, override bool, volID string) int {
|
||||||
|
|
||||||
vol, err := decodeHostVolume(ast)
|
vol, err := decodeHostVolume(ast)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error decoding the volume definition: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error decoding the volume definition: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
if volID != "" {
|
||||||
|
ns := c.namespace
|
||||||
|
if vol.Namespace != "" {
|
||||||
|
ns = vol.Namespace
|
||||||
|
}
|
||||||
|
stub, possible, err := getHostVolumeByPrefix(client, volID, ns)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Could not update existing volume: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if len(possible) > 0 {
|
||||||
|
out, err := formatHostVolumes(possible, formatOpts{short: true})
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error formatting: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
c.Ui.Error(fmt.Sprintf("Prefix matched multiple volumes\n\n%s", out))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
vol.ID = stub.ID
|
||||||
|
}
|
||||||
|
|
||||||
req := &api.HostVolumeCreateRequest{
|
req := &api.HostVolumeCreateRequest{
|
||||||
Volume: vol,
|
Volume: vol,
|
||||||
@@ -44,7 +65,6 @@ func (c *VolumeCreateCommand) hostVolumeCreate(
|
|||||||
fmt.Sprintf("[bold][yellow]Volume Warnings:\n%s[reset]\n", resp.Warnings)))
|
fmt.Sprintf("[bold][yellow]Volume Warnings:\n%s[reset]\n", resp.Warnings)))
|
||||||
}
|
}
|
||||||
|
|
||||||
var volID string
|
|
||||||
var lastIndex uint64
|
var lastIndex uint64
|
||||||
|
|
||||||
if detach || vol.State == api.HostVolumeStateReady {
|
if detach || vol.State == api.HostVolumeStateReady {
|
||||||
|
|||||||
@@ -80,6 +80,13 @@ parameters {
|
|||||||
got, _, err := client.HostVolumes().Get(id, &api.QueryOptions{Namespace: "prod"})
|
got, _, err := client.HostVolumes().Get(id, &api.QueryOptions{Namespace: "prod"})
|
||||||
must.NoError(t, err)
|
must.NoError(t, err)
|
||||||
must.NotNil(t, got)
|
must.NotNil(t, got)
|
||||||
|
|
||||||
|
// Verify we can update the volume without changes
|
||||||
|
args = []string{"-address", url, "-detach", "-id", got.ID, file.Name()}
|
||||||
|
code = cmd.Run(args)
|
||||||
|
must.Eq(t, 0, code, must.Sprintf("got error: %s", ui.ErrorWriter.String()))
|
||||||
|
list, _, err := client.HostVolumes().List(nil, &api.QueryOptions{Namespace: "prod"})
|
||||||
|
must.Len(t, 1, list, must.Sprintf("new volume should not be created on update"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHostVolume_HCLDecode(t *testing.T) {
|
func TestHostVolume_HCLDecode(t *testing.T) {
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ General Options:
|
|||||||
|
|
||||||
Register Options:
|
Register Options:
|
||||||
|
|
||||||
|
-id
|
||||||
|
Update a volume previously created with this ID prefix. Used for dynamic
|
||||||
|
host volumes only.
|
||||||
|
|
||||||
-policy-override
|
-policy-override
|
||||||
Sets the flag to force override any soft mandatory Sentinel policies. Used
|
Sets the flag to force override any soft mandatory Sentinel policies. Used
|
||||||
for dynamic host volumes only.
|
for dynamic host volumes only.
|
||||||
@@ -50,6 +54,7 @@ func (c *VolumeRegisterCommand) AutocompleteFlags() complete.Flags {
|
|||||||
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
||||||
complete.Flags{
|
complete.Flags{
|
||||||
"-policy-override": complete.PredictNothing,
|
"-policy-override": complete.PredictNothing,
|
||||||
|
"-id": complete.PredictNothing,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,8 +70,10 @@ func (c *VolumeRegisterCommand) Name() string { return "volume register" }
|
|||||||
|
|
||||||
func (c *VolumeRegisterCommand) Run(args []string) int {
|
func (c *VolumeRegisterCommand) Run(args []string) int {
|
||||||
var override bool
|
var override bool
|
||||||
|
var volID string
|
||||||
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
||||||
flags.BoolVar(&override, "policy-override", false, "override soft mandatory Sentinel policies")
|
flags.BoolVar(&override, "policy-override", false, "override soft mandatory Sentinel policies")
|
||||||
|
flags.StringVar(&volID, "id", "", "update an existing dynamic host volume")
|
||||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||||
|
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
@@ -118,7 +125,7 @@ func (c *VolumeRegisterCommand) Run(args []string) int {
|
|||||||
case "csi":
|
case "csi":
|
||||||
return c.csiRegister(client, ast)
|
return c.csiRegister(client, ast)
|
||||||
case "host":
|
case "host":
|
||||||
return c.hostVolumeRegister(client, ast, override)
|
return c.hostVolumeRegister(client, ast, override, volID)
|
||||||
default:
|
default:
|
||||||
c.Ui.Error(fmt.Sprintf("Error unknown volume type: %s", volType))
|
c.Ui.Error(fmt.Sprintf("Error unknown volume type: %s", volType))
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/hashicorp/nomad/api"
|
"github.com/hashicorp/nomad/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *VolumeRegisterCommand) hostVolumeRegister(client *api.Client, ast *ast.File, override bool) int {
|
func (c *VolumeRegisterCommand) hostVolumeRegister(client *api.Client, ast *ast.File, override bool, volID string) int {
|
||||||
vol, err := decodeHostVolume(ast)
|
vol, err := decodeHostVolume(ast)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error decoding the volume definition: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error decoding the volume definition: %s", err))
|
||||||
@@ -20,6 +20,27 @@ func (c *VolumeRegisterCommand) hostVolumeRegister(client *api.Client, ast *ast.
|
|||||||
c.Ui.Error("Node ID is required for registering")
|
c.Ui.Error("Node ID is required for registering")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
if volID != "" {
|
||||||
|
ns := c.namespace
|
||||||
|
if vol.Namespace != "" {
|
||||||
|
ns = vol.Namespace
|
||||||
|
}
|
||||||
|
stub, possible, err := getHostVolumeByPrefix(client, volID, ns)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Could not update existing volume: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if len(possible) > 0 {
|
||||||
|
out, err := formatHostVolumes(possible, formatOpts{short: true})
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error formatting: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
c.Ui.Error(fmt.Sprintf("Prefix matched multiple volumes\n\n%s", out))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
vol.ID = stub.ID
|
||||||
|
}
|
||||||
|
|
||||||
req := &api.HostVolumeRegisterRequest{
|
req := &api.HostVolumeRegisterRequest{
|
||||||
Volume: vol,
|
Volume: vol,
|
||||||
|
|||||||
@@ -92,4 +92,11 @@ parameters {
|
|||||||
got, _, err := client.HostVolumes().Get(id, &api.QueryOptions{Namespace: "prod"})
|
got, _, err := client.HostVolumes().Get(id, &api.QueryOptions{Namespace: "prod"})
|
||||||
must.NoError(t, err)
|
must.NoError(t, err)
|
||||||
must.NotNil(t, got)
|
must.NotNil(t, got)
|
||||||
|
|
||||||
|
// Verify we can update the volume without changes
|
||||||
|
args = []string{"-address", url, "-id", got.ID, file.Name()}
|
||||||
|
code = cmd.Run(args)
|
||||||
|
must.Eq(t, 0, code, must.Sprintf("got error: %s", ui.ErrorWriter.String()))
|
||||||
|
list, _, err := client.HostVolumes().List(nil, &api.QueryOptions{Namespace: "prod"})
|
||||||
|
must.Len(t, 1, list, must.Sprintf("new volume should not be registered on update"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func (c *VolumeStatusCommand) hostVolumeStatus(client *api.Client, id, nodeID, n
|
|||||||
// if an exact match is not found. note we can't use the shared getByPrefix
|
// if an exact match is not found. note we can't use the shared getByPrefix
|
||||||
// helper here because the List API doesn't match the required signature
|
// helper here because the List API doesn't match the required signature
|
||||||
|
|
||||||
volStub, possible, err := c.getByPrefix(client, id)
|
volStub, possible, err := getHostVolumeByPrefix(client, id, c.namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error listing volumes: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error listing volumes: %s", err))
|
||||||
return 1
|
return 1
|
||||||
@@ -91,10 +91,10 @@ func (c *VolumeStatusCommand) listHostVolumes(client *api.Client, nodeID, nodePo
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VolumeStatusCommand) getByPrefix(client *api.Client, prefix string) (*api.HostVolumeStub, []*api.HostVolumeStub, error) {
|
func getHostVolumeByPrefix(client *api.Client, prefix, ns string) (*api.HostVolumeStub, []*api.HostVolumeStub, error) {
|
||||||
vols, _, err := client.HostVolumes().List(nil, &api.QueryOptions{
|
vols, _, err := client.HostVolumes().List(nil, &api.QueryOptions{
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
Namespace: c.namespace,
|
Namespace: ns,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user