Skip to content

Commit dadea7a

Browse files
l0wl3velBenjamin Ritter
andauthored
IAM Role Assignment (#665)
* Initial PoC for a Project Role Assignment resource Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> * fix: move project_role_assignment into new "authorization" resource group Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> * feat: add authorization_project_role_assignment acceptance test Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> * docs: add authorization_project_role_assignment docs and examples Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> * fix: linting Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> * feat: add generic role_assignment resources Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> * feat: add infrastructure for experimental features Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> * feat: Make IAM resources part of the iam experiment Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> * fix: Log an error if an experiment does not exist Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> * fix: Do not cache the experiment check Caching the experiment check causes problems when running the provider in debug mode, since configure in the provider can be called multiple times there with different configurations, with different experiments enabled. Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> --------- Signed-off-by: Benjamin Ritter <benjamin.ritter@stackit.cloud> Co-authored-by: Benjamin Ritter <benjamin.ritter@stackit.cloud>
1 parent 69b117f commit dadea7a

File tree

18 files changed

+853
-1
lines changed

18 files changed

+853
-1
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,25 @@ To use beta resources in the STACKIT Terraform provider, follow these steps:
172172
173173
For more details, please refer to the [beta resources configuration guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources).
174174

175+
## Opting into Experiments
176+
177+
Experiments are features that are even less mature and stable than Beta Resources. While there is some assumed stability in beta resources, will have to expect breaking changes while using experimental resources. Experimental Resources do not come with any support or warranty.
178+
179+
To enable experiments set the experiments field in the provider definition:
180+
181+
```hcl
182+
provider "stackit" {
183+
region = "eu01"
184+
experiments = ["iam"]
185+
}
186+
```
187+
188+
### Available Experiments
189+
190+
#### `iam`
191+
192+
Enables IAM management features in the Terraform provider. The underlying IAM API is expected to undergo a redesign in the future, which leads to it being considered experimental.
193+
175194
## Acceptance Tests
176195

177196
Terraform acceptance tests are run using the command `make test-acceptance-tf`. For all services,

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
157157
- `default_region` (String) Region will be used as the default location for regional services. Not all services require a region, some are global
158158
- `dns_custom_endpoint` (String) Custom endpoint for the DNS service
159159
- `enable_beta_resources` (Boolean) Enable beta resources. Default is false.
160+
- `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: [iam]
160161
- `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service
161162
- `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service
162163
- `logme_custom_endpoint` (String) Custom endpoint for the LogMe service
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "stackit_authorization_organization_role_assignment Resource - stackit"
4+
subcategory: ""
5+
description: |-
6+
organization Role Assignment resource schema.
7+
~> This resource is part of the iam experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
8+
---
9+
10+
# stackit_authorization_organization_role_assignment (Resource)
11+
12+
organization Role Assignment resource schema.
13+
14+
~> This resource is part of the iam experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
15+
16+
## Example Usage
17+
18+
```terraform
19+
resource "stackit_authorization_organization_role_assignment" "example" {
20+
resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
21+
role = "owner"
22+
subject = "john.doe@stackit.cloud"
23+
}
24+
```
25+
26+
<!-- schema generated by tfplugindocs -->
27+
## Schema
28+
29+
### Required
30+
31+
- `resource_id` (String) organization Resource to assign the role to.
32+
- `role` (String) Role to be assigned
33+
- `subject` (String) Identifier of user, service account or client. Usually email address or name in case of clients
34+
35+
### Read-Only
36+
37+
- `id` (String) Terraform's internal resource identifier. It is structured as "[resource_id],[role],[subject]".
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "stackit_authorization_project_role_assignment Resource - stackit"
4+
subcategory: ""
5+
description: |-
6+
project Role Assignment resource schema.
7+
~> This resource is part of the iam experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
8+
---
9+
10+
# stackit_authorization_project_role_assignment (Resource)
11+
12+
project Role Assignment resource schema.
13+
14+
~> This resource is part of the iam experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
15+
16+
## Example Usage
17+
18+
```terraform
19+
resource "stackit_authorization_project_role_assignment" "example" {
20+
resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
21+
role = "owner"
22+
subject = "john.doe@stackit.cloud"
23+
}
24+
```
25+
26+
<!-- schema generated by tfplugindocs -->
27+
## Schema
28+
29+
### Required
30+
31+
- `resource_id` (String) project Resource to assign the role to.
32+
- `role` (String) Role to be assigned
33+
- `subject` (String) Identifier of user, service account or client. Usually email address or name in case of clients
34+
35+
### Read-Only
36+
37+
- `id` (String) Terraform's internal resource identifier. It is structured as "[resource_id],[role],[subject]".
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
resource "stackit_authorization_organization_role_assignment" "example" {
2+
resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
3+
role = "owner"
4+
subject = "john.doe@stackit.cloud"
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
resource "stackit_authorization_project_role_assignment" "example" {
2+
resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
3+
role = "owner"
4+
subject = "john.doe@stackit.cloud"
5+
}

stackit/internal/core/core.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type ProviderData struct {
4141
SKECustomEndpoint string
4242
ServiceEnablementCustomEndpoint string
4343
EnableBetaResources bool
44+
Experiments []string
4445
}
4546

4647
// GetRegion returns the effective region for the provider, falling back to the deprecated _region_ attribute
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package features
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"slices"
7+
"strings"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
10+
"github.com/hashicorp/terraform-plugin-log/tflog"
11+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
12+
)
13+
14+
var AvailableExperiments []string = []string{"iam"}
15+
16+
// Check if an experiment is valid.
17+
func ValidExperiment(experiment string, diags *diag.Diagnostics) bool {
18+
validExperiment := slices.ContainsFunc(AvailableExperiments, func(e string) bool {
19+
return strings.EqualFold(e, experiment)
20+
})
21+
if !validExperiment {
22+
diags.AddError("Invalid Experiment", fmt.Sprintf("The Experiment %s is invalid. This is most likely a bug in the STACKIT Provider. Please open an issue. Available Experiments: %v", experiment, AvailableExperiments))
23+
}
24+
25+
return validExperiment
26+
}
27+
28+
// Check if an experiment is enabled.
29+
func CheckExperimentEnabled(ctx context.Context, data *core.ProviderData, experiment, resourceType string, diags *diag.Diagnostics) {
30+
if !ValidExperiment(experiment, diags) {
31+
errTitle := fmt.Sprintf("The experiment %s does not exist.", experiment)
32+
errContent := "This is a bug in the STACKIT Terraform Provider. Please open an issue here: https://github.com/stackitcloud/terraform-provider-stackit/issues"
33+
diags.AddError(errTitle, errContent)
34+
return
35+
}
36+
experimentActive := slices.ContainsFunc(data.Experiments, func(e string) bool {
37+
return strings.EqualFold(e, experiment)
38+
})
39+
40+
if experimentActive {
41+
warnTitle := fmt.Sprintf("%s is part of the %s experiment.", resourceType, experiment)
42+
warnContent := fmt.Sprintf("This resource is part of the %s experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.", experiment)
43+
tflog.Warn(ctx, fmt.Sprintf("%s | %s", warnTitle, warnContent))
44+
diags.AddWarning(warnTitle, warnContent)
45+
return
46+
}
47+
errTitle := fmt.Sprintf("%s is part of the %s experiment, which is currently disabled by default", resourceType, experiment)
48+
errContent := fmt.Sprintf(`Enable the %s experiment by adding it into your provider block.`, experiment)
49+
tflog.Error(ctx, fmt.Sprintf("%s | %s", errTitle, errContent))
50+
diags.AddError(errTitle, errContent)
51+
}
52+
53+
func AddExperimentDescription(description, experiment string) string {
54+
// Callout block: https://developer.hashicorp.com/terraform/registry/providers/docs#callouts
55+
return fmt.Sprintf("%s\n\n~> %s%s%s",
56+
description,
57+
"This resource is part of the ",
58+
experiment,
59+
" experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.",
60+
)
61+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package features
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/diag"
8+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
9+
)
10+
11+
func TestValidExperiment(t *testing.T) {
12+
type args struct {
13+
experiment string
14+
diags *diag.Diagnostics
15+
}
16+
tests := []struct {
17+
name string
18+
args args
19+
want bool
20+
}{
21+
{
22+
name: "valid",
23+
args: args{
24+
experiment: "iam",
25+
diags: &diag.Diagnostics{},
26+
},
27+
want: true,
28+
},
29+
{
30+
name: "invalid",
31+
args: args{
32+
experiment: "foo",
33+
diags: &diag.Diagnostics{},
34+
},
35+
want: false,
36+
},
37+
}
38+
for _, tt := range tests {
39+
t.Run(tt.name, func(t *testing.T) {
40+
if got := ValidExperiment(tt.args.experiment, tt.args.diags); got != tt.want {
41+
t.Errorf("ValidExperiment() = %v, want %v", got, tt.want)
42+
}
43+
})
44+
}
45+
}
46+
47+
func TestCheckExperimentEnabled(t *testing.T) {
48+
type args struct {
49+
ctx context.Context
50+
data *core.ProviderData
51+
experiment string
52+
resourceType string
53+
diags *diag.Diagnostics
54+
}
55+
tests := []struct {
56+
name string
57+
args args
58+
wantDiagsErr bool
59+
wantDiagsWarning bool
60+
}{
61+
{
62+
name: "enabled",
63+
args: args{
64+
ctx: context.Background(),
65+
data: &core.ProviderData{
66+
Experiments: []string{"iam"},
67+
},
68+
experiment: "iam",
69+
diags: &diag.Diagnostics{},
70+
},
71+
wantDiagsErr: false,
72+
wantDiagsWarning: true,
73+
},
74+
{
75+
name: "disabled",
76+
args: args{
77+
ctx: context.Background(),
78+
data: &core.ProviderData{
79+
Experiments: []string{},
80+
},
81+
experiment: "iam",
82+
diags: &diag.Diagnostics{},
83+
},
84+
wantDiagsErr: true,
85+
wantDiagsWarning: false,
86+
},
87+
{
88+
name: "invalid experiment",
89+
args: args{
90+
ctx: context.Background(),
91+
data: &core.ProviderData{
92+
Experiments: []string{"iam"},
93+
},
94+
experiment: "foobar",
95+
resourceType: "provider",
96+
diags: &diag.Diagnostics{},
97+
},
98+
wantDiagsErr: true,
99+
wantDiagsWarning: false,
100+
},
101+
}
102+
for _, tt := range tests {
103+
t.Run(tt.name, func(t *testing.T) {
104+
CheckExperimentEnabled(tt.args.ctx, tt.args.data, tt.args.experiment, tt.args.resourceType, tt.args.diags)
105+
if got := tt.args.diags.HasError(); got != tt.wantDiagsErr {
106+
t.Errorf("CheckExperimentEnabled() diags.HasError() = %v, want %v", got, tt.wantDiagsErr)
107+
}
108+
if got := tt.args.diags.WarningsCount() > 0; got != tt.wantDiagsWarning {
109+
t.Errorf("CheckExperimentEnabled() diags.WarningsCount() > 0 = %v, want %v", got, tt.wantDiagsErr)
110+
}
111+
})
112+
}
113+
}

0 commit comments

Comments
 (0)