Skip to content

Commit 41d31e7

Browse files
author
Markus Schwer
committed
feat(authorization): add custom role resource and data source
1 parent 24b7387 commit 41d31e7

File tree

11 files changed

+1032
-41
lines changed

11 files changed

+1032
-41
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/stackitcloud/terraform-provider-stackit
33
go 1.24.0
44

55
require (
6+
dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/authorization v0.0.0-20251126130857-9f2211a4c524
67
github.com/google/go-cmp v0.7.0
78
github.com/google/uuid v1.6.0
89
github.com/gorilla/mux v1.8.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
22
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
3+
dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/authorization v0.0.0-20251126130857-9f2211a4c524 h1:ITrpsUZNlZSMWF5W7Jixp5ekWxiMgy4KOURODk73uhI=
4+
dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/authorization v0.0.0-20251126130857-9f2211a4c524/go.mod h1:ZupN/2xICyLvD0FPxihbUH2KNXtVBVpp/DzsoyFKib4=
35
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
46
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
57
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=

stackit/internal/services/authorization/authorization_acc_test.go

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
_ "embed"
1212

1313
"github.com/hashicorp/terraform-plugin-testing/config"
14+
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
1415
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
1516
"github.com/hashicorp/terraform-plugin-testing/terraform"
1617
stackitSdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config"
@@ -33,12 +34,33 @@ var invalidRole string
3334
//go:embed testfiles/organization-role.tf
3435
var organizationRole string
3536

37+
//go:embed testfiles/custom-role.tf
38+
var customRole string
39+
3640
var testConfigVars = config.Variables{
3741
"project_id": config.StringVariable(testutil.ProjectId),
3842
"test_service_account": config.StringVariable(testutil.TestProjectServiceAccountEmail),
3943
"organization_id": config.StringVariable(testutil.OrganizationId),
4044
}
4145

46+
var testConfigVarsCustomRole = config.Variables{
47+
"project_id": config.StringVariable(testutil.ProjectId),
48+
"test_service_account": config.StringVariable(testutil.TestProjectServiceAccountEmail),
49+
"organization_id": config.StringVariable(testutil.OrganizationId),
50+
"role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))),
51+
"role_description": config.StringVariable("Some description"),
52+
"role_permissions_0": config.StringVariable("iam.role.list"),
53+
}
54+
55+
var testConfigVarsCustomRoleUpdated = config.Variables{
56+
"project_id": config.StringVariable(testutil.ProjectId),
57+
"test_service_account": config.StringVariable(testutil.TestProjectServiceAccountEmail),
58+
"organization_id": config.StringVariable(testutil.OrganizationId),
59+
"role_name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlpha))),
60+
"role_description": config.StringVariable("Updated description"),
61+
"role_permissions_0": config.StringVariable("iam.role.edit"),
62+
}
63+
4264
func TestAccProjectRoleAssignmentResource(t *testing.T) {
4365
t.Log(testutil.AuthorizationProviderConfig())
4466
resource.Test(t, resource.TestCase{
@@ -53,8 +75,7 @@ func TestAccProjectRoleAssignmentResource(t *testing.T) {
5375
return err
5476
}
5577

56-
members, err := client.ListMembers(context.TODO(), "project", testutil.ProjectId).Execute()
57-
78+
members, err := client.ListMembers(context.Background(), "project", testutil.ProjectId).Execute()
5879
if err != nil {
5980
return err
6081
}
@@ -93,18 +114,105 @@ func TestAccProjectRoleAssignmentResource(t *testing.T) {
93114
},
94115
},
95116
})
117+
118+
resource.Test(t, resource.TestCase{
119+
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
120+
Steps: []resource.TestStep{
121+
{
122+
ConfigVariables: testConfigVarsCustomRole,
123+
Config: testutil.AuthorizationProviderConfig() + customRole,
124+
Check: resource.ComposeAggregateTestCheckFunc(
125+
resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom-role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsCustomRole["project_id"])),
126+
resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom-role", "name", testutil.ConvertConfigVariable(testConfigVarsCustomRole["role_name"])),
127+
resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom-role", "description", testutil.ConvertConfigVariable(testConfigVarsCustomRole["role_description"])),
128+
resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom-role", "permissions.#", "1"),
129+
resource.TestCheckTypeSetElemAttr("stackit_authorization_project_custom_role.custom-role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsCustomRole["role_permissions_0"])),
130+
resource.TestCheckResourceAttrSet("stackit_authorization_project_custom_role.custom-role", "role_id"),
131+
),
132+
},
133+
// Data source
134+
{
135+
ConfigVariables: testConfigVarsCustomRole,
136+
Config: fmt.Sprintf(`
137+
%s
138+
139+
data "stackit_authorization_project_custom_role" "custom-role" {
140+
resource_id = stackit_authorization_project_custom_role.custom-role.resource_id
141+
role_id = stackit_authorization_project_custom_role.custom-role.role_id
142+
}
143+
`,
144+
testutil.AuthorizationProviderConfig()+customRole,
145+
),
146+
Check: resource.ComposeAggregateTestCheckFunc(
147+
resource.TestCheckResourceAttr("data.stackit_authorization_project_custom_role.custom-role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsCustomRole["project_id"])),
148+
resource.TestCheckResourceAttrPair(
149+
"stackit_authorization_project_custom_role.custom-role", "resource_id",
150+
"data.stackit_authorization_project_custom_role.custom-role", "resource_id",
151+
),
152+
resource.TestCheckResourceAttrPair(
153+
"stackit_authorization_project_custom_role.custom-role", "role_id",
154+
"data.stackit_authorization_project_custom_role.custom-role", "role_id",
155+
),
156+
resource.TestCheckResourceAttrPair(
157+
"stackit_authorization_project_custom_role.custom-role", "name",
158+
"data.stackit_authorization_project_custom_role.custom-role", "name",
159+
),
160+
resource.TestCheckResourceAttrPair(
161+
"stackit_authorization_project_custom_role.custom-role", "description",
162+
"data.stackit_authorization_project_custom_role.custom-role", "description",
163+
),
164+
resource.TestCheckResourceAttrPair(
165+
"stackit_authorization_project_custom_role.custom-role", "permissions",
166+
"data.stackit_authorization_project_custom_role.custom-role", "permissions",
167+
),
168+
),
169+
},
170+
// Import
171+
{
172+
ConfigVariables: testConfigVarsCustomRole,
173+
ResourceName: "stackit_authorization_project_custom_role.custom-role",
174+
ImportStateIdFunc: func(s *terraform.State) (string, error) {
175+
r, ok := s.RootModule().Resources["stackit_authorization_project_custom_role.custom-role"]
176+
if !ok {
177+
return "", fmt.Errorf("couldn't find resource stackit_authorization_project_custom_role.custom-role")
178+
}
179+
roleId, ok := r.Primary.Attributes["role_id"]
180+
if !ok {
181+
return "", fmt.Errorf("couldn't find attribute role_id")
182+
}
183+
184+
return fmt.Sprintf("%s,%s", testutil.ProjectId, roleId), nil
185+
},
186+
ImportState: true,
187+
ImportStateVerify: true,
188+
},
189+
// Update
190+
{
191+
ConfigVariables: testConfigVarsCustomRoleUpdated,
192+
Config: testutil.AuthorizationProviderConfig() + customRole,
193+
Check: resource.ComposeAggregateTestCheckFunc(
194+
resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom-role", "resource_id", testutil.ConvertConfigVariable(testConfigVarsCustomRoleUpdated["project_id"])),
195+
resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom-role", "name", testutil.ConvertConfigVariable(testConfigVarsCustomRoleUpdated["role_name"])),
196+
resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom-role", "description", testutil.ConvertConfigVariable(testConfigVarsCustomRoleUpdated["role_description"])),
197+
resource.TestCheckResourceAttr("stackit_authorization_project_custom_role.custom-role", "permissions.#", "1"),
198+
resource.TestCheckTypeSetElemAttr("stackit_authorization_project_custom_role.custom-role", "permissions.*", testutil.ConvertConfigVariable(testConfigVarsCustomRoleUpdated["role_permissions_0"])),
199+
resource.TestCheckResourceAttrSet("stackit_authorization_project_custom_role.custom-role", "role_id"),
200+
),
201+
},
202+
// Deletion is done by the framework implicitly
203+
},
204+
})
96205
}
97206

98207
func authApiClient() (*authorization.APIClient, error) {
99208
var client *authorization.APIClient
100209
var err error
101-
if testutil.AuthorizationCustomEndpoint == "" {
102-
client, err = authorization.NewAPIClient(
103-
stackitSdkConfig.WithRegion("eu01"),
104-
)
210+
if testutil.AuthorizationCustomEndpoint == "" || testutil.TokenCustomEndpoint == "" {
211+
client, err = authorization.NewAPIClient()
105212
} else {
106213
client, err = authorization.NewAPIClient(
107214
stackitSdkConfig.WithEndpoint(testutil.AuthorizationCustomEndpoint),
215+
stackitSdkConfig.WithTokenEndpoint(testutil.TokenCustomEndpoint),
108216
)
109217
}
110218
if err != nil {
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package customrole
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/datasource"
10+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
11+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
"github.com/hashicorp/terraform-plugin-log/tflog"
14+
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
15+
"github.com/stackitcloud/stackit-sdk-go/services/authorization"
16+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
17+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
18+
authorizationUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/authorization/utils"
19+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
20+
)
21+
22+
// Ensure the implementation satisfies the expected interfaces.
23+
var (
24+
_ datasource.DataSource = &customRoleDataSource{}
25+
)
26+
27+
// NewAuthorizationDataSource creates a new customrole of the authorizationDataSource.
28+
func NewCustomRoleDataSource() datasource.DataSource {
29+
return &customRoleDataSource{}
30+
}
31+
32+
// NewProjectRoleAssignmentDataSources is a helper function generate custom role
33+
// data sources for all possible resource types.
34+
func NewCustomRoleDataSources() []func() datasource.DataSource {
35+
resources := make([]func() datasource.DataSource, 0)
36+
for _, v := range resourceTypes {
37+
resources = append(resources, func() datasource.DataSource {
38+
return &customRoleDataSource{
39+
resourceType: v,
40+
}
41+
})
42+
}
43+
44+
return resources
45+
}
46+
47+
// customRoleDataSource is the datasource implementation.
48+
type customRoleDataSource struct {
49+
resourceType string
50+
client *authorization.APIClient
51+
}
52+
53+
// Configure sets up the API client for the authorization customrole resource.
54+
func (d *customRoleDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
55+
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
56+
if !ok {
57+
return
58+
}
59+
60+
if resp.Diagnostics.HasError() {
61+
return
62+
}
63+
64+
apiClient := authorizationUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics)
65+
66+
if resp.Diagnostics.HasError() {
67+
return
68+
}
69+
70+
d.client = apiClient
71+
72+
tflog.Info(ctx, "authorization client configured")
73+
}
74+
75+
// Metadata provides metadata for the custom role datasource.
76+
func (d *customRoleDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
77+
resp.TypeName = fmt.Sprintf("%s_authorization_%s_custom_role", req.ProviderTypeName, d.resourceType)
78+
}
79+
80+
// Schema defines the schema for the custom role data source.
81+
func (d *customRoleDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
82+
resp.Schema = schema.Schema{
83+
Description: descriptions["main"],
84+
Attributes: map[string]schema.Attribute{
85+
"id": schema.StringAttribute{
86+
Description: descriptions["id"],
87+
Computed: true,
88+
},
89+
"role_id": schema.StringAttribute{
90+
Description: descriptions["role_id"],
91+
Required: true,
92+
Validators: []validator.String{
93+
validate.UUID(),
94+
validate.NoSeparator(),
95+
},
96+
},
97+
"resource_id": schema.StringAttribute{
98+
Description: descriptions["resource_id"],
99+
Required: true,
100+
Validators: []validator.String{
101+
validate.UUID(),
102+
validate.NoSeparator(),
103+
},
104+
},
105+
"name": schema.StringAttribute{
106+
Description: descriptions["name"],
107+
Computed: true,
108+
},
109+
"description": schema.StringAttribute{
110+
Description: descriptions["subject"],
111+
Computed: true,
112+
},
113+
"permissions": schema.ListAttribute{
114+
ElementType: types.StringType,
115+
Description: descriptions["permissions"],
116+
Computed: true,
117+
},
118+
},
119+
}
120+
}
121+
122+
func (d *customRoleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { //nolint:gocritic // function signature required by Terraform
123+
var model Model
124+
diags := req.Config.Get(ctx, &model)
125+
resp.Diagnostics.Append(diags...)
126+
127+
if resp.Diagnostics.HasError() {
128+
return
129+
}
130+
131+
resourceId := model.ResourceId.ValueString()
132+
roleId := model.RoleId.ValueString()
133+
134+
roleResp, err := d.client.GetRole(ctx, d.resourceType, resourceId, roleId).Execute()
135+
if err != nil {
136+
var oapiErr *oapierror.GenericOpenAPIError
137+
138+
ok := errors.As(err, &oapiErr)
139+
if ok && oapiErr.StatusCode == http.StatusNotFound {
140+
resp.State.RemoveResource(ctx)
141+
return
142+
}
143+
144+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading custom role", fmt.Sprintf("Calling API: %v", err))
145+
146+
return
147+
}
148+
149+
if err = mapGetCustomRoleResponse(ctx, roleResp, &model); err != nil {
150+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading custom role", fmt.Sprintf("Processing API response: %v", err))
151+
return
152+
}
153+
154+
// Set the updated state.
155+
diags = resp.State.Set(ctx, &model)
156+
resp.Diagnostics.Append(diags...)
157+
tflog.Info(ctx, fmt.Sprintf("read custom role %s", roleId))
158+
}

0 commit comments

Comments
 (0)