diff --git a/command/volume_create.go b/command/volume_create.go index 5a4254e52..cca272e72 100644 --- a/command/volume_create.go +++ b/command/volume_create.go @@ -42,6 +42,10 @@ Create Options: command. If -detach is omitted or false, the command will monitor the state 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 Display full information when monitoring volume state. Used for dynamic host volumes only. @@ -60,6 +64,7 @@ func (c *VolumeCreateCommand) AutocompleteFlags() complete.Flags { "-detach": complete.PredictNothing, "-verbose": 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 { var detach, verbose, override bool + var volID string flags := c.Meta.FlagSet(c.Name(), FlagSetClient) flags.BoolVar(&detach, "detach", false, "detach from monitor") flags.BoolVar(&verbose, "verbose", false, "display full volume IDs") 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()) } if err := flags.Parse(args); err != nil { @@ -129,7 +136,7 @@ func (c *VolumeCreateCommand) Run(args []string) int { case "csi": return c.csiCreate(client, ast) case "host": - return c.hostVolumeCreate(client, ast, detach, verbose, override) + return c.hostVolumeCreate(client, ast, detach, verbose, override, volID) default: c.Ui.Error(fmt.Sprintf("Error unknown volume type: %s", volType)) return 1 diff --git a/command/volume_create_host.go b/command/volume_create_host.go index 50588d15c..3ef709ea1 100644 --- a/command/volume_create_host.go +++ b/command/volume_create_host.go @@ -19,13 +19,34 @@ import ( ) 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) if err != nil { c.Ui.Error(fmt.Sprintf("Error decoding the volume definition: %s", err)) 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{ Volume: vol, @@ -44,7 +65,6 @@ func (c *VolumeCreateCommand) hostVolumeCreate( fmt.Sprintf("[bold][yellow]Volume Warnings:\n%s[reset]\n", resp.Warnings))) } - var volID string var lastIndex uint64 if detach || vol.State == api.HostVolumeStateReady { diff --git a/command/volume_create_host_test.go b/command/volume_create_host_test.go index 00e795483..af1fb1b61 100644 --- a/command/volume_create_host_test.go +++ b/command/volume_create_host_test.go @@ -80,6 +80,13 @@ parameters { got, _, err := client.HostVolumes().Get(id, &api.QueryOptions{Namespace: "prod"}) must.NoError(t, err) 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) { diff --git a/command/volume_register.go b/command/volume_register.go index d47c93b22..ec510a4e5 100644 --- a/command/volume_register.go +++ b/command/volume_register.go @@ -38,6 +38,10 @@ General Options: Register Options: + -id + Update a volume previously created with this ID prefix. Used for dynamic + host volumes only. + -policy-override Sets the flag to force override any soft mandatory Sentinel policies. Used for dynamic host volumes only. @@ -50,6 +54,7 @@ func (c *VolumeRegisterCommand) AutocompleteFlags() complete.Flags { return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), complete.Flags{ "-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 { var override bool + var volID string flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 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()) } if err := flags.Parse(args); err != nil { @@ -118,7 +125,7 @@ func (c *VolumeRegisterCommand) Run(args []string) int { case "csi": return c.csiRegister(client, ast) case "host": - return c.hostVolumeRegister(client, ast, override) + return c.hostVolumeRegister(client, ast, override, volID) default: c.Ui.Error(fmt.Sprintf("Error unknown volume type: %s", volType)) return 1 diff --git a/command/volume_register_host.go b/command/volume_register_host.go index f0f35a78a..ff0929bcb 100644 --- a/command/volume_register_host.go +++ b/command/volume_register_host.go @@ -10,7 +10,7 @@ import ( "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) if err != nil { 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") 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{ Volume: vol, diff --git a/command/volume_register_host_test.go b/command/volume_register_host_test.go index fef179b02..845f0dae8 100644 --- a/command/volume_register_host_test.go +++ b/command/volume_register_host_test.go @@ -92,4 +92,11 @@ parameters { got, _, err := client.HostVolumes().Get(id, &api.QueryOptions{Namespace: "prod"}) must.NoError(t, err) 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")) } diff --git a/command/volume_status_host.go b/command/volume_status_host.go index ebe035ddb..018834eef 100644 --- a/command/volume_status_host.go +++ b/command/volume_status_host.go @@ -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 // 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 { c.Ui.Error(fmt.Sprintf("Error listing volumes: %s", err)) return 1 @@ -91,10 +91,10 @@ func (c *VolumeStatusCommand) listHostVolumes(client *api.Client, nodeID, nodePo 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{ Prefix: prefix, - Namespace: c.namespace, + Namespace: ns, }) if err != nil {