Skip to content

Commit c2d2f27

Browse files
author
Markus Schwer
committed
feat(authorization): add custom role resource and data source
1 parent 4d395b3 commit c2d2f27

File tree

11 files changed

+1000
-30
lines changed

11 files changed

+1000
-30
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
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
@@ -11,7 +12,7 @@ require (
1112
github.com/hashicorp/terraform-plugin-go v0.29.0
1213
github.com/hashicorp/terraform-plugin-log v0.9.0
1314
github.com/hashicorp/terraform-plugin-testing v1.13.3
14-
github.com/stackitcloud/stackit-sdk-go/core v0.17.3
15+
github.com/stackitcloud/stackit-sdk-go/core v0.20.0
1516
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.6.0
1617
github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.1
1718
github.com/stackitcloud/stackit-sdk-go/services/git v0.8.0

go.sum

Lines changed: 4 additions & 2 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=
@@ -152,8 +154,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
152154
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
153155
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
154156
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
155-
github.com/stackitcloud/stackit-sdk-go/core v0.17.3 h1:GsZGmRRc/3GJLmCUnsZswirr5wfLRrwavbnL/renOqg=
156-
github.com/stackitcloud/stackit-sdk-go/core v0.17.3/go.mod h1:HBCXJGPgdRulplDzhrmwC+Dak9B/x0nzNtmOpu+1Ahg=
157+
github.com/stackitcloud/stackit-sdk-go/core v0.20.0 h1:4rrUk6uT1g4nOn5/g1uXukP07Tux/o5xbMz/f/qE1rY=
158+
github.com/stackitcloud/stackit-sdk-go/core v0.20.0/go.mod h1:fqto7M82ynGhEnpZU6VkQKYWYoFG5goC076JWXTUPRQ=
157159
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.9.0 h1:7ZKd3b+E/R4TEVShLTXxx5FrsuDuJBOyuVOuKTMa4mo=
158160
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.9.0/go.mod h1:/FoXa6hF77Gv8brrvLBCKa5ie1Xy9xn39yfHwaln9Tw=
159161
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.6.0 h1:Q+qIdejeMsYMkbtVoI9BpGlKGdSVFRBhH/zj44SP8TM=

stackit/internal/services/authorization/authorization_acc_test.go

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"testing"
1111

1212
"github.com/hashicorp/terraform-plugin-testing/config"
13+
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
1314
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
1415
"github.com/hashicorp/terraform-plugin-testing/terraform"
1516
stackitSdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config"
@@ -32,12 +33,33 @@ var invalidRole string
3233
//go:embed testfiles/organization-role.tf
3334
var organizationRole string
3435

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

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

55-
members, err := client.ListMembers(context.TODO(), "project", testutil.ProjectId).Execute()
77+
members, err := client.ListMembers(context.Background(), "project", testutil.ProjectId).Execute()
5678
if err != nil {
5779
return err
5880
}
@@ -92,19 +114,106 @@ func TestAccProjectRoleAssignmentResource(t *testing.T) {
92114
},
93115
},
94116
})
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+
})
95205
}
96206

97207
func authApiClient() (*authorization.APIClient, error) {
98208
var client *authorization.APIClient
99209

100210
var err error
101-
if testutil.AuthorizationCustomEndpoint == "" {
102-
client, err = authorization.NewAPIClient(
103-
stackitSdkConfig.WithRegion("eu01"),
104-
)
211+
if testutil.AuthorizationCustomEndpoint == "" || testutil.TokenCustomEndpoint == "" {
212+
client, err = authorization.NewAPIClient()
105213
} else {
106214
client, err = authorization.NewAPIClient(
107215
stackitSdkConfig.WithEndpoint(testutil.AuthorizationCustomEndpoint),
216+
stackitSdkConfig.WithTokenEndpoint(testutil.TokenCustomEndpoint),
108217
)
109218
}
110219

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)