Skip to content

Commit 6db033c

Browse files
authored
feat(filestorage): add initial file-storage support (#681)
1 parent 5d2e918 commit 6db033c

11 files changed

Lines changed: 468 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Add commands for attaching, detaching, and listing policies attached to a user in managed object storage service.
13+
- Add command for listing and deleting file-storage instances.
1314

1415
## [3.29.0] - 2026-02-06
1516

internal/commands/all/list_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func mockListResponses(mService *smock.Service) {
2525
mService.On("GetTags").Return(&upcloud.Tags{}, nil)
2626
mService.On("GetKubernetesClusters", mock.Anything).Return(nil, nil)
2727
mService.On("GetLoadBalancers", mock.Anything).Return(nil, nil)
28+
mService.On("GetFileStorages", mock.Anything).Return(nil, nil)
2829
}
2930

3031
var networks = &upcloud.Networks{Networks: []upcloud.Network{

internal/commands/all/resource.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/UpCloudLtd/progress/messages"
1414
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
1515
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database"
16+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/filestorage"
1617
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/kubernetes"
1718
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/loadbalancer"
1819
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/network"
@@ -31,6 +32,7 @@ const (
3132
includeHelp = "Include resources matching the given name. If defined multiple times, resource is included if it matches any of the given names. `*` matches all resources."
3233
excludeHelp = "Exclude resources matching the given name. If defined multiple times, resource is included if it matches any of the given names."
3334

35+
typeFileStorage = "file-storage"
3436
typeKubernetes = "kubernetes-cluster"
3537
typeLoadBalancer = "load-balancer"
3638
typeCertificateBundle = "certificate-bundle"
@@ -147,7 +149,7 @@ func findResources[T any](exec commands.Executor, wg *sync.WaitGroup, returnChan
147149

148150
func ListResources(exec commands.Executor, include, exclude []string) ([]Resource, error) {
149151
var resources []Resource
150-
returnChan := make(chan findResult, 12)
152+
returnChan := make(chan findResult, 13)
151153

152154
var wg sync.WaitGroup
153155

@@ -163,7 +165,7 @@ func ListResources(exec commands.Executor, include, exclude []string) ([]Resourc
163165
findResources(exec, &wg, returnChan, &resolver.CachingStorage{Access: "private"}, include, exclude)
164166
findResources(exec, &wg, returnChan, &cachingTag{}, include, exclude)
165167
findResources(exec, &wg, returnChan, &cachingCertificateBundle{}, include, exclude)
166-
168+
findResources(exec, &wg, returnChan, &resolver.CachingFileStorage{}, include, exclude)
167169
wg.Wait()
168170
close(returnChan)
169171

@@ -187,6 +189,12 @@ func ListResources(exec commands.Executor, include, exclude []string) ([]Resourc
187189

188190
func getResource(val any) (Resource, error) {
189191
switch v := val.(type) {
192+
case upcloud.FileStorage:
193+
return Resource{
194+
Name: v.Name,
195+
Type: typeFileStorage,
196+
UUID: v.UUID,
197+
}, nil
190198
case upcloud.LoadBalancer:
191199
return Resource{
192200
Name: v.Name,
@@ -265,6 +273,8 @@ func getResource(val any) (Resource, error) {
265273

266274
func deleteResource(exec commands.Executor, resource Resource) (err error) {
267275
switch resource.Type {
276+
case typeFileStorage:
277+
_, err = filestorage.Delete(exec, resource.UUID, true)
268278
case typeKubernetes:
269279
_, err = kubernetes.Delete(exec, resource.UUID, true)
270280
case typeLoadBalancer:

internal/commands/base/base.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
databaseindex "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/index"
1212
databaseproperties "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/properties"
1313
databasesession "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/session"
14+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/filestorage"
1415
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/gateway"
1516
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/host"
1617
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/ipaddress"
@@ -188,6 +189,11 @@ func BuildCommands(rootCmd *cobra.Command, conf *config.Config) {
188189
commands.BuildCommand(databaseindex.DeleteCommand(), indexCommand.Cobra(), conf)
189190
commands.BuildCommand(databaseindex.ListCommand(), indexCommand.Cobra(), conf)
190191

192+
// FileStorage
193+
filestorageCommand := commands.BuildCommand(filestorage.BaseFileStorageCommand(), rootCmd, conf)
194+
commands.BuildCommand(filestorage.ListCommand(), filestorageCommand.Cobra(), conf)
195+
commands.BuildCommand(filestorage.DeleteCommand(), filestorageCommand.Cobra(), conf)
196+
191197
// LoadBalancers
192198
loadbalancerCommand := commands.BuildCommand(loadbalancer.BaseLoadBalancerCommand(), rootCmd, conf)
193199
commands.BuildCommand(loadbalancer.ListCommand(), loadbalancerCommand.Cobra(), conf)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package filestorage
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
7+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/completion"
8+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/config"
9+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/output"
10+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/resolver"
11+
"github.com/spf13/pflag"
12+
13+
"github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request"
14+
)
15+
16+
// DeleteCommand creates the "file-storage delete" command
17+
func DeleteCommand() commands.Command {
18+
return &deleteCommand{
19+
BaseCommand: commands.New(
20+
"delete",
21+
"Delete a file storage service",
22+
"upctl file-storage delete 55199a44-4751-4e27-9394-7c7661910be8",
23+
),
24+
}
25+
}
26+
27+
type deleteCommand struct {
28+
*commands.BaseCommand
29+
resolver.CachingFileStorage
30+
completion.FileStorage
31+
32+
wait config.OptionalBoolean
33+
}
34+
35+
// InitCommand implements Command.InitCommand
36+
func (c *deleteCommand) InitCommand() {
37+
flags := &pflag.FlagSet{}
38+
config.AddToggleFlag(flags, &c.wait, "wait", false, "Wait until the file storage instance has been deleted before returning.")
39+
c.AddFlags(flags)
40+
}
41+
42+
func Delete(exec commands.Executor, uuid string, wait bool) (output.Output, error) {
43+
svc := exec.All()
44+
msg := fmt.Sprintf("Deleting file storage service %v", uuid)
45+
exec.PushProgressStarted(msg)
46+
47+
err := svc.DeleteFileStorage(exec.Context(), &request.DeleteFileStorageRequest{
48+
UUID: uuid,
49+
})
50+
if err != nil {
51+
return commands.HandleError(exec, msg, err)
52+
}
53+
54+
if wait {
55+
exec.PushProgressUpdateMessage(msg, fmt.Sprintf("Waiting for file storage service %s to be deleted", uuid))
56+
err = svc.WaitForFileStorageDeletion(exec.Context(), &request.WaitForFileStorageDeletionRequest{UUID: uuid})
57+
if err != nil {
58+
return commands.HandleError(exec, msg, err)
59+
}
60+
exec.PushProgressUpdateMessage(msg, msg)
61+
}
62+
63+
exec.PushProgressSuccess(msg)
64+
65+
return output.None{}, nil
66+
}
67+
68+
// Execute implements commands.MultipleArgumentCommand
69+
func (c *deleteCommand) Execute(exec commands.Executor, arg string) (output.Output, error) {
70+
return Delete(exec, arg, c.wait.Value())
71+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package filestorage
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
5+
)
6+
7+
// BaseFileStorageCommand creates the base "file-storage" command
8+
func BaseFileStorageCommand() commands.Command {
9+
baseCmd := commands.New("file-storage", "Manage file storage services")
10+
11+
return &filestorageCommand{
12+
BaseCommand: baseCmd,
13+
}
14+
}
15+
16+
type filestorageCommand struct {
17+
*commands.BaseCommand
18+
}
19+
20+
// InitCommand implements Command.InitCommand
21+
func (c *filestorageCommand) InitCommand() {
22+
c.Cobra().Aliases = []string{"nfs", "filestorage"}
23+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package filestorage
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
5+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/output"
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/paging"
7+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/ui"
8+
"github.com/spf13/pflag"
9+
10+
"github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request"
11+
)
12+
13+
// ListCommand creates the "objectstorage list" command
14+
func ListCommand() commands.Command {
15+
return &listCommand{
16+
BaseCommand: commands.New("list", "List file storage services", "upctl file-storage list"),
17+
}
18+
}
19+
20+
type listCommand struct {
21+
*commands.BaseCommand
22+
paging.PageParameters
23+
}
24+
25+
func (c *listCommand) InitCommand() {
26+
fs := &pflag.FlagSet{}
27+
c.ConfigureFlags(fs)
28+
c.AddFlags(fs)
29+
}
30+
31+
// ExecuteWithoutArguments implements commands.NoArgumentCommand
32+
func (c *listCommand) ExecuteWithoutArguments(exec commands.Executor) (output.Output, error) {
33+
svc := exec.All()
34+
filestorages, err := svc.GetFileStorages(exec.Context(), &request.GetFileStoragesRequest{
35+
Page: c.Page(),
36+
})
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
rows := []output.TableRow{}
42+
for _, filestorage := range filestorages {
43+
rows = append(rows, output.TableRow{
44+
filestorage.UUID,
45+
filestorage.Name,
46+
filestorage.Zone,
47+
filestorage.OperationalState,
48+
filestorage.SizeGiB,
49+
})
50+
}
51+
52+
return output.MarshaledWithHumanOutput{
53+
Value: filestorages,
54+
Output: output.Table{
55+
Columns: []output.TableColumn{
56+
{Key: "uuid", Header: "UUID", Colour: ui.DefaultUUUIDColours},
57+
{Key: "name", Header: "Name"},
58+
{Key: "zone", Header: "Zone"},
59+
{Key: "operational_state", Header: "Operational state"},
60+
{Key: "size_gib", Header: "Size (GiB)"},
61+
},
62+
Rows: rows,
63+
},
64+
}, nil
65+
}

internal/completion/filestorage.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package completion
2+
3+
import (
4+
"context"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/service"
7+
"github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
// FileStorage implements argument completion for filestorages, by uuid or name.
12+
type FileStorage struct{}
13+
14+
// make sure FileStorage implements the interface
15+
var _ Provider = FileStorage{}
16+
17+
// CompleteArgument implements completion.Provider
18+
func (s FileStorage) CompleteArgument(ctx context.Context, svc service.AllServices, toComplete string) ([]string, cobra.ShellCompDirective) {
19+
filestorages, err := svc.GetFileStorages(ctx, &request.GetFileStoragesRequest{})
20+
if err != nil {
21+
return None(toComplete)
22+
}
23+
var vals []string
24+
for _, filesto := range filestorages {
25+
vals = append(vals, filesto.UUID, filesto.Name)
26+
}
27+
28+
return MatchStringPrefix(vals, toComplete, true), cobra.ShellCompDirectiveNoFileComp
29+
}

0 commit comments

Comments
 (0)