Skip to content

Commit 8c9eb0e

Browse files
project: Identity and Access Management (#866)
* Support for Entities and IAM endpoints (#754) ## 📝 Description **What does this PR do and why is this change necessary?** Implements: - **GET** /entities - **GET** /iam/role-permissions - **GET** /iam/users/{username}/role-permissions - **PUT** /iam/users/{username}/role-permissions ## ✔️ How to Test **How do I run the relevant unit/integration tests?** ```bash make TEST_ARGS='-run TestIAM' test-unit ``` ```bash make TEST_ARGS='-run TestEntities' test-unit ``` * IO Ready and new IAM Endpoints (#864) ## 📝 Description Adds `io_ready` field to Volumes and 2 new endpoints for IAM ## ✔️ How to Test ``` make test-unit ``` * pr feedback --------- Co-authored-by: Zhiwei Liang <[email protected]>
1 parent 72287a0 commit 8c9eb0e

13 files changed

+395
-1
lines changed

entities.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package linodego
2+
3+
import "context"
4+
5+
// LinodeEntity is anything in Linode with IAM Permissions
6+
type LinodeEntity struct {
7+
ID int `json:"id"`
8+
Label string `json:"label"`
9+
Type string `json:"type"`
10+
}
11+
12+
// ListEntities returns a paginated list of all entities
13+
func (c *Client) ListEntities(ctx context.Context, opts *ListOptions) ([]LinodeEntity, error) {
14+
return getPaginatedResults[LinodeEntity](ctx, c, "entities", opts)
15+
}
16+
17+
// GetEntityRoles returns a list of roles for the entity and user
18+
func (c *Client) GetEntityRoles(ctx context.Context, username string, entityType string, entityID int) ([]string, error) {
19+
perms, err := doGETRequest[[]string](ctx, c,
20+
formatAPIPath("iam/users/%s/permissions/%s/%d", username, entityType, entityID))
21+
if err != nil || perms == nil {
22+
return nil, err
23+
}
24+
25+
return (*perms), err
26+
}

iam.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package linodego
2+
3+
import "context"
4+
5+
// UserRolePermissions are the account and entity permissions for the User
6+
type UserRolePermissions struct {
7+
AccountAccess []string `json:"account_access"`
8+
EntityAccess []UserAccess `json:"entity_access"`
9+
}
10+
11+
// GetUpdateOptions converts UserRolePermissions for use in UpdateUserRolePermissions
12+
func (p *UserRolePermissions) GetUpdateOptions() UserRolePermissionsUpdateOptions {
13+
return UserRolePermissionsUpdateOptions{
14+
AccountAccess: p.AccountAccess,
15+
EntityAccess: p.EntityAccess,
16+
}
17+
}
18+
19+
// UserRolePermissionsUpdateOptions are fields accepted by UpdateUserRolePermissions
20+
type UserRolePermissionsUpdateOptions struct {
21+
AccountAccess []string `json:"account_access"`
22+
EntityAccess []UserAccess `json:"entity_access"`
23+
}
24+
25+
// UserAccess is the breakdown of entities Roles
26+
type UserAccess struct {
27+
ID int `json:"id"`
28+
Type string `json:"type"`
29+
Roles []string `json:"roles"`
30+
}
31+
32+
// AccountRolePermissions are the account and entity roles for the Account
33+
type AccountRolePermissions struct {
34+
AccountAccess []AccountAccess `json:"account_access"`
35+
EntityAccess []AccountAccess `json:"entity_access"`
36+
}
37+
38+
// AccountAccess is the Roles for each Type for the Account
39+
type AccountAccess struct {
40+
Type string `json:"type"`
41+
Roles []Role `json:"roles"`
42+
}
43+
44+
// Role is the IAM Role and its Permissions
45+
type Role struct {
46+
Name string `json:"name"`
47+
Description string `json:"description"`
48+
Permissions []string `json:"permissions"`
49+
}
50+
51+
// GetUserRolePermissions returns any role permissions for username
52+
func (c *Client) GetUserRolePermissions(ctx context.Context, username string) (*UserRolePermissions, error) {
53+
return doGETRequest[UserRolePermissions](ctx, c,
54+
formatAPIPath("iam/users/%s/role-permissions", username),
55+
)
56+
}
57+
58+
// UpdateUserRolePermissions updates any role permissions for username
59+
func (c *Client) UpdateUserRolePermissions(ctx context.Context, username string, opts UserRolePermissionsUpdateOptions) (*UserRolePermissions, error) {
60+
return doPUTRequest[UserRolePermissions](ctx, c,
61+
formatAPIPath("iam/users/%s/role-permissions", username),
62+
opts,
63+
)
64+
}
65+
66+
// GetAccountRolePermissions returns the role permissions for this Account
67+
func (c *Client) GetAccountRolePermissions(ctx context.Context) (*AccountRolePermissions, error) {
68+
return doGETRequest[AccountRolePermissions](ctx, c, "iam/role-permissions")
69+
}
70+
71+
// GetUserAccountPermissions returns the account permissions for username
72+
func (c *Client) GetUserAccountPermissions(ctx context.Context, username string) ([]string, error) {
73+
perms, err := doGETRequest[[]string](ctx, c,
74+
formatAPIPath("iam/users/%s/permissions/account", username))
75+
if err != nil || perms == nil {
76+
return nil, err
77+
}
78+
79+
return (*perms), err
80+
}

test/unit/entities_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package unit
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/linode/linodego"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestEntities_List(t *testing.T) {
12+
fixtureData, err := fixtures.GetFixture("entities_list")
13+
assert.NoError(t, err)
14+
15+
var base ClientBaseCase
16+
base.SetUp(t)
17+
defer base.TearDown(t)
18+
19+
base.MockGet(formatMockAPIPath("entities"), fixtureData)
20+
21+
entities, err := base.Client.ListEntities(context.Background(), &linodego.ListOptions{})
22+
assert.NoError(t, err)
23+
24+
assert.Equal(t, 7, entities[0].ID)
25+
}
26+
27+
func TestEntitiesPermissions_Get(t *testing.T) {
28+
fixtureData, err := fixtures.GetFixture("entities_permissions")
29+
assert.NoError(t, err)
30+
31+
var base ClientBaseCase
32+
base.SetUp(t)
33+
defer base.TearDown(t)
34+
35+
base.MockGet(formatMockAPIPath("iam/users/Linode/permissions/linode/123"), fixtureData)
36+
37+
perms, err := base.Client.GetEntityRoles(context.Background(), "Linode", "linode", 123)
38+
assert.NoError(t, err)
39+
40+
assert.Equal(t, "rebuild_linode", perms[1])
41+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"data": [
3+
{
4+
"id": 7,
5+
"label": "linode7",
6+
"type": "linode"
7+
},
8+
{
9+
"id": 10,
10+
"label": "linode10",
11+
"type": "linode"
12+
},
13+
{
14+
"id": 1,
15+
"label": "no_devices",
16+
"type": "firewall"
17+
},
18+
{
19+
"id": 2,
20+
"label": "active_with_nodebalancer",
21+
"type": "firewall"
22+
}
23+
],
24+
"page": 1,
25+
"pages": 1,
26+
"results": 4
27+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[
2+
"generate_linode_lish_token_remote",
3+
"rebuild_linode",
4+
"shutdown_linode"
5+
]
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"account_access": [
3+
{
4+
"type": "account",
5+
"roles": [
6+
{
7+
"name": "account_admin",
8+
"description": "Access to perform any supported action on all entities of the account",
9+
"permissions": [
10+
"create_linode",
11+
"update_linode",
12+
"update_firewall"
13+
]
14+
}
15+
]
16+
},
17+
{
18+
"type": "linode",
19+
"roles": [
20+
{
21+
"name": "account_linode_admin",
22+
"description": "Access to perform any supported action on all linode instances of the account",
23+
"permissions": [
24+
"create_linode",
25+
"update_linode",
26+
"delete_linode"
27+
]
28+
}
29+
]
30+
},
31+
{
32+
"type": "firewall",
33+
"roles": [
34+
{
35+
"name": "firewall_creator",
36+
"description": "Access to create a firewall instance",
37+
"permissions": [
38+
"update_linode",
39+
"view_linode"
40+
]
41+
}
42+
]
43+
}
44+
],
45+
"entity_access": [
46+
{
47+
"type": "linode",
48+
"roles": [
49+
{
50+
"name": "linode_contributor",
51+
"description": "Access to update a linode instance",
52+
"permissions": [
53+
"update_linode",
54+
"view_linode"
55+
]
56+
}
57+
]
58+
},
59+
{
60+
"type": "firewall",
61+
"roles": [
62+
{
63+
"name": "firewall_viewer",
64+
"description": "Access to view a firewall instance",
65+
"permissions": [
66+
"update_linode",
67+
"view_linode"
68+
]
69+
},
70+
{
71+
"name": "firewall_admin",
72+
"description": "Access to perform any supported action on a firewall instance",
73+
"permissions": [
74+
"update_linode",
75+
"view_linode"
76+
]
77+
}
78+
]
79+
}
80+
]
81+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[
2+
"list_events",
3+
"view_account_settings",
4+
"cancel_account"
5+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"account_access": [
3+
"account_linode_admin",
4+
"linode_creator",
5+
"firewall_creator"
6+
],
7+
"entity_access": [
8+
{
9+
"id": 1,
10+
"type": "linode",
11+
"roles": [
12+
"linode_contributor"
13+
]
14+
},
15+
{
16+
"id": 1,
17+
"type": "firewall",
18+
"roles": [
19+
"firewall_admin"
20+
]
21+
}
22+
]
23+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"account_access": [
3+
"account_linode_admin",
4+
"linode_creator",
5+
"firewall_creator",
6+
"test_admin"
7+
],
8+
"entity_access": [
9+
{
10+
"id": 1,
11+
"type": "linode",
12+
"roles": [
13+
"linode_contributor"
14+
]
15+
},
16+
{
17+
"id": 1,
18+
"type": "firewall",
19+
"roles": [
20+
"firewall_admin"
21+
]
22+
}
23+
]
24+
}

test/unit/fixtures/volume_get.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"hardware_type": "",
1111
"linode_label": "",
1212
"encryption": "",
13+
"io_ready": true,
1314
"created": "2025-01-01T12:00:00",
1415
"updated": "2025-01-10T12:00:00"
1516
}
16-
17+

0 commit comments

Comments
 (0)