Skip to content

Commit 09e7b9c

Browse files
Permission Items: Common code
Follow-up to #1465 and #1470 Lots of the code is exactly the same since it's the same API. This PR puts it in a common struct so that other permissions resources will be easier to add
1 parent cebd643 commit 09e7b9c

File tree

4 files changed

+257
-400
lines changed

4 files changed

+257
-400
lines changed

docs/resources/folder_permission_item.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ resource "grafana_folder_permission_item" "on_user" {
6363
- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
6464
- `role` (String) the role onto which the permission is to be assigned
6565
- `team` (String) the team onto which the permission is to be assigned
66-
- `user` (String) the user onto which the permission is to be assigned
66+
- `user` (String) the user or service account onto which the permission is to be assigned
6767

6868
### Read-Only
6969

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// This is common code for the folder/dashboards/datasources/service accounts permissions resources.
2+
// They all use the same API for setting permissions, so the code is shared.
3+
4+
package grafana
5+
6+
import (
7+
"strconv"
8+
9+
"github.com/grafana/grafana-openapi-client-go/client"
10+
"github.com/grafana/grafana-openapi-client-go/client/access_control"
11+
"github.com/grafana/grafana-openapi-client-go/models"
12+
"github.com/grafana/terraform-provider-grafana/v2/internal/common"
13+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
14+
"github.com/hashicorp/terraform-plugin-framework/diag"
15+
"github.com/hashicorp/terraform-plugin-framework/path"
16+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
17+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
18+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
19+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
20+
"github.com/hashicorp/terraform-plugin-framework/types"
21+
)
22+
23+
const (
24+
permissionTargetRole = "role"
25+
permissionTargetTeam = "team"
26+
permissionTargetUser = "user"
27+
)
28+
29+
type resourcePermissionItemBaseModel struct {
30+
ID types.String `tfsdk:"id"`
31+
OrgID types.String `tfsdk:"org_id"`
32+
Role types.String `tfsdk:"role"`
33+
Team types.String `tfsdk:"team"`
34+
User types.String `tfsdk:"user"`
35+
Permission types.String `tfsdk:"permission"`
36+
}
37+
38+
type resourcePermissionBase struct {
39+
basePluginFrameworkResource
40+
resourceType string
41+
}
42+
43+
func (r *resourcePermissionBase) addInSchemaAttributes(attributes map[string]schema.Attribute) map[string]schema.Attribute {
44+
targetOneOf := stringvalidator.ExactlyOneOf(
45+
path.MatchRoot(permissionTargetRole),
46+
path.MatchRoot(permissionTargetTeam),
47+
path.MatchRoot(permissionTargetUser),
48+
)
49+
50+
attributes["id"] = schema.StringAttribute{
51+
Computed: true,
52+
PlanModifiers: []planmodifier.String{
53+
stringplanmodifier.UseStateForUnknown(),
54+
},
55+
}
56+
attributes["org_id"] = pluginFrameworkOrgIDAttribute()
57+
attributes[permissionTargetRole] = schema.StringAttribute{
58+
Optional: true,
59+
Description: "the role onto which the permission is to be assigned",
60+
Validators: []validator.String{
61+
stringvalidator.OneOf("Editor", "Viewer"),
62+
targetOneOf,
63+
},
64+
}
65+
attributes[permissionTargetTeam] = schema.StringAttribute{
66+
Optional: true,
67+
Description: "the team onto which the permission is to be assigned",
68+
Validators: []validator.String{
69+
targetOneOf,
70+
},
71+
PlanModifiers: []planmodifier.String{
72+
&orgScopedAttributePlanModifier{},
73+
},
74+
}
75+
attributes[permissionTargetUser] = schema.StringAttribute{
76+
Optional: true,
77+
Description: "the user or service account onto which the permission is to be assigned",
78+
Validators: []validator.String{
79+
targetOneOf,
80+
},
81+
PlanModifiers: []planmodifier.String{
82+
&orgScopedAttributePlanModifier{},
83+
},
84+
}
85+
attributes["permission"] = schema.StringAttribute{
86+
Required: true,
87+
Description: "the permission to be assigned",
88+
Validators: []validator.String{
89+
stringvalidator.OneOf("View", "Edit", "Admin"),
90+
},
91+
}
92+
return attributes
93+
}
94+
95+
func (r *resourcePermissionBase) readItem(id string, checkExistsFunc func(client *client.GrafanaHTTPAPI, itemID string) error) (*resourcePermissionItemBaseModel, diag.Diagnostics) {
96+
client, orgID, splitID, err := r.clientFromExistingOrgResource(resourceFolderPermissionItemID, id)
97+
if err != nil {
98+
return nil, diag.Diagnostics{diag.NewErrorDiagnostic("Unable to parse resource ID", err.Error())}
99+
}
100+
itemID := splitID[0].(string)
101+
permissionTargetType := splitID[1].(string)
102+
permissionTargetID := splitID[2].(string)
103+
104+
// Check that the resource exists. This depends on the resource type, so a generic function is passed in.
105+
if err := checkExistsFunc(client, itemID); err != nil {
106+
if common.IsNotFoundError(err) {
107+
return nil, nil
108+
}
109+
return nil, diag.Diagnostics{diag.NewErrorDiagnostic("Resource does not exist", err.Error())}
110+
}
111+
112+
// GET
113+
permissionsResp, err := client.AccessControl.GetResourcePermissions(itemID, r.resourceType)
114+
if err != nil {
115+
if common.IsNotFoundError(err) {
116+
return nil, nil
117+
}
118+
return nil, diag.Diagnostics{diag.NewErrorDiagnostic("Failed to read permissions", err.Error())}
119+
}
120+
121+
for _, permission := range permissionsResp.Payload {
122+
data := &resourcePermissionItemBaseModel{
123+
ID: types.StringValue(id),
124+
OrgID: types.StringValue(strconv.FormatInt(orgID, 10)),
125+
Permission: types.StringValue(permission.Permission),
126+
}
127+
switch permissionTargetType {
128+
case permissionTargetTeam:
129+
if v := strconv.FormatInt(permission.TeamID, 10); v == permissionTargetID {
130+
data.Team = types.StringValue(v)
131+
} else {
132+
continue
133+
}
134+
case permissionTargetUser:
135+
if v := strconv.FormatInt(permission.UserID, 10); v == permissionTargetID {
136+
data.User = types.StringValue(v)
137+
} else {
138+
continue
139+
}
140+
case permissionTargetRole:
141+
if permission.BuiltInRole == permissionTargetID {
142+
data.Role = types.StringValue(permissionTargetID)
143+
} else {
144+
continue
145+
}
146+
default:
147+
return nil, diag.Diagnostics{diag.NewErrorDiagnostic("Unknown permission target type", permissionTargetType)}
148+
}
149+
150+
return data, nil
151+
}
152+
153+
return nil, nil
154+
}
155+
156+
func (r *resourcePermissionBase) writeItem(itemID string, data *resourcePermissionItemBaseModel) diag.Diagnostics {
157+
client, orgID := r.clientFromNewOrgResource(data.OrgID.ValueString())
158+
data.OrgID = types.StringValue(strconv.FormatInt(orgID, 10))
159+
160+
var err error
161+
switch {
162+
case !data.User.IsNull():
163+
_, userIDStr := SplitOrgResourceID(data.User.ValueString())
164+
userID, parseErr := strconv.ParseInt(userIDStr, 10, 64)
165+
if parseErr != nil {
166+
return diag.Diagnostics{diag.NewErrorDiagnostic("Failed to parse user ID", parseErr.Error())}
167+
}
168+
_, err = client.AccessControl.SetResourcePermissionsForUser(
169+
access_control.NewSetResourcePermissionsForUserParams().
170+
WithUserID(userID).
171+
WithBody(&models.SetPermissionCommand{
172+
Permission: data.Permission.ValueString(),
173+
}).
174+
WithResource(foldersPermissionsType).
175+
WithResourceID(itemID),
176+
)
177+
data.ID = types.StringValue(
178+
resourceFolderPermissionItemID.Make(orgID, itemID, permissionTargetUser, userIDStr),
179+
)
180+
case !data.Team.IsNull():
181+
_, teamIDStr := SplitOrgResourceID(data.Team.ValueString())
182+
teamID, parseErr := strconv.ParseInt(teamIDStr, 10, 64)
183+
if parseErr != nil {
184+
return diag.Diagnostics{diag.NewErrorDiagnostic("Failed to parse user ID", parseErr.Error())}
185+
}
186+
_, err = client.AccessControl.SetResourcePermissionsForTeam(
187+
access_control.NewSetResourcePermissionsForTeamParams().
188+
WithTeamID(teamID).
189+
WithBody(&models.SetPermissionCommand{
190+
Permission: data.Permission.ValueString(),
191+
}).
192+
WithResource(foldersPermissionsType).
193+
WithResourceID(itemID),
194+
)
195+
data.ID = types.StringValue(
196+
resourceFolderPermissionItemID.Make(orgID, itemID, permissionTargetTeam, teamIDStr),
197+
)
198+
case !data.Role.IsNull():
199+
_, err = client.AccessControl.SetResourcePermissionsForBuiltInRole(
200+
access_control.NewSetResourcePermissionsForBuiltInRoleParams().
201+
WithBuiltInRole(data.Role.ValueString()).
202+
WithBody(&models.SetPermissionCommand{
203+
Permission: data.Permission.ValueString(),
204+
}).
205+
WithResource(foldersPermissionsType).
206+
WithResourceID(itemID),
207+
)
208+
data.ID = types.StringValue(
209+
resourceFolderPermissionItemID.Make(orgID, itemID, permissionTargetRole, data.Role.ValueString()),
210+
)
211+
}
212+
if err != nil {
213+
return diag.Diagnostics{diag.NewErrorDiagnostic("Failed to write permissions", err.Error())}
214+
}
215+
return nil
216+
}

0 commit comments

Comments
 (0)