Skip to content

Commit d700433

Browse files
authored
feat: add name to Storage Box Subaccount (#806)
Related to https://docs.hetzner.cloud/changelog#2026-01-15-storage-box-subaccount-name Breaking changes: - [`StorabeBoxClient.GetSubaccount`](https://pkg.go.dev/github.com/hetznercloud/hcloud-go/v2/hcloud#StorageBoxClient.GetSubaccount) no longer tries to get a subaccount by `username`, but by `name`. To get a subaccount by `username` please use [`StorabeBoxClient.GetSubaccountByUsername`](https://pkg.go.dev/github.com/hetznercloud/hcloud-go/v2/hcloud#StorageBoxClient.GetSubaccountByUsername).
1 parent 2355652 commit d700433

File tree

5 files changed

+167
-25
lines changed

5 files changed

+167
-25
lines changed

hcloud/schema/storage_box_subaccount.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "time"
55
// StorageBoxSubaccount defines the schema of a Storage Box subaccount.
66
type StorageBoxSubaccount struct {
77
ID int64 `json:"id"`
8+
Name string `json:"name"`
89
Username string `json:"username"`
910
HomeDirectory string `json:"home_directory"`
1011
Server string `json:"server"`
@@ -36,6 +37,7 @@ type StorageBoxSubaccountListResponse struct {
3637

3738
// StorageBoxSubaccountCreateRequest defines the schema of the request when creating a Storage Box subaccount.
3839
type StorageBoxSubaccountCreateRequest struct {
40+
Name string `json:"name,omitempty"`
3941
HomeDirectory string `json:"home_directory"`
4042
Password string `json:"password"`
4143
Description string `json:"description,omitempty"`
@@ -68,6 +70,7 @@ type StorageBoxSubaccountCreateResponseSubaccount struct {
6870

6971
// StorageBoxSubaccountUpdateRequest defines the schema of the request when updating a Storage Box subaccount.
7072
type StorageBoxSubaccountUpdateRequest struct {
73+
Name string `json:"name,omitempty"`
7174
Description *string `json:"description,omitempty"`
7275
Labels *map[string]string `json:"labels,omitempty"`
7376
}

hcloud/storage_box_subaccount.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
// See https://docs.hetzner.cloud/reference/hetzner#storage-box-subaccounts
1616
type StorageBoxSubaccount struct {
1717
ID int64
18+
Name string
1819
Username string
1920
HomeDirectory string
2021
Server string
@@ -34,7 +35,7 @@ type StorageBoxSubaccountAccessSettings struct {
3435
WebDAVEnabled bool
3536
}
3637

37-
// GetSubaccount retrieves a [StorageBoxSubaccount] either by its ID or by its username, depending on whether
38+
// GetSubaccount retrieves a [StorageBoxSubaccount] either by its ID or by its name, depending on whether
3839
// the input can be parsed as an integer. If no matching [StorageBoxSubaccount] is found, it returns nil.
3940
//
4041
// When fetching by ID, see https://docs.hetzner.cloud/reference/hetzner#storage-box-subaccounts-get-a-subaccount
@@ -44,17 +45,17 @@ type StorageBoxSubaccountAccessSettings struct {
4445
func (c *StorageBoxClient) GetSubaccount(
4546
ctx context.Context,
4647
storageBox *StorageBox,
47-
idOrUsername string,
48+
idOrName string,
4849
) (*StorageBoxSubaccount, *Response, error) {
4950
return getByIDOrName(
5051
ctx,
5152
func(ctx context.Context, id int64) (*StorageBoxSubaccount, *Response, error) {
5253
return c.GetSubaccountByID(ctx, storageBox, id)
5354
},
54-
func(ctx context.Context, username string) (*StorageBoxSubaccount, *Response, error) {
55-
return c.GetSubaccountByUsername(ctx, storageBox, username)
55+
func(ctx context.Context, name string) (*StorageBoxSubaccount, *Response, error) {
56+
return c.GetSubaccountByName(ctx, storageBox, name)
5657
},
57-
idOrUsername,
58+
idOrName,
5859
)
5960
}
6061

@@ -84,6 +85,23 @@ func (c *StorageBoxClient) GetSubaccountByID(
8485
return StorageBoxSubaccountFromSchema(respBody.Subaccount), resp, nil
8586
}
8687

88+
// GetSubaccountByName retrieves a [StorageBoxSubaccount] by its name.
89+
//
90+
// See https://docs.hetzner.cloud/reference/hetzner#storage-box-subaccounts-list-subaccounts
91+
//
92+
// Experimental: [StorageBoxClient] is experimental, breaking changes may occur within minor releases.
93+
func (c *StorageBoxClient) GetSubaccountByName(
94+
ctx context.Context,
95+
storageBox *StorageBox,
96+
name string,
97+
) (*StorageBoxSubaccount, *Response, error) {
98+
return firstByName(name, func() ([]*StorageBoxSubaccount, *Response, error) {
99+
return c.ListSubaccounts(ctx, storageBox, StorageBoxSubaccountListOpts{
100+
Name: name,
101+
})
102+
})
103+
}
104+
87105
// GetSubaccountByUsername retrieves a [StorageBoxSubaccount] by its username.
88106
//
89107
// See https://docs.hetzner.cloud/reference/hetzner#storage-box-subaccounts-list-subaccounts
@@ -104,12 +122,16 @@ func (c *StorageBoxClient) GetSubaccountByUsername(
104122
// StorageBoxSubaccountListOpts represents the options for listing [StorageBoxSubaccount].
105123
type StorageBoxSubaccountListOpts struct {
106124
LabelSelector string
125+
Name string
107126
Username string
108127
Sort []string
109128
}
110129

111130
func (o StorageBoxSubaccountListOpts) values() url.Values {
112131
vals := url.Values{}
132+
if o.Name != "" {
133+
vals.Add("name", o.Name)
134+
}
113135
if o.Username != "" {
114136
vals.Add("username", o.Username)
115137
}
@@ -175,6 +197,7 @@ func (c *StorageBoxClient) AllSubaccounts(
175197

176198
// StorageBoxSubaccountCreateOpts represents the options for creating a [StorageBoxSubaccount].
177199
type StorageBoxSubaccountCreateOpts struct {
200+
Name string
178201
HomeDirectory string
179202
Password string
180203
Description string
@@ -228,6 +251,7 @@ func (c *StorageBoxClient) CreateSubaccount(
228251

229252
// StorageBoxSubaccountUpdateOpts represents the options for updating a [StorageBoxSubaccount].
230253
type StorageBoxSubaccountUpdateOpts struct {
254+
Name string
231255
Description *string
232256
Labels map[string]string
233257
}

hcloud/storage_box_subaccount_test.go

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
func TestStorageBoxClientGetSubaccount(t *testing.T) {
16-
t.Run("GetSubaccount (ByID)", func(t *testing.T) {
16+
t.Run("GetSubaccountByID", func(t *testing.T) {
1717
ctx, server, client := makeTestUtils(t)
1818

1919
server.Expect([]mockutil.Request{
@@ -23,9 +23,10 @@ func TestStorageBoxClientGetSubaccount(t *testing.T) {
2323
JSONRaw: `{
2424
"subaccount": {
2525
"id": 13,
26-
"username": "my-user",
27-
"home_directory": "/home/my-user",
28-
"server": "my-server",
26+
"name": "subaccount1",
27+
"home_directory": "/home/subaccount1",
28+
"username": "u516715-sub1",
29+
"server": "u516715.your-storagebox.de",
2930
"access_settings": {
3031
"reachable_externally": true,
3132
"readonly": false,
@@ -53,9 +54,10 @@ func TestStorageBoxClientGetSubaccount(t *testing.T) {
5354
require.NotNil(t, subaccount)
5455

5556
assert.Equal(t, int64(13), subaccount.ID)
56-
assert.Equal(t, "my-user", subaccount.Username)
57-
assert.Equal(t, "/home/my-user", subaccount.HomeDirectory)
58-
assert.Equal(t, "my-server", subaccount.Server)
57+
assert.Equal(t, "subaccount1", subaccount.Name)
58+
assert.Equal(t, "/home/subaccount1", subaccount.HomeDirectory)
59+
assert.Equal(t, "u516715-sub1", subaccount.Username)
60+
assert.Equal(t, "u516715.your-storagebox.de", subaccount.Server)
5961
assert.True(t, subaccount.AccessSettings.ReachableExternally)
6062
assert.False(t, subaccount.AccessSettings.Readonly)
6163
assert.True(t, subaccount.AccessSettings.SambaEnabled)
@@ -66,7 +68,7 @@ func TestStorageBoxClientGetSubaccount(t *testing.T) {
6668
assert.Equal(t, "prod", subaccount.Labels["environment"])
6769
})
6870

69-
t.Run("GetSubaccount (not found)", func(t *testing.T) {
71+
t.Run("GetSubaccountByID (not found)", func(t *testing.T) {
7072
ctx, server, client := makeTestUtils(t)
7173

7274
server.Expect([]mockutil.Request{
@@ -92,12 +94,12 @@ func TestStorageBoxClientGetSubaccount(t *testing.T) {
9294
assert.Equal(t, 404, resp.StatusCode)
9395
})
9496

95-
t.Run("GetSubaccount (ByUsername)", func(t *testing.T) {
97+
t.Run("GetSubaccountByName", func(t *testing.T) {
9698
ctx, server, client := makeTestUtils(t)
9799

98100
server.Expect([]mockutil.Request{
99101
{
100-
Method: "GET", Path: "/storage_boxes/42/subaccounts?username=my-user",
102+
Method: "GET", Path: "/storage_boxes/42/subaccounts?name=subaccount1",
101103
Status: 200,
102104
JSONRaw: `{
103105
"subaccounts": [{ "id": 13 }]
@@ -107,15 +109,38 @@ func TestStorageBoxClientGetSubaccount(t *testing.T) {
107109

108110
storageBox := &StorageBox{ID: 42}
109111

110-
subaccount, resp, err := client.StorageBox.GetSubaccountByUsername(ctx, storageBox, "my-user")
112+
subaccount, resp, err := client.StorageBox.GetSubaccountByName(ctx, storageBox, "subaccount1")
111113
require.NoError(t, err)
112114
require.NotNil(t, resp)
113115
require.NotNil(t, subaccount)
114116

115117
assert.Equal(t, int64(13), subaccount.ID)
116118
})
117119

118-
t.Run("GetSubbacount (IDOrName)", func(t *testing.T) {
120+
t.Run("GetSubaccountByUsername", func(t *testing.T) {
121+
ctx, server, client := makeTestUtils(t)
122+
123+
server.Expect([]mockutil.Request{
124+
{
125+
Method: "GET", Path: "/storage_boxes/42/subaccounts?username=u516715-sub1",
126+
Status: 200,
127+
JSONRaw: `{
128+
"subaccounts": [{ "id": 13 }]
129+
}`,
130+
},
131+
})
132+
133+
storageBox := &StorageBox{ID: 42}
134+
135+
subaccount, resp, err := client.StorageBox.GetSubaccountByUsername(ctx, storageBox, "u516715-sub1")
136+
require.NoError(t, err)
137+
require.NotNil(t, resp)
138+
require.NotNil(t, subaccount)
139+
140+
assert.Equal(t, int64(13), subaccount.ID)
141+
})
142+
143+
t.Run("GetSubaccount", func(t *testing.T) {
119144
ctx, server, client := makeTestUtils(t)
120145

121146
server.Expect([]mockutil.Request{
@@ -127,7 +152,7 @@ func TestStorageBoxClientGetSubaccount(t *testing.T) {
127152
}`,
128153
},
129154
{
130-
Method: "GET", Path: "/storage_boxes/42/subaccounts?username=my-user",
155+
Method: "GET", Path: "/storage_boxes/42/subaccounts?name=subaccount1",
131156
Status: 200,
132157
JSONRaw: `{
133158
"subaccounts": [{ "id": 14 }]
@@ -144,7 +169,7 @@ func TestStorageBoxClientGetSubaccount(t *testing.T) {
144169

145170
assert.Equal(t, int64(13), subaccount.ID)
146171

147-
subaccount, resp, err = client.StorageBox.GetSubaccount(ctx, storageBox, "my-user")
172+
subaccount, resp, err = client.StorageBox.GetSubaccount(ctx, storageBox, "subaccount1")
148173
require.NoError(t, err)
149174
require.NotNil(t, resp)
150175
require.NotNil(t, subaccount)
@@ -223,7 +248,8 @@ func TestStorageBoxClientCreateSubaccount(t *testing.T) {
223248
require.NoError(t, err)
224249

225250
expectedBody := `{
226-
"home_directory": "/home/my-user",
251+
"name": "subaccount1",
252+
"home_directory": "/home/subaccount1",
227253
"password": "my-password",
228254
"access_settings": {
229255
"reachable_externally": true,
@@ -249,7 +275,8 @@ func TestStorageBoxClientCreateSubaccount(t *testing.T) {
249275
storageBox := &StorageBox{ID: 42}
250276

251277
opts := StorageBoxSubaccountCreateOpts{
252-
HomeDirectory: "/home/my-user",
278+
Name: "subaccount1",
279+
HomeDirectory: "/home/subaccount1",
253280
Password: "my-password",
254281
Description: "This describes my subaccount",
255282
AccessSettings: &StorageBoxSubaccountCreateOptsAccessSettings{
@@ -272,6 +299,46 @@ func TestStorageBoxClientCreateSubaccount(t *testing.T) {
272299

273300
assert.Equal(t, int64(42), subaccount.ID)
274301
})
302+
303+
t.Run("CreateSubaccount (minimal)", func(t *testing.T) {
304+
ctx, server, client := makeTestUtils(t)
305+
306+
server.Expect([]mockutil.Request{
307+
{
308+
Method: "POST", Path: "/storage_boxes/42/subaccounts",
309+
Status: 201,
310+
Want: func(t *testing.T, r *http.Request) {
311+
body, err := io.ReadAll(r.Body)
312+
require.NoError(t, err)
313+
314+
expectedBody := `{
315+
"home_directory": "/home/subaccount1",
316+
"password": "my-password"
317+
}`
318+
assert.JSONEq(t, expectedBody, string(body))
319+
},
320+
JSONRaw: `{
321+
"subaccount": { "id": 42 },
322+
"action": { "id": 12345 }
323+
}`,
324+
},
325+
})
326+
327+
storageBox := &StorageBox{ID: 42}
328+
329+
opts := StorageBoxSubaccountCreateOpts{
330+
HomeDirectory: "/home/subaccount1",
331+
Password: "my-password",
332+
}
333+
result, _, err := client.StorageBox.CreateSubaccount(ctx, storageBox, opts)
334+
require.NoError(t, err)
335+
require.NotNil(t, result)
336+
337+
subaccount := result.Subaccount
338+
require.NotNil(t, subaccount)
339+
340+
assert.Equal(t, int64(42), subaccount.ID)
341+
})
275342
}
276343

277344
func TestStorageBoxClientUpdateSubaccount(t *testing.T) {
@@ -287,6 +354,7 @@ func TestStorageBoxClientUpdateSubaccount(t *testing.T) {
287354
require.NoError(t, err)
288355

289356
expectedBody := `{
357+
"name": "subaccount1",
290358
"labels": {
291359
"environment": "prod",
292360
"example.com/my": "label",
@@ -310,6 +378,7 @@ func TestStorageBoxClientUpdateSubaccount(t *testing.T) {
310378
}
311379

312380
opts := StorageBoxSubaccountUpdateOpts{
381+
Name: "subaccount1",
313382
Description: Ptr("Updated description"),
314383
Labels: map[string]string{
315384
"environment": "prod",
@@ -324,6 +393,42 @@ func TestStorageBoxClientUpdateSubaccount(t *testing.T) {
324393

325394
assert.Equal(t, int64(13), subaccount.ID)
326395
})
396+
397+
t.Run("UpdateSubaccount (minimal)", func(t *testing.T) {
398+
ctx, server, client := makeTestUtils(t)
399+
400+
server.Expect([]mockutil.Request{
401+
{
402+
Method: "PUT", Path: "/storage_boxes/42/subaccounts/13",
403+
Status: 200,
404+
Want: func(t *testing.T, r *http.Request) {
405+
body, err := io.ReadAll(r.Body)
406+
require.NoError(t, err)
407+
408+
expectedBody := `{}`
409+
assert.JSONEq(t, expectedBody, string(body))
410+
},
411+
JSONRaw: `{
412+
"subaccount": { "id": 13 }
413+
}`,
414+
},
415+
})
416+
417+
subaccount := &StorageBoxSubaccount{
418+
ID: 13,
419+
StorageBox: &StorageBox{
420+
ID: 42,
421+
},
422+
}
423+
424+
opts := StorageBoxSubaccountUpdateOpts{}
425+
426+
subaccount, _, err := client.StorageBox.UpdateSubaccount(ctx, subaccount, opts)
427+
require.NoError(t, err)
428+
require.NotNil(t, subaccount)
429+
430+
assert.Equal(t, int64(13), subaccount.ID)
431+
})
327432
}
328433

329434
func TestStorageBoxClientDeleteSubaccount(t *testing.T) {
@@ -384,7 +489,7 @@ func TestStorageBoxClientResetSubaccountPassword(t *testing.T) {
384489
require.NotNil(t, action)
385490
}
386491

387-
func TestStorageBoxSubbacountUpdateAccessSettings(t *testing.T) {
492+
func TestStorageBoxSubaccountUpdateAccessSettings(t *testing.T) {
388493
ctx, server, client := makeTestUtils(t)
389494

390495
server.Expect([]mockutil.Request{
@@ -429,7 +534,7 @@ func TestStorageBoxSubbacountUpdateAccessSettings(t *testing.T) {
429534
require.NotNil(t, action)
430535
}
431536

432-
func TestStorageBoxSubbacountChangeHomeDirectory(t *testing.T) {
537+
func TestStorageBoxSubaccountChangeHomeDirectory(t *testing.T) {
433538
ctx, server, client := makeTestUtils(t)
434539

435540
server.Expect([]mockutil.Request{

0 commit comments

Comments
 (0)