diff --git a/docs/data-sources/resourcemanager_project.md b/docs/data-sources/resourcemanager_project.md index e02fa572..b2cd5e88 100644 --- a/docs/data-sources/resourcemanager_project.md +++ b/docs/data-sources/resourcemanager_project.md @@ -20,28 +20,18 @@ data "stackit_resourcemanager_project" "example" { ``` + ## Schema ### Optional - `container_id` (String) Project container ID. Globally unique, user-friendly identifier. -- `owner_email` (String, Deprecated) Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect. - -!> The "owner_email" field has been deprecated in favor of the "members" field. Please use the "members" field to assign the owner role to a user, by setting the "role" field to `owner`. +- `owner_email` (String) Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect. - `project_id` (String) Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project. ### Read-Only - `id` (String) Terraform's internal data source. ID. It is structured as "`container_id`". - `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64} -- `members` (Attributes List) The members assigned to the project. At least one subject needs to be a user, and not a client or service account. (see [below for nested schema](#nestedatt--members)) - `name` (String) Project name. - `parent_container_id` (String) Parent resource identifier. Both container ID (user-friendly) and UUID are supported - - -### Nested Schema for `members` - -Read-Only: - -- `role` (String) The role of the member in the project. Legacy roles (`project.admin`, `project.auditor`, `project.member`, `project.owner`) are not supported. -- `subject` (String) Unique identifier of the user, service account or client. This is usually the email address for users or service accounts, and the name in case of clients. diff --git a/docs/resources/resourcemanager_project.md b/docs/resources/resourcemanager_project.md index d240e864..dd17c357 100644 --- a/docs/resources/resourcemanager_project.md +++ b/docs/resources/resourcemanager_project.md @@ -19,20 +19,12 @@ resource "stackit_resourcemanager_project" "example" { labels = { "Label 1" = "foo" } - members = [ - { - "subject" : "john.doe@stackit.cloud" - "role" : "owner", - }, - { - "subject" : "some.engineer@stackit.cloud" - "role" : "reader", - }, - ] + owner_email = "john.doe@stackit.cloud" } ``` + ## Schema ### Required @@ -43,21 +35,10 @@ resource "stackit_resourcemanager_project" "example" { ### Optional - `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64} -- `members` (Attributes List) The members assigned to the project. At least one subject needs to be a user, and not a client or service account. (see [below for nested schema](#nestedatt--members)) -- `owner_email` (String, Deprecated) Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect. - -!> The "owner_email" field has been deprecated in favor of the "members" field. Please use the "members" field to assign the owner role to a user, by setting the "role" field to `owner`. +- `owner_email` (String) Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect. ### Read-Only - `container_id` (String) Project container ID. Globally unique, user-friendly identifier. - `id` (String) Terraform's internal resource ID. It is structured as "`container_id`". - `project_id` (String) Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project. - - -### Nested Schema for `members` - -Required: - -- `role` (String) The role of the member in the project. Possible values include, but are not limited to: `owner`, `editor`, `reader`. Legacy roles (`project.admin`, `project.auditor`, `project.member`, `project.owner`) are not supported. -- `subject` (String) Unique identifier of the user, service account or client. This is usually the email address for users or service accounts, and the name in case of clients. diff --git a/examples/resources/stackit_resourcemanager_project/resource.tf b/examples/resources/stackit_resourcemanager_project/resource.tf index dd9a4717..518d4eaa 100644 --- a/examples/resources/stackit_resourcemanager_project/resource.tf +++ b/examples/resources/stackit_resourcemanager_project/resource.tf @@ -4,14 +4,5 @@ resource "stackit_resourcemanager_project" "example" { labels = { "Label 1" = "foo" } - members = [ - { - "subject" : "john.doe@stackit.cloud" - "role" : "owner", - }, - { - "subject" : "some.engineer@stackit.cloud" - "role" : "reader", - }, - ] + owner_email = "john.doe@stackit.cloud" } diff --git a/stackit/internal/services/resourcemanager/project/datasource.go b/stackit/internal/services/resourcemanager/project/datasource.go index 5fec6612..665ae8b5 100644 --- a/stackit/internal/services/resourcemanager/project/datasource.go +++ b/stackit/internal/services/resourcemanager/project/datasource.go @@ -102,18 +102,17 @@ func (d *projectDataSource) Configure(ctx context.Context, req datasource.Config // Schema defines the schema for the data source. func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { descriptions := map[string]string{ - "main": "Resource Manager project data source schema. To identify the project, you need to provider either project_id or container_id. If you provide both, project_id will be used.", - "id": "Terraform's internal data source. ID. It is structured as \"`container_id`\".", - "project_id": "Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project.", - "container_id": "Project container ID. Globally unique, user-friendly identifier.", - "parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported", - "name": "Project name.", - "labels": `Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`, - "owner_email": "Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect.", - "owner_email_deprecation_message": "The \"owner_email\" field has been deprecated in favor of the \"members\" field. Please use the \"members\" field to assign the owner role to a user, by setting the \"role\" field to `owner`.", - "members": "The members assigned to the project. At least one subject needs to be a user, and not a client or service account.", - "members.role": fmt.Sprintf("The role of the member in the project. Legacy roles (%s) are not supported.", strings.Join(utils.QuoteValues(utils.LegacyProjectRoles), ", ")), - "members.subject": "Unique identifier of the user, service account or client. This is usually the email address for users or service accounts, and the name in case of clients.", + "main": "Resource Manager project data source schema. To identify the project, you need to provider either project_id or container_id. If you provide both, project_id will be used.", + "id": "Terraform's internal data source. ID. It is structured as \"`container_id`\".", + "project_id": "Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project.", + "container_id": "Project container ID. Globally unique, user-friendly identifier.", + "parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported", + "name": "Project name.", + "labels": `Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`, + "owner_email": "Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect.", + "members": "The members assigned to the project. At least one subject needs to be a user, and not a client or service account.", + "members.role": fmt.Sprintf("The role of the member in the project. Legacy roles (%s) are not supported.", strings.Join(utils.QuoteValues(utils.LegacyProjectRoles), ", ")), + "members.subject": "Unique identifier of the user, service account or client. This is usually the email address for users or service accounts, and the name in case of clients.", } resp.Schema = schema.Schema{ @@ -170,10 +169,8 @@ func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest }, }, "owner_email": schema.StringAttribute{ - Description: descriptions["owner_email"], - DeprecationMessage: descriptions["owner_email_deprecation_message"], - MarkdownDescription: fmt.Sprintf("%s\n\n!> %s", descriptions["owner_email"], descriptions["owner_email_deprecation_message"]), - Optional: true, + Description: descriptions["owner_email"], + Optional: true, }, "members": schema.ListNestedAttribute{ Description: descriptions["members"], diff --git a/stackit/internal/services/resourcemanager/project/resource.go b/stackit/internal/services/resourcemanager/project/resource.go index c7507775..dd5754f1 100644 --- a/stackit/internal/services/resourcemanager/project/resource.go +++ b/stackit/internal/services/resourcemanager/project/resource.go @@ -147,18 +147,17 @@ func (r *projectResource) Configure(ctx context.Context, req resource.ConfigureR // Schema defines the schema for the resource. func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { descriptions := map[string]string{ - "main": "Resource Manager project resource schema. To use this resource, it is required that you set the service account email in the provider configuration.", - "id": "Terraform's internal resource ID. It is structured as \"`container_id`\".", - "project_id": "Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project.", - "container_id": "Project container ID. Globally unique, user-friendly identifier.", - "parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported", - "name": "Project name.", - "labels": "Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}", - "owner_email": "Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect.", - "owner_email_deprecation_message": "The \"owner_email\" field has been deprecated in favor of the \"members\" field. Please use the \"members\" field to assign the owner role to a user, by setting the \"role\" field to `owner`.", - "members": "The members assigned to the project. At least one subject needs to be a user, and not a client or service account.", - "members.role": fmt.Sprintf("The role of the member in the project. Possible values include, but are not limited to: `owner`, `editor`, `reader`. Legacy roles (%s) are not supported.", strings.Join(utils.QuoteValues(utils.LegacyProjectRoles), ", ")), - "members.subject": "Unique identifier of the user, service account or client. This is usually the email address for users or service accounts, and the name in case of clients.", + "main": "Resource Manager project resource schema. To use this resource, it is required that you set the service account email in the provider configuration.", + "id": "Terraform's internal resource ID. It is structured as \"`container_id`\".", + "project_id": "Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project.", + "container_id": "Project container ID. Globally unique, user-friendly identifier.", + "parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported", + "name": "Project name.", + "labels": "Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}", + "owner_email": "Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect.", + "members": "The members assigned to the project. At least one subject needs to be a user, and not a client or service account.", + "members.role": fmt.Sprintf("The role of the member in the project. Possible values include, but are not limited to: `owner`, `editor`, `reader`. Legacy roles (%s) are not supported.", strings.Join(utils.QuoteValues(utils.LegacyProjectRoles), ", ")), + "members.subject": "Unique identifier of the user, service account or client. This is usually the email address for users or service accounts, and the name in case of clients.", } resp.Schema = schema.Schema{ @@ -224,16 +223,13 @@ func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, }, "owner_email": schema.StringAttribute{ - Description: descriptions["owner_email"], - DeprecationMessage: descriptions["owner_email_deprecation_message"], - MarkdownDescription: fmt.Sprintf("%s\n\n!> %s", descriptions["owner_email"], descriptions["owner_email_deprecation_message"]), + Description: descriptions["owner_email"], // When removing the owner_email field, we should mark the members field as required and add a listvalidator.SizeAtLeast(1) validator to it Optional: true, }, "members": schema.ListNestedAttribute{ Description: descriptions["members"], Optional: true, - // Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "role": schema.StringAttribute{ @@ -254,6 +250,39 @@ func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, re } } +// ModifyPlan will be called in the Plan phase and will check if the members field is set +func (r *projectResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.Plan.Get(ctx, &model) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + membersResp, err := r.authorizationClient.ListMembersExecute(ctx, projectResourceType, model.ProjectId.ValueString()) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating project", fmt.Sprintf("Reading members: %v", err)) + return + } + + members := []string{} + for _, m := range *membersResp.Members { + if utils.IsLegacyProjectRole(*m.Role) { + continue + } + members = append(members, fmt.Sprintf(" - %s (%s)", *m.Subject, *m.Role)) + } + + if !(model.Members.IsNull() || model.Members.IsUnknown()) { + core.LogAndAddWarning(ctx, &resp.Diagnostics, "The members set in the \"members\" field will override the current members in your project", + fmt.Sprintf("%s\n%s\n%s\n\n%s", + "The current members in your project will be removed and replaced with the members set in the \"members\" field.", + "This might not be represented in the Terraform plan if you are migrating from the \"owner_email\" field, since the current members are not yet set in the state.", + "Please make sure that the members in the \"members\" field are correct and complete.", + fmt.Sprintf("Current members in your project:\n%v", strings.Join(members, "\n")))) + } +} + // ConfigValidators validates the resource configuration func (r *projectResource) ConfigValidators(_ context.Context) []resource.ConfigValidator { return []resource.ConfigValidator{