Skip to content

Commit 7ead31c

Browse files
authored
feat(storage): add encryption support (#282)
1 parent d30af2d commit 7ead31c

16 files changed

Lines changed: 122 additions & 60 deletions

File tree

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
12+
- Support for storage encryption to storage `create`, `clone`, `show`, and `list` commands as well as server `create` and `show` commands.
13+
14+
### Removed
15+
16+
- From human output of `storage list`, _Created_ column. This field is still available in the machine readable outputs.
17+
1018
## [3.2.2] - 2024-01-02
1119

1220
### Added

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.20
44

55
require (
66
github.com/UpCloudLtd/progress v1.0.2
7-
github.com/UpCloudLtd/upcloud-go-api/v6 v6.11.0
7+
github.com/UpCloudLtd/upcloud-go-api/v6 v6.12.0
88
github.com/adrg/xdg v0.3.2
99
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
1010
github.com/gemalto/flume v0.12.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
1717
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
1818
github.com/UpCloudLtd/progress v1.0.2 h1:CTr1bBuFuXop9TEhR1PakbUMPTlUVL7Bgae9JgqXwPg=
1919
github.com/UpCloudLtd/progress v1.0.2/go.mod h1:iGxOnb9HvHW0yrLGUjHr0lxHhn7TehgWwh7a8NqK6iQ=
20-
github.com/UpCloudLtd/upcloud-go-api/v6 v6.11.0 h1:8KvsimMoPPBx8IVebtHJHavrJPoJfNL5jsyW4TAC5m4=
21-
github.com/UpCloudLtd/upcloud-go-api/v6 v6.11.0/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg=
20+
github.com/UpCloudLtd/upcloud-go-api/v6 v6.12.0 h1:Qol8WuStmqWTXO8Hfel6FjCgLOZ98MGVCvg3ExcEs68=
21+
github.com/UpCloudLtd/upcloud-go-api/v6 v6.12.0/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg=
2222
github.com/adrg/xdg v0.3.2 h1:GUSGQ5pHdev83AYhDSS1A/CX+0JIsxbiWtow2DSA+RU=
2323
github.com/adrg/xdg v0.3.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
2424
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=

internal/commands/server/create.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@ var defaultCreateParams = &createParams{
5757

5858
type createParams struct {
5959
request.CreateServerRequest
60-
firewall bool
61-
metadata bool
62-
os string
63-
osStorageSize int
60+
firewall bool
61+
metadata bool
62+
os string
63+
osStorageSize int
64+
osStorageEncrypted config.OptionalBoolean
6465

6566
labels []string
6667
storages []string
@@ -110,6 +111,10 @@ func (s *createParams) processParams(exec commands.Executor) error {
110111
s.StorageDevices[0].Size = s.osStorageSize
111112
}
112113

114+
if s.osStorageEncrypted.Value() {
115+
s.StorageDevices[0].Encrypted = s.osStorageEncrypted.AsUpcloudBoolean()
116+
}
117+
113118
if s.LoginUser == nil {
114119
s.LoginUser = &request.LoginUser{}
115120
}
@@ -130,6 +135,8 @@ func (s *createParams) processParams(exec commands.Executor) error {
130135
}
131136

132137
func (s *createParams) handleStorage(in string, exec commands.Executor) (*request.CreateServerStorageDevice, error) {
138+
var encryptedRaw string
139+
133140
sd := &request.CreateServerStorageDevice{}
134141
fs := &pflag.FlagSet{}
135142
args, err := commands.Parse(in)
@@ -138,6 +145,7 @@ func (s *createParams) handleStorage(in string, exec commands.Executor) (*reques
138145
}
139146
fs.StringVar(&sd.Action, "action", sd.Action, "")
140147
fs.StringVar(&sd.Address, "address", sd.Address, "")
148+
fs.StringVar(&encryptedRaw, "encrypt", encryptedRaw, "")
141149
fs.StringVar(&sd.Storage, "storage", sd.Storage, "")
142150
fs.StringVar(&sd.Type, "type", sd.Type, "")
143151
fs.StringVar(&sd.Tier, "tier", sd.Tier, "")
@@ -148,6 +156,10 @@ func (s *createParams) handleStorage(in string, exec commands.Executor) (*reques
148156
return nil, err
149157
}
150158

159+
if encrypted, err := commands.BoolFromString(encryptedRaw); err == nil {
160+
sd.Encrypted = *encrypted
161+
}
162+
151163
if sd.Action != request.CreateServerStorageDeviceActionCreate {
152164
if sd.Storage == "" {
153165
return nil, fmt.Errorf("storage UUID or Title must be provided for %s operation", sd.Action)
@@ -258,6 +270,7 @@ func (s *createCommand) InitCommand() {
258270
fs.StringArrayVar(&s.params.networks, "network", def.networks, "A network interface for the server, multiple can be declared.\nUsage: --network family=IPv4,type=public\n\n--network type=private,network=037a530b-533e-4cef-b6ad-6af8094bb2bc,ip-address=10.0.0.1")
259271
fs.StringVar(&s.params.os, "os", def.os, "Server OS to use (will be the first storage device). The value should be title or UUID of an either public or private template. Set to empty to fully customise the storages.")
260272
fs.IntVar(&s.params.osStorageSize, "os-storage-size", def.osStorageSize, "OS storage size in GiB. This is only applicable if `os` is also set. Zero value makes the disk equal to the minimum size of the template.")
273+
config.AddToggleFlag(fs, &s.params.osStorageEncrypted, "os-storage-encrypt", false, "Encrypt the OS storage. This is only applicable if `os` is also set.")
261274
fs.StringVar(&s.params.PasswordDelivery, "password-delivery", def.PasswordDelivery, "Defines how password is delivered. Available: email, sms")
262275
fs.StringVar(&s.params.Plan, "plan", def.Plan, "Server plan name. See \"server plans\" command for valid plans. Set to \"custom\" and use `cores` and `memory` options for flexible plan.")
263276
fs.StringVar(&s.params.RemoteAccessPassword, "remote-access-password", def.RemoteAccessPassword, "Defines the remote access password.")

internal/commands/server/create_test.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,9 @@ func TestCreateServer(t *testing.T) {
266266
"--hostname", "example.com",
267267
"--title", "test-server",
268268
"--zone", "uk-lon1",
269+
"--os-storage-encrypt",
269270
"--password-delivery", "email",
270-
"--storage", "action=create,address=virtio,type=disk,size=20,title=new-storage",
271+
"--storage", "action=create,address=virtio,encrypt=true,type=disk,size=20,title=new-storage",
271272
"--storage", fmt.Sprintf("action=clone,storage=%s,title=three-clone", Storage3.Title),
272273
"--storage", fmt.Sprintf("action=attach,storage=%s,type=cdrom", Storage1.Title),
273274
},
@@ -282,20 +283,22 @@ func TestCreateServer(t *testing.T) {
282283
LoginUser: &request.LoginUser{CreatePassword: "yes"},
283284
StorageDevices: request.CreateServerStorageDeviceSlice{
284285
request.CreateServerStorageDevice{
285-
Action: "clone",
286-
Address: "virtio",
287-
Storage: StorageDef.UUID,
288-
Title: "example.com-OS",
289-
Size: 50,
290-
Tier: upcloud.StorageTierMaxIOPS,
291-
Type: upcloud.StorageTypeDisk,
286+
Action: "clone",
287+
Address: "virtio",
288+
Encrypted: upcloud.FromBool(true),
289+
Storage: StorageDef.UUID,
290+
Title: "example.com-OS",
291+
Size: 50,
292+
Tier: upcloud.StorageTierMaxIOPS,
293+
Type: upcloud.StorageTypeDisk,
292294
},
293295
request.CreateServerStorageDevice{
294-
Action: "create",
295-
Address: "virtio",
296-
Title: "new-storage",
297-
Size: 20,
298-
Type: upcloud.StorageTypeDisk,
296+
Action: "create",
297+
Address: "virtio",
298+
Encrypted: upcloud.FromBool(true),
299+
Title: "new-storage",
300+
Size: 20,
301+
Type: upcloud.StorageTypeDisk,
299302
},
300303
request.CreateServerStorageDevice{
301304
Action: "clone",
@@ -470,6 +473,7 @@ func TestCreateServer(t *testing.T) {
470473
assert.Equal(t, test.error, err.Error())
471474
}
472475
} else {
476+
assert.NoError(t, err)
473477
mService.AssertNumberOfCalls(t, "GetStorages", 1)
474478
mService.AssertNumberOfCalls(t, "CreateServer", 1)
475479
}

internal/commands/server/show.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ func (s *showCommand) Execute(exec commands.Executor, uuid string) (output.Outpu
9191
storage.Type,
9292
storage.Address,
9393
storage.Size,
94+
storage.Encrypted,
9495
strings.Join(flags, " "),
9596
})
9697
}
@@ -152,6 +153,7 @@ func (s *showCommand) Execute(exec commands.Executor, uuid string) (output.Outpu
152153
{Key: "type", Header: "Type"},
153154
{Key: "address", Header: "Address"},
154155
{Key: "size", Header: "Size (GiB)"},
156+
{Key: "encrypted", Header: "Encrypted", Format: format.Boolean},
155157
{Key: "flags", Header: "Flags"},
156158
},
157159
Rows: storageRows,

internal/commands/server/show_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,9 @@ func TestServerHumanOutput(t *testing.T) {
172172
173173
Storage: (Flags: B = bootdisk, P = part of plan)
174174
175-
UUID Title Type Address Size (GiB) Flags
176-
────────────────────────────────────── ───────────────────────────────── ────── ────────── ──────────── ───────
177-
012580a1-32a1-466e-a323-689ca16f2d43 Storage for server1.example.com disk virtio:0 20 P
175+
UUID Title Type Address Size (GiB) Encrypted Flags
176+
────────────────────────────────────── ───────────────────────────────── ────── ────────── ──────────── ─────────── ───────
177+
012580a1-32a1-466e-a323-689ca16f2d43 Storage for server1.example.com disk virtio:0 20 no P
178178
179179
NICs: (Flags: S = source IP filtering, B = bootable)
180180

internal/commands/storage/clone.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type cloneCommand struct {
2424

2525
type cloneParams struct {
2626
request.CloneStorageRequest
27+
encrypted config.OptionalBoolean
2728
}
2829

2930
// CloneCommand creates the "storage clone" command
@@ -53,6 +54,7 @@ func (s *cloneCommand) InitCommand() {
5354
flagSet.StringVar(&s.params.Tier, "tier", defaultCloneParams.Tier, "The storage tier to use.")
5455
flagSet.StringVar(&s.params.Title, "title", defaultCloneParams.Title, "A short, informational description.")
5556
flagSet.StringVar(&s.params.Zone, "zone", defaultCloneParams.Zone, namedargs.ZoneDescription("storage"))
57+
config.AddToggleFlag(flagSet, &s.params.encrypted, "encrypt", false, "Encrypt the new storage.")
5658

5759
s.AddFlags(flagSet)
5860
_ = s.Cobra().MarkFlagRequired("title")
@@ -68,6 +70,7 @@ func (s *cloneCommand) Execute(exec commands.Executor, uuid string) (output.Outp
6870
svc := exec.Storage()
6971
req := s.params.CloneStorageRequest
7072
req.UUID = uuid
73+
req.Encrypted = s.params.encrypted.AsUpcloudBoolean()
7174

7275
msg := fmt.Sprintf("Cloning storage %v", uuid)
7376
exec.PushProgressStarted(msg)

internal/commands/storage/clone_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ func TestCloneCommand(t *testing.T) {
6666
Title: "test-title",
6767
},
6868
},
69+
{
70+
name: "encrypted",
71+
args: []string{"--zone", "test-zone", "--title", "test-title", "--encrypt"},
72+
expected: request.CloneStorageRequest{
73+
Encrypted: upcloud.FromBool(true),
74+
UUID: Storage2.UUID,
75+
Zone: "test-zone",
76+
Tier: "hdd",
77+
Title: "test-title",
78+
},
79+
},
6980
{
7081
name: "title is missing",
7182
args: []string{

internal/commands/storage/create.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func newCreateParams() createParams {
5050
type createParams struct {
5151
request.CreateStorageRequest
5252
backupTime string
53+
encrypted config.OptionalBoolean
5354
}
5455

5556
func (s *createParams) processParams() error {
@@ -62,6 +63,9 @@ func (s *createParams) processParams() error {
6263
} else {
6364
s.BackupRule = nil
6465
}
66+
67+
s.Encrypted = s.encrypted.AsUpcloudBoolean()
68+
6569
return nil
6670
}
6771

@@ -76,6 +80,7 @@ func applyCreateFlags(fs *pflag.FlagSet, dst, def *createParams) {
7680
fs.IntVar(&dst.Size, "size", def.Size, "Size of the storage in GiB.")
7781
fs.StringVar(&dst.Zone, "zone", def.Zone, namedargs.ZoneDescription("storage"))
7882
fs.StringVar(&dst.Tier, "tier", def.Tier, "Storage tier.")
83+
config.AddToggleFlag(fs, &dst.encrypted, "encrypt", false, "Encrypt the storage.")
7984
fs.StringVar(&dst.backupTime, "backup-time", def.backupTime, "The time when to create a backup in HH:MM. Empty value means no backups.")
8085
fs.StringVar(&dst.BackupRule.Interval, "backup-interval", def.BackupRule.Interval, "The interval of the backup.\nAvailable: daily,mon,tue,wed,thu,fri,sat,sun")
8186
fs.IntVar(&dst.BackupRule.Retention, "backup-retention", def.BackupRule.Retention, "How long to store the backups in days. The accepted range is 1-1095")

0 commit comments

Comments
 (0)