Skip to content

Commit 0b5033e

Browse files
AluchirBachir Khiativillevsv-upcloudkangasta
authored
feat(obs): extend support for object storage (#458)
### Object Storage Service Management - **Service Creation**: Implemented `object-storage create` command with support for: - `--label` flag for setting labels during creation - `--network` flag for network attachment during creation - `--configured` flag for setting configured status - `--wait` flag that blocks until service reaches "running" state ### Bucket Management - **Bucket Operations**: Implemented bucket management: - `object-storage bucket create` - Create new buckets - `object-storage bucket delete` - Delete existing buckets - `object-storage bucket list` - List all buckets for a service ### Label Management - **Dedicated Label Commands**: Added structured label management: - `object-storage label add` - Add labels to existing services - `object-storage label remove` - Remove specific labels from services - `object-storage label list` - List all labels for a service ### Network Management - **Network Operations**: Added network management: - `object-storage network attach` - Attach networks to services - `object-storage network detach` - Detach networks from services ### User Management - **User Operations**: Added user management: - `object-storage user create` - Create new users - `object-storage user delete` - Delete existing users - `object-storage user list` - List all users for a service - Access Key Management - **Access Key Operations**: Implemented access key management: - `object-storage create-access-key` - Generate new access keys for users - `object-storage delete-access-key` - Remove access keys - `object-storage list-access-keys` - List all access keys for a user --------- Co-authored-by: Bachir Khiati <bachir.khiati@upcloud.com> Co-authored-by: Ville Välimäki <ville.valimaki@upcloud.com> Co-authored-by: Ville Välimäki <110451292+villevsv-upcloud@users.noreply.github.com> Co-authored-by: Toni Kangas <kangasta@users.noreply.github.com>
1 parent cb4e5fa commit 0b5033e

28 files changed

Lines changed: 1463 additions & 27 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Object storage service creation with `object-storage create` command supporting labels, networks, and configured status
13+
- Object storage label management with dedicated commands: `object-storage label add`, `object-storage label remove`, and `object-storage label list`
14+
- Object storage network management with `object-storage network attach` and `object-storage network detach` commands
15+
- Object storage bucket management with `object-storage bucket create`, `object-storage bucket delete`, and `object-storage bucket list` commands
16+
- Object storage user management with `object-storage user create`, `object-storage user delete`, and `object-storage user list` commands
17+
- Object storage user access key management with `object-storage create-access-key`, `object-storage delete-access-key`, and `object-storage list-access-keys` commands
18+
1019
## [3.21.0] - 2025-07-15
1120

1221
### Added

examples/use_object_storage.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Using Object Storage with upctl
2+
3+
This example demonstrates how to create and manage an object storage service, including managing buckets and configuring
4+
S3-compatible access via access keys.
5+
6+
Set environment variables for convenience:
7+
8+
```env
9+
prefix=example-upctl-
10+
region=europe-1
11+
```
12+
13+
Create a managed object storage service:
14+
15+
```sh
16+
upctl object-storage create --name ${prefix}service --network type=public,name=${prefix}network,family=IPv4 --region ${region}
17+
```
18+
19+
List all buckets in your service (will be empty at this point):
20+
21+
```sh
22+
upctl object-storage bucket list ${prefix}service
23+
```
24+
25+
Create a new bucket:
26+
27+
```sh
28+
upctl object-storage bucket create ${prefix}service --name ${prefix}bucket
29+
```
30+
31+
Create a user and an access key for S3-compatible access:
32+
33+
```sh
34+
upctl object-storage user create ${prefix}service --username ${prefix}user
35+
upctl object-storage access-key create ${prefix}service --username ${prefix}user
36+
```
37+
38+
Once not needed anymore, delete the user:
39+
40+
```sh
41+
upctl object-storage user delete ${prefix}service --username ${prefix}user
42+
```
43+
44+
Delete also the managed object storage service along with its buckets:
45+
46+
```sh
47+
upctl object-storage delete ${prefix}service --delete-buckets
48+
```

internal/commands/base/base.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import (
1919
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/network"
2020
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/networkpeering"
2121
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/objectstorage"
22+
objectstorageAccesskey "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/objectstorage/accesskey"
23+
objectstoragebucket "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/objectstorage/bucket"
24+
objectstoragelabel "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/objectstorage/label"
25+
objectstoragenetwork "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/objectstorage/network"
26+
objectstorageuser "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/objectstorage/user"
2227
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/partner"
2328
partneraccount "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/partner/account"
2429
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/root"
@@ -212,11 +217,42 @@ func BuildCommands(rootCmd *cobra.Command, conf *config.Config) {
212217

213218
// Managed object storage operations
214219
objectStorageCommand := commands.BuildCommand(objectstorage.BaseobjectstorageCommand(), rootCmd, conf)
220+
commands.BuildCommand(objectstorage.CreateCommand(), objectStorageCommand.Cobra(), conf)
215221
commands.BuildCommand(objectstorage.DeleteCommand(), objectStorageCommand.Cobra(), conf)
216222
commands.BuildCommand(objectstorage.ListCommand(), objectStorageCommand.Cobra(), conf)
217223
commands.BuildCommand(objectstorage.ShowCommand(), objectStorageCommand.Cobra(), conf)
218224
commands.BuildCommand(objectstorage.RegionsCommand(), objectStorageCommand.Cobra(), conf)
219225

226+
// Object storage user management
227+
userCommand := commands.BuildCommand(objectstorageuser.BaseUserCommand(), objectStorageCommand.Cobra(), conf)
228+
commands.BuildCommand(objectstorageuser.CreateCommand(), userCommand.Cobra(), conf)
229+
commands.BuildCommand(objectstorageuser.DeleteCommand(), userCommand.Cobra(), conf)
230+
commands.BuildCommand(objectstorageuser.ListCommand(), userCommand.Cobra(), conf)
231+
232+
// Object storage access key management
233+
accessKeyCommand := commands.BuildCommand(objectstorageAccesskey.BaseAccessKeyCommand(), objectStorageCommand.Cobra(), conf)
234+
commands.BuildCommand(objectstorageAccesskey.CreateCommand(), accessKeyCommand.Cobra(), conf)
235+
commands.BuildCommand(objectstorageAccesskey.DeleteCommand(), accessKeyCommand.Cobra(), conf)
236+
commands.BuildCommand(objectstorageAccesskey.ListCommand(), accessKeyCommand.Cobra(), conf)
237+
238+
// Object storage network management
239+
objectStorageNetworkCommand := commands.BuildCommand(objectstoragenetwork.BaseNetworkCommand(), objectStorageCommand.Cobra(), conf)
240+
commands.BuildCommand(objectstoragenetwork.AttachCommand(), objectStorageNetworkCommand.Cobra(), conf)
241+
commands.BuildCommand(objectstoragenetwork.DetachCommand(), objectStorageNetworkCommand.Cobra(), conf)
242+
commands.BuildCommand(objectstoragenetwork.ListCommand(), objectStorageNetworkCommand.Cobra(), conf)
243+
244+
// Object storage bucket management
245+
bucketCommand := commands.BuildCommand(objectstoragebucket.BaseBucketCommand(), objectStorageCommand.Cobra(), conf)
246+
commands.BuildCommand(objectstoragebucket.CreateCommand(), bucketCommand.Cobra(), conf)
247+
commands.BuildCommand(objectstoragebucket.DeleteCommand(), bucketCommand.Cobra(), conf)
248+
commands.BuildCommand(objectstoragebucket.ListCommand(), bucketCommand.Cobra(), conf)
249+
250+
// Object storage label management
251+
labelCommand := commands.BuildCommand(objectstoragelabel.BaseLabelCommand(), objectStorageCommand.Cobra(), conf)
252+
commands.BuildCommand(objectstoragelabel.AddCommand(), labelCommand.Cobra(), conf)
253+
commands.BuildCommand(objectstoragelabel.RemoveCommand(), labelCommand.Cobra(), conf)
254+
commands.BuildCommand(objectstoragelabel.ListCommand(), labelCommand.Cobra(), conf)
255+
220256
// Network Gateway operations
221257
gatewayCommand := commands.BuildCommand(gateway.BaseGatewayCommand(), rootCmd, conf)
222258
commands.BuildCommand(gateway.DeleteCommand(), gatewayCommand.Cobra(), conf)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package accesskey
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
5+
)
6+
7+
// BaseAccessKeyCommand creates the base "object-storage access-key" command
8+
func BaseAccessKeyCommand() commands.Command {
9+
return &accessKeyCommand{
10+
BaseCommand: commands.New("access-key", "Manage access keys in managed object storage services"),
11+
}
12+
}
13+
14+
type accessKeyCommand struct {
15+
*commands.BaseCommand
16+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package accesskey
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/output"
9+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/resolver"
10+
"github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request"
11+
)
12+
13+
// CreateCommand creates the 'object-storage access-key create' command
14+
func CreateCommand() commands.Command {
15+
return &createCommand{
16+
BaseCommand: commands.New(
17+
"create",
18+
"Create an access key for a user in managed object storage service",
19+
"upctl object-storage access-key create <service-uuid> --username myuser",
20+
"upctl object-storage access-key create my-service --username myuser",
21+
),
22+
}
23+
}
24+
25+
type createCommand struct {
26+
*commands.BaseCommand
27+
completion.ObjectStorage
28+
resolver.CachingObjectStorage
29+
params request.CreateManagedObjectStorageUserAccessKeyRequest
30+
}
31+
32+
// InitCommand implements Command.InitCommand
33+
func (s *createCommand) InitCommand() {
34+
s.Cobra().Long = commands.WrapLongDescription(`Create an access key for a user in managed object storage service\n\nCreates a new access key for the specified user in a managed object storage service. The access key can be used to authenticate API requests to the object storage service. Note that the secret access key is only shown once during creation and cannot be retrieved later.`)
35+
36+
fs := s.Cobra().Flags()
37+
fs.StringVar(&s.params.Username, "username", "", "The username to create the access key for.")
38+
commands.Must(s.Cobra().MarkFlagRequired("username"))
39+
}
40+
41+
// Execute implements Command.Execute
42+
func (s *createCommand) Execute(exec commands.Executor, serviceUUID string) (output.Output, error) {
43+
s.params.ServiceUUID = serviceUUID
44+
45+
svc := exec.All()
46+
47+
msg := fmt.Sprintf("Creating access key for user %v in service %v", s.params.Username, s.params.ServiceUUID)
48+
exec.PushProgressStarted(msg)
49+
50+
res, err := svc.CreateManagedObjectStorageUserAccessKey(exec.Context(), &s.params)
51+
if err != nil {
52+
return commands.HandleError(exec, msg, err)
53+
}
54+
55+
exec.PushProgressSuccess(msg)
56+
57+
// Handle SecretAccessKey which might be a pointer
58+
var secretKey interface{}
59+
if res.SecretAccessKey != nil {
60+
secretKey = *res.SecretAccessKey
61+
} else {
62+
secretKey = ""
63+
}
64+
65+
return output.MarshaledWithHumanDetails{Value: res, Details: []output.DetailRow{
66+
{Title: "Access Key ID", Value: res.AccessKeyID},
67+
{Title: "Secret Access Key", Value: secretKey},
68+
{Title: "Status", Value: res.Status},
69+
{Title: "Created At", Value: res.CreatedAt},
70+
}}, nil
71+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package accesskey
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/output"
9+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/resolver"
10+
"github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request"
11+
)
12+
13+
// DeleteCommand creates the 'object-storage access-key delete' command
14+
func DeleteCommand() commands.Command {
15+
return &deleteCommand{
16+
BaseCommand: commands.New(
17+
"delete",
18+
"Delete an access key from a user in managed object storage service",
19+
"upctl object-storage access-key delete <service-uuid> --username myuser --access-key-id AKIAIOSFODNN7EXAMPLE",
20+
"upctl object-storage access-key delete my-service --username myuser --access-key-id AKIAIOSFODNN7EXAMPLE",
21+
),
22+
}
23+
}
24+
25+
type deleteCommand struct {
26+
*commands.BaseCommand
27+
completion.ObjectStorage
28+
resolver.CachingObjectStorage
29+
params request.DeleteManagedObjectStorageUserAccessKeyRequest
30+
}
31+
32+
// InitCommand implements Command.InitCommand
33+
func (s *deleteCommand) InitCommand() {
34+
s.Cobra().Long = commands.WrapLongDescription(`Delete an access key from a user in managed object storage service\n\nDeletes the specified access key from the user in the managed object storage service. The access key will be permanently deleted and can no longer be used for authentication.`)
35+
36+
fs := s.Cobra().Flags()
37+
fs.StringVar(&s.params.Username, "username", "", "Username that owns the access key")
38+
fs.StringVar(&s.params.AccessKeyID, "access-key-id", "", "Access key ID to delete")
39+
40+
commands.Must(s.Cobra().MarkFlagRequired("username"))
41+
commands.Must(s.Cobra().MarkFlagRequired("access-key-id"))
42+
}
43+
44+
// Execute implements commands.MultipleArgumentCommand
45+
func (s *deleteCommand) Execute(exec commands.Executor, serviceUUID string) (output.Output, error) {
46+
s.params.ServiceUUID = serviceUUID
47+
48+
svc := exec.All()
49+
50+
msg := fmt.Sprintf("Deleting access key %s for user %s from service %s", s.params.AccessKeyID, s.params.Username, serviceUUID)
51+
exec.PushProgressStarted(msg)
52+
53+
err := svc.DeleteManagedObjectStorageUserAccessKey(exec.Context(), &s.params)
54+
if err != nil {
55+
return commands.HandleError(exec, msg, err)
56+
}
57+
58+
exec.PushProgressSuccess(msg)
59+
60+
return output.OnlyMarshaled{Value: fmt.Sprintf("Access key %s deleted for user %s from service %s", s.params.AccessKeyID, s.params.Username, serviceUUID)}, nil
61+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package accesskey
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/output"
9+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/resolver"
10+
"github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request"
11+
)
12+
13+
// ListCommand creates the 'object-storage access-key list' command
14+
func ListCommand() commands.Command {
15+
return &listCommand{
16+
BaseCommand: commands.New(
17+
"list",
18+
"List access keys for a user in managed object storage service",
19+
"upctl object-storage access-key list <service-uuid> --username myuser",
20+
"upctl object-storage access-key list my-service --username myuser",
21+
),
22+
}
23+
}
24+
25+
type listCommand struct {
26+
*commands.BaseCommand
27+
completion.ObjectStorage
28+
resolver.CachingObjectStorage
29+
params request.GetManagedObjectStorageUserAccessKeysRequest
30+
}
31+
32+
// InitCommand implements Command.InitCommand
33+
func (s *listCommand) InitCommand() {
34+
s.Cobra().Long = commands.WrapLongDescription(`List access keys for a user in managed object storage service\n\nLists all access keys for the specified user in the managed object storage service. This helps you find the access key IDs needed for deletion.`)
35+
36+
fs := s.Cobra().Flags()
37+
fs.StringVar(&s.params.Username, "username", "", "The username of the user to list access keys from.")
38+
commands.Must(s.Cobra().MarkFlagRequired("username"))
39+
}
40+
41+
// Execute implements commands.MultipleArgumentCommand
42+
func (s *listCommand) Execute(exec commands.Executor, uuid string) (output.Output, error) {
43+
s.params.ServiceUUID = uuid
44+
45+
svc := exec.All()
46+
47+
msg := fmt.Sprintf("Listing access keys for user %s in service %s", s.params.Username, uuid)
48+
exec.PushProgressStarted(msg)
49+
50+
res, err := svc.GetManagedObjectStorageUserAccessKeys(exec.Context(), &s.params)
51+
if err != nil {
52+
return commands.HandleError(exec, msg, err)
53+
}
54+
55+
exec.PushProgressSuccess(msg)
56+
57+
rows := []output.TableRow{}
58+
for _, accessKey := range res {
59+
rows = append(rows, output.TableRow{
60+
accessKey.AccessKeyID,
61+
accessKey.Status,
62+
accessKey.CreatedAt.String(),
63+
})
64+
}
65+
66+
return output.MarshaledWithHumanOutput{
67+
Value: res,
68+
Output: output.Table{
69+
Columns: []output.TableColumn{
70+
{Key: "access_key_id", Header: "Access Key ID"},
71+
{Key: "status", Header: "Status"},
72+
{Key: "created_at", Header: "Created"},
73+
},
74+
Rows: rows,
75+
},
76+
}, nil
77+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package bucket
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
5+
)
6+
7+
// BaseBucketCommand creates the base "object-storage bucket" command
8+
func BaseBucketCommand() commands.Command {
9+
return &bucketCommand{
10+
BaseCommand: commands.New("bucket", "Manage buckets in managed object storage services"),
11+
}
12+
}
13+
14+
type bucketCommand struct {
15+
*commands.BaseCommand
16+
}

0 commit comments

Comments
 (0)