Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/resources/authorization_folder_role_assignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {

- `resource_id` (String) folder Resource to assign the role to.
- `role` (String) Role to be assigned. Available roles can be queried using stackit-cli: `stackit curl https://authorization.api.stackit.cloud/v2/permissions`
- `subject` (String) Identifier of user, service account or client. Usually email address or name in case of clients
- `subject` (String) Identifier of user, service account or client. Usually email address or name in case of clients. All letters must be lowercased.

### Read-Only

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {

- `resource_id` (String) organization Resource to assign the role to.
- `role` (String) Role to be assigned. Available roles can be queried using stackit-cli: `stackit curl https://authorization.api.stackit.cloud/v2/permissions`
- `subject` (String) Identifier of user, service account or client. Usually email address or name in case of clients
- `subject` (String) Identifier of user, service account or client. Usually email address or name in case of clients. All letters must be lowercased.

### Read-Only

Expand Down
2 changes: 1 addition & 1 deletion docs/resources/authorization_project_role_assignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {

- `resource_id` (String) project Resource to assign the role to.
- `role` (String) Role to be assigned. Available roles can be queried using stackit-cli: `stackit curl https://authorization.api.stackit.cloud/v2/permissions`
- `subject` (String) Identifier of user, service account or client. Usually email address or name in case of clients
- `subject` (String) Identifier of user, service account or client. Usually email address or name in case of clients. All letters must be lowercased.

### Read-Only

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (r *roleAssignmentResource) Schema(_ context.Context, _ resource.SchemaRequ
"id": "Terraform's internal resource identifier. It is structured as \"`resource_id`,`role`,`subject`\".",
"resource_id": fmt.Sprintf("%s Resource to assign the role to.", r.apiName),
"role": "Role to be assigned. Available roles can be queried using stackit-cli: `stackit curl https://authorization.api.stackit.cloud/v2/permissions`",
"subject": "Identifier of user, service account or client. Usually email address or name in case of clients",
"subject": "Identifier of user, service account or client. Usually email address or name in case of clients. All letters must be lowercased.",
}

resp.Schema = schema.Schema{
Expand Down Expand Up @@ -133,6 +133,9 @@ func (r *roleAssignmentResource) Schema(_ context.Context, _ resource.SchemaRequ
"subject": schema.StringAttribute{
Description: descriptions["subject"],
Required: true,
Validators: []validator.String{
validate.IsLowercased(),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Expand Down
24 changes: 24 additions & 0 deletions stackit/internal/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,27 @@ func ValidNoTrailingNewline() *Validator {
},
}
}

// IsLowercased returns a Validator that checks if all letters in the input string are lowercase.
// Examples:
// - "example": valid
// - "example123": valid, numbers are not affected by case
// - "exaMPle123": invalid
func IsLowercased() *Validator {
description := `All letters in the value must be lowercase as defined by gos unicode.IsLower(rune).`
return &Validator{
description: description,
validate: func(_ context.Context, request validator.StringRequest, response *validator.StringResponse) {
val := request.ConfigValue.ValueString()
lowercased := strings.ToLower(val)
if val != lowercased {
response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
request.Path,
description,
val,
))
return
}
},
}
}
42 changes: 42 additions & 0 deletions stackit/internal/validate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -966,3 +966,45 @@ func TestValidNoTrailingNewline(t *testing.T) {
})
}
}

func TestIsLowercased(t *testing.T) {
tests := []struct {
description string
input string
wantErr bool
}{
{
description: "empty",
input: "",
},
{
description: "simple lowercase",
input: "lowercase",
},
{
description: "lowercase with numbers and symbols",
input: "lowercase123!@#",
},
{
description: "uppercase letters",
input: "UpperCase",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
r := validator.StringResponse{}
IsLowercased().ValidateString(context.Background(), validator.StringRequest{
ConfigValue: types.StringValue(tt.input),
}, &r)

if tt.wantErr && !r.Diagnostics.HasError() {
t.Fatalf("Expected validation to fail for input: %q", tt.input)
}
if !tt.wantErr && r.Diagnostics.HasError() {
t.Fatalf("Expected validation to succeed for input: %q, but got errors: %v", tt.input, r.Diagnostics.Errors())
}
})
}
}
Loading