diff --git a/docs/resources/api_token.md b/docs/resources/api_token.md index 2fa374c6..949316aa 100644 --- a/docs/resources/api_token.md +++ b/docs/resources/api_token.md @@ -13,7 +13,7 @@ API Token resource ## Example Usage ```terraform -resource "astro_api_token" "example_organization_token" { +resource "astro_api_token" "organization_token" { name = "organization api token" description = "organization api token description" type = "ORGANIZATION" @@ -25,7 +25,7 @@ resource "astro_api_token" "example_organization_token" { expiry_period_in_days = 30 } -resource "astro_api_token" "example_organization_token_with_multiple_roles" { +resource "astro_api_token" "organization_token_with_multiple_roles" { name = "organization api token with multiple roles" description = "organization api token description" type = "ORGANIZATION" @@ -46,7 +46,7 @@ resource "astro_api_token" "example_organization_token_with_multiple_roles" { }] } -resource "astro_api_token" "example_workspace_token" { +resource "astro_api_token" "workspace_token" { name = "workspace api token" description = "workspace api token description" type = "WORKSPACE" @@ -57,7 +57,7 @@ resource "astro_api_token" "example_workspace_token" { }] } -resource "astro_api_token" "example_workspace_token_with_deployment_role" { +resource "astro_api_token" "workspace_token_with_deployment_role" { name = "workspace api token" description = "workspace api token description" type = "WORKSPACE" @@ -73,7 +73,7 @@ resource "astro_api_token" "example_workspace_token_with_deployment_role" { }] } -resource "astro_api_token" "example_deployment_token" { +resource "astro_api_token" "deployment_token" { name = "deployment api token" description = "deployment api token description" type = "DEPLOYMENT" @@ -84,7 +84,7 @@ resource "astro_api_token" "example_deployment_token" { }] } -resource "astro_api_token" "example_deployment_token_with_custom_role" { +resource "astro_api_token" "deployment_token_with_custom_role" { name = "deployment api token with custom role" description = "deployment api token description" type = "DEPLOYMENT" @@ -94,6 +94,22 @@ resource "astro_api_token" "example_deployment_token_with_custom_role" { "entity_type" : "DEPLOYMENT" }] } + +# Import an existing api token +import { + id = "clxm46ged05b301neuucdqwox" // ID of the existing api token + to = astro_api_token.imported_api_token +} +resource "astro_api_token" "imported_api_token" { + name = "imported api token" + description = "imported api token description" + type = "ORGANIZATION" + roles = [{ + "role" : "ORGANIZATION_OWNER", + "entity_id" : "clx42kkcm01fo01o06agtmshg", + "entity_type" : "ORGANIZATION" + }] +} ``` diff --git a/docs/resources/team.md b/docs/resources/team.md index 63156cdf..97770a2e 100644 --- a/docs/resources/team.md +++ b/docs/resources/team.md @@ -13,9 +13,9 @@ Team resource ## Example Usage ```terraform -resource "astro_team" "example" { +resource "astro_team" "team" { name = "team" - description = "team-description" + description = "team description" member_ids = ["clhpichn8002m01mqa4ocs7g6"] organization_role = "ORGANIZATION_OWNER" workspace_roles = [{ @@ -28,10 +28,30 @@ resource "astro_team" "example" { }] } -resource "astro_team" "example_with_no_optional_fields" { +resource "astro_team" "team_with_no_optional_fields" { name = "team" organization_role = "ORGANIZATION_OWNER" } + +# Import an existing team +import { + id = "clx486hno068301il306nuhsm" # ID of the existing team + to = astro_team.imported_team +} +resource "astro_team" "imported_team" { + name = "imported team" + description = "imported team description" + member_ids = ["clhpichn8002m01mqa4ocs7g6"] + organization_role = "ORGANIZATION_OWNER" + workspace_roles = [{ + workspace_id = "clx42sxw501gl01o0gjenthnh" + role = "WORKSPACE_OWNER" + }] + deployment_roles = [{ + deployment_id = "clyn6kxud003x01mtxmccegnh" + role = "DEPLOYMENT_ADMIN" + }] +} ``` diff --git a/docs/resources/user_invite.md b/docs/resources/user_invite.md index b491a2e4..1f74cc0b 100644 --- a/docs/resources/user_invite.md +++ b/docs/resources/user_invite.md @@ -13,7 +13,7 @@ User Invite resource ## Example Usage ```terraform -resource "astro_user_invite" "example" { +resource "astro_user_invite" "user_invite" { email = "email@organization.com" role = "ORGANIZATION_MEMBER" } diff --git a/docs/resources/user_roles.md b/docs/resources/user_roles.md index 76c59865..543cfd38 100644 --- a/docs/resources/user_roles.md +++ b/docs/resources/user_roles.md @@ -60,6 +60,32 @@ resource "astro_user_roles" "all_roles" { } ] } + +# Import an existing user roles +import { + id = "clzaftcaz006001lhkey6qzzg" # ID of the existing user + to = astro_user_roles.imported_user_roles +} +resource "astro_user_roles" "imported_user_roles" { + user_id = "clzaftcaz006001lhkey6qzzg" + organization_role = "ORGANIZATION_MEMBER" + workspace_roles = [ + { + workspace_id = "clx42sxw501gl01o0gjenthnh" + role = "WORKSPACE_OWNER" + }, + { + workspace_id = "clzafte7z006001lhkey6qzzb" + role = "WORKSPACE_MEMBER" + } + ] + deployment_roles = [ + { + deployment_id = "clyn6kxud003x01mtxmccegnh" + role = "my custom role" + } + ] +} ``` diff --git a/examples/resources/astro_api_token/resource.tf b/examples/resources/astro_api_token/resource.tf index c50f091d..71bef066 100644 --- a/examples/resources/astro_api_token/resource.tf +++ b/examples/resources/astro_api_token/resource.tf @@ -1,4 +1,4 @@ -resource "astro_api_token" "example_organization_token" { +resource "astro_api_token" "organization_token" { name = "organization api token" description = "organization api token description" type = "ORGANIZATION" @@ -10,7 +10,7 @@ resource "astro_api_token" "example_organization_token" { expiry_period_in_days = 30 } -resource "astro_api_token" "example_organization_token_with_multiple_roles" { +resource "astro_api_token" "organization_token_with_multiple_roles" { name = "organization api token with multiple roles" description = "organization api token description" type = "ORGANIZATION" @@ -31,7 +31,7 @@ resource "astro_api_token" "example_organization_token_with_multiple_roles" { }] } -resource "astro_api_token" "example_workspace_token" { +resource "astro_api_token" "workspace_token" { name = "workspace api token" description = "workspace api token description" type = "WORKSPACE" @@ -42,7 +42,7 @@ resource "astro_api_token" "example_workspace_token" { }] } -resource "astro_api_token" "example_workspace_token_with_deployment_role" { +resource "astro_api_token" "workspace_token_with_deployment_role" { name = "workspace api token" description = "workspace api token description" type = "WORKSPACE" @@ -58,7 +58,7 @@ resource "astro_api_token" "example_workspace_token_with_deployment_role" { }] } -resource "astro_api_token" "example_deployment_token" { +resource "astro_api_token" "deployment_token" { name = "deployment api token" description = "deployment api token description" type = "DEPLOYMENT" @@ -69,7 +69,7 @@ resource "astro_api_token" "example_deployment_token" { }] } -resource "astro_api_token" "example_deployment_token_with_custom_role" { +resource "astro_api_token" "deployment_token_with_custom_role" { name = "deployment api token with custom role" description = "deployment api token description" type = "DEPLOYMENT" @@ -79,3 +79,19 @@ resource "astro_api_token" "example_deployment_token_with_custom_role" { "entity_type" : "DEPLOYMENT" }] } + +# Import an existing api token +import { + id = "clxm46ged05b301neuucdqwox" // ID of the existing api token + to = astro_api_token.imported_api_token +} +resource "astro_api_token" "imported_api_token" { + name = "imported api token" + description = "imported api token description" + type = "ORGANIZATION" + roles = [{ + "role" : "ORGANIZATION_OWNER", + "entity_id" : "clx42kkcm01fo01o06agtmshg", + "entity_type" : "ORGANIZATION" + }] +} diff --git a/examples/resources/astro_team/resource.tf b/examples/resources/astro_team/resource.tf index db34dfd3..14dbf861 100644 --- a/examples/resources/astro_team/resource.tf +++ b/examples/resources/astro_team/resource.tf @@ -1,6 +1,6 @@ -resource "astro_team" "example" { +resource "astro_team" "team" { name = "team" - description = "team-description" + description = "team description" member_ids = ["clhpichn8002m01mqa4ocs7g6"] organization_role = "ORGANIZATION_OWNER" workspace_roles = [{ @@ -13,8 +13,27 @@ resource "astro_team" "example" { }] } -resource "astro_team" "example_with_no_optional_fields" { +resource "astro_team" "team_with_no_optional_fields" { name = "team" organization_role = "ORGANIZATION_OWNER" } +# Import an existing team +import { + id = "clx486hno068301il306nuhsm" # ID of the existing team + to = astro_team.imported_team +} +resource "astro_team" "imported_team" { + name = "imported team" + description = "imported team description" + member_ids = ["clhpichn8002m01mqa4ocs7g6"] + organization_role = "ORGANIZATION_OWNER" + workspace_roles = [{ + workspace_id = "clx42sxw501gl01o0gjenthnh" + role = "WORKSPACE_OWNER" + }] + deployment_roles = [{ + deployment_id = "clyn6kxud003x01mtxmccegnh" + role = "DEPLOYMENT_ADMIN" + }] +} \ No newline at end of file diff --git a/examples/resources/astro_user_invite/resource.tf b/examples/resources/astro_user_invite/resource.tf index 9301f85d..c49f22b5 100644 --- a/examples/resources/astro_user_invite/resource.tf +++ b/examples/resources/astro_user_invite/resource.tf @@ -1,4 +1,4 @@ -resource "astro_user_invite" "example" { +resource "astro_user_invite" "user_invite" { email = "email@organization.com" role = "ORGANIZATION_MEMBER" } \ No newline at end of file diff --git a/examples/resources/astro_user_roles/resource.tf b/examples/resources/astro_user_roles/resource.tf index 53eb471e..bb43c332 100644 --- a/examples/resources/astro_user_roles/resource.tf +++ b/examples/resources/astro_user_roles/resource.tf @@ -44,4 +44,30 @@ resource "astro_user_roles" "all_roles" { role = "my custom role" } ] +} + +# Import an existing user roles +import { + id = "clzaftcaz006001lhkey6qzzg" # ID of the existing user + to = astro_user_roles.imported_user_roles +} +resource "astro_user_roles" "imported_user_roles" { + user_id = "clzaftcaz006001lhkey6qzzg" + organization_role = "ORGANIZATION_MEMBER" + workspace_roles = [ + { + workspace_id = "clx42sxw501gl01o0gjenthnh" + role = "WORKSPACE_OWNER" + }, + { + workspace_id = "clzafte7z006001lhkey6qzzb" + role = "WORKSPACE_MEMBER" + } + ] + deployment_roles = [ + { + deployment_id = "clyn6kxud003x01mtxmccegnh" + role = "my custom role" + } + ] } \ No newline at end of file diff --git a/internal/provider/common/role.go b/internal/provider/common/role.go index 79b6cd09..29cbcc2c 100644 --- a/internal/provider/common/role.go +++ b/internal/provider/common/role.go @@ -104,10 +104,10 @@ func ValidateWorkspaceDeploymentRoles(ctx context.Context, input ValidateWorkspa DeploymentIds: &deploymentIds, }) if err != nil { - tflog.Error(ctx, "failed to mutate Team roles", map[string]interface{}{"error": err}) + tflog.Error(ctx, "failed to mutate roles", map[string]interface{}{"error": err}) return diag.Diagnostics{diag.NewErrorDiagnostic( "Client Error", - fmt.Sprintf("Unable to mutate Team roles and list deployments, got error: %s", err), + fmt.Sprintf("Unable to mutate roles and list deployments, got error: %s", err), ), } } @@ -129,9 +129,9 @@ func ValidateWorkspaceDeploymentRoles(ctx context.Context, input ValidateWorkspa // check if deploymentWorkspaceIds are in workspaceIds workspaceIds = lo.Intersect(lo.Uniq(workspaceIds), lo.Uniq(deploymentWorkspaceIds)) if len(workspaceIds) != len(deploymentWorkspaceIds) { - tflog.Error(ctx, "failed to mutate Team roles", map[string]interface{}{"error": err}) + tflog.Error(ctx, "failed to mutate roles") return diag.Diagnostics{diag.NewErrorDiagnostic( - "Unable to mutate Team roles, not every deployment role has a corresponding workspace role", + "Unable to mutate roles, not every deployment role has a corresponding workspace role", "Please ensure that every deployment role has a corresponding workspace role", ), } diff --git a/internal/provider/models/team.go b/internal/provider/models/team.go index 79e9f53a..313f80f3 100644 --- a/internal/provider/models/team.go +++ b/internal/provider/models/team.go @@ -64,7 +64,7 @@ func (data *TeamDataSource) ReadFromResponse(ctx context.Context, team *iam.Team if team.RolesCount != nil { data.RolesCount = types.Int64Value(int64(*team.RolesCount)) } else { - data.RolesCount = types.Int64Value(0) + data.RolesCount = types.Int64Value(1) } data.CreatedAt = types.StringValue(team.CreatedAt.String()) diff --git a/internal/provider/models/user_roles.go b/internal/provider/models/user_roles.go index 04d0cc16..361cecea 100644 --- a/internal/provider/models/user_roles.go +++ b/internal/provider/models/user_roles.go @@ -25,7 +25,7 @@ func (data *UserRoles) ReadFromResponse( ) diag.Diagnostics { var diags diag.Diagnostics data.UserId = types.StringValue(userId) - data.OrganizationRole = types.StringPointerValue((*string)(userRoles.OrganizationRole)) + data.OrganizationRole = types.StringValue(string(*userRoles.OrganizationRole)) data.WorkspaceRoles, diags = utils.ObjectSet(ctx, userRoles.WorkspaceRoles, schemas.WorkspaceRoleAttributeTypes(), WorkspaceRoleTypesObject) if diags.HasError() { return diags diff --git a/internal/provider/resources/resource_api_token.go b/internal/provider/resources/resource_api_token.go index 90f93585..5dfa78af 100644 --- a/internal/provider/resources/resource_api_token.go +++ b/internal/provider/resources/resource_api_token.go @@ -5,6 +5,8 @@ import ( "fmt" "net/http" + "github.com/astronomer/terraform-provider-astro/internal/clients/platform" + "github.com/astronomer/terraform-provider-astro/internal/provider/common" "github.com/astronomer/terraform-provider-astro/internal/clients" @@ -34,6 +36,7 @@ func NewApiTokenResource() resource.Resource { // ApiTokenResource defines the resource implementation. type ApiTokenResource struct { IamClient *iam.ClientWithResponses + PlatformClient *platform.ClientWithResponses OrganizationId string } @@ -56,7 +59,6 @@ func (r *ApiTokenResource) Schema( Attributes: schemas.ApiTokenResourceSchemaAttributes(), } } - func (r *ApiTokenResource) Configure( ctx context.Context, req resource.ConfigureRequest, @@ -74,6 +76,7 @@ func (r *ApiTokenResource) Configure( } r.IamClient = apiClients.IamClient + r.PlatformClient = apiClients.PlatformClient r.OrganizationId = apiClients.OrganizationId } @@ -106,6 +109,33 @@ func (r *ApiTokenResource) Create( return } + // Validate organization id + if string(role.EntityType) == string(iam.ORGANIZATION) { + if role.EntityId != r.OrganizationId { + resp.Diagnostics.AddError( + "API Token of type 'ORGANIZATION' cannot have an 'ORGANIZATION' role with a different organization id", + "Please provide a valid role for the entity type 'ORGANIZATION' with the correct organization id", + ) + return + } + } + + // Validate workspaces + workspaceRoles := FilterApiTokenRolesByType(roles, string(iam.WORKSPACE)) + diags = r.HasValidWorkspaces(ctx, workspaceRoles) + if diags != nil { + resp.Diagnostics.Append(diags...) + return + } + + // Validate deployments + deploymentRoles := FilterApiTokenRolesByType(roles, string(iam.DEPLOYMENT)) + diags = r.HasValidDeployments(ctx, deploymentRoles) + if diags != nil { + resp.Diagnostics.Append(diags...) + return + } + // Create the API token request createApiTokenRequest := iam.CreateApiTokenRequest{ Name: data.Name.ValueString(), @@ -259,10 +289,11 @@ func (r *ApiTokenResource) Update( req resource.UpdateRequest, resp *resource.UpdateResponse, ) { - var data models.ApiTokenResource + var data, currentState models.ApiTokenResource // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + req.State.Get(ctx, ¤tState) if resp.Diagnostics.HasError() { return @@ -275,6 +306,49 @@ func (r *ApiTokenResource) Update( return } + // Get the role for the entity type + role, diags := RequestApiTokenPrimaryRole(roles, data.Type.ValueString()) + if diags != nil { + resp.Diagnostics.Append(diags...) + return + } + + // Validate organization id + if string(role.EntityType) == string(iam.ORGANIZATION) { + if role.EntityId != r.OrganizationId { + resp.Diagnostics.AddError( + "API Token of type 'ORGANIZATION' cannot have an 'ORGANIZATION' role with a different organization id", + "Please provide a valid role for the entity type 'ORGANIZATION' with the correct organization id", + ) + return + } + } + + // Validate workspaces + workspaceRoles := FilterApiTokenRolesByType(roles, string(iam.WORKSPACE)) + diags = r.HasValidWorkspaces(ctx, workspaceRoles) + if diags != nil { + resp.Diagnostics.Append(diags...) + return + } + + // Validate deployments + deploymentRoles := FilterApiTokenRolesByType(roles, string(iam.DEPLOYMENT)) + diags = r.HasValidDeployments(ctx, deploymentRoles) + if diags != nil { + resp.Diagnostics.Append(diags...) + return + } + + // Validate token expiry + if data.ExpiryPeriodInDays.ValueInt64() != currentState.ExpiryPeriodInDays.ValueInt64() { + resp.Diagnostics.AddError( + "API Token expiry period cannot be updated", + "Please provide the same expiry period as the existing API token", + ) + return + } + // Update API token roles updateApiTokenRolesRequest := iam.UpdateApiTokenRolesRequest{ Roles: roles, @@ -479,7 +553,7 @@ func (r *ApiTokenResource) ValidateApiTokenRoles(entityType string, roles []iam. } } - if common.ValidateRoleMatchesEntityType(role.Role, entityType) { + if common.ValidateRoleMatchesEntityType(role.Role, entityType) && string(role.EntityType) == entityType { numRolesMatchingEntityType++ } } @@ -547,3 +621,117 @@ func RequestApiTokenPrimaryRole(roles []iam.ApiTokenRole, entityType string) (ia ), } } + +func FilterApiTokenRolesByType(roles []iam.ApiTokenRole, entityType string) []iam.ApiTokenRole { + var filteredRoles []iam.ApiTokenRole + for _, role := range roles { + if role.EntityType == iam.ApiTokenRoleEntityType(entityType) { + filteredRoles = append(filteredRoles, role) + } + } + return filteredRoles +} + +func (r *ApiTokenResource) HasValidWorkspaces(ctx context.Context, workspaceRoles []iam.ApiTokenRole) diag.Diagnostics { + if len(workspaceRoles) == 0 { + return nil + } + // Get workspace ids + var workspaceIds []string + for _, workspaceRole := range workspaceRoles { + workspaceIds = append(workspaceIds, workspaceRole.EntityId) + } + workspaceIds = lo.Uniq(workspaceIds) + + listWorkspacesRequest := platform.ListWorkspacesParams{ + WorkspaceIds: lo.ToPtr(workspaceIds), + } + + // List organization workspaces + workspaces, err := r.PlatformClient.ListWorkspacesWithResponse( + ctx, + r.OrganizationId, + &listWorkspacesRequest, + ) + if err != nil { + tflog.Error(ctx, "failed to list workspaces", map[string]interface{}{"error": err}) + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Client Error", + fmt.Sprintf("Unable to list workspaces, got error: %s", err), + ), + } + } + _, diagnostic := clients.NormalizeAPIError(ctx, workspaces.HTTPResponse, workspaces.Body) + if diagnostic != nil { + return diag.Diagnostics{diagnostic} + } + organizationWorkspaceIds := lo.Map(workspaces.JSON200.Workspaces, func(workspace platform.Workspace, _ int) string { + return workspace.Id + }) + + invalidWorkspaceIds, _ := lo.Difference(workspaceIds, organizationWorkspaceIds) + if len(invalidWorkspaceIds) > 0 { + tflog.Error(ctx, "invalid workspace ids") + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "One or more workspaces is not in the organization, cannot set roles for workspaces that do not exist", + fmt.Sprintf("The following workspace ids are invalid: %v", invalidWorkspaceIds), + ), + } + } + + return nil +} + +func (r *ApiTokenResource) HasValidDeployments(ctx context.Context, deploymentRoles []iam.ApiTokenRole) diag.Diagnostics { + if len(deploymentRoles) == 0 { + return nil + } + // Get deployment ids + var deploymentIds []string + for _, deploymentRole := range deploymentRoles { + deploymentIds = append(deploymentIds, deploymentRole.EntityId) + } + deploymentIds = lo.Uniq(deploymentIds) + + listDeploymentsRequest := platform.ListDeploymentsParams{ + DeploymentIds: lo.ToPtr(deploymentIds), + } + + // List organization deployments + deployments, err := r.PlatformClient.ListDeploymentsWithResponse( + ctx, + r.OrganizationId, + &listDeploymentsRequest, + ) + if err != nil { + tflog.Error(ctx, "failed to list deployments", map[string]interface{}{"error": err}) + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Client Error", + fmt.Sprintf("Unable to list deployments, got error: %s", err), + ), + } + } + _, diagnostic := clients.NormalizeAPIError(ctx, deployments.HTTPResponse, deployments.Body) + if diagnostic != nil { + return diag.Diagnostics{diagnostic} + } + organizationDeploymentIds := lo.Map(deployments.JSON200.Deployments, func(deployment platform.Deployment, _ int) string { + return deployment.Id + }) + + invalidDeploymentIds, _ := lo.Difference(deploymentIds, organizationDeploymentIds) + if len(invalidDeploymentIds) > 0 { + tflog.Error(ctx, "invalid deployment ids") + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + "One or more deployments is not in the organization, cannot set roles for deployments that do not exist", + fmt.Sprintf("The following deployment ids are invalid: %v", invalidDeploymentIds), + ), + } + } + + return nil +} diff --git a/internal/provider/resources/resource_api_token_test.go b/internal/provider/resources/resource_api_token_test.go index 7b03ef1f..9c559cbe 100644 --- a/internal/provider/resources/resource_api_token_test.go +++ b/internal/provider/resources/resource_api_token_test.go @@ -69,6 +69,21 @@ func TestAcc_ResourceOrganizationApiToken(t *testing.T) { }), ExpectError: regexp.MustCompile("Role 'WORKSPACE_OWNER' is not valid for token type 'ORGANIZATION'"), }, + // Test invalid organization id + { + Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ + Name: apiTokenName, + Type: string(iam.ORGANIZATION), + Roles: []apiTokenRole{ + { + Role: string(iam.ORGANIZATIONOWNER), + EntityId: "clz3blqb500lh01mtkwu9zk5z", + EntityType: string(iam.ORGANIZATION), + }, + }, + }), + ExpectError: regexp.MustCompile("API Token of type 'ORGANIZATION' cannot have an 'ORGANIZATION' role with a different organization id"), + }, // Test multiple roles of the same type { Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ @@ -89,6 +104,51 @@ func TestAcc_ResourceOrganizationApiToken(t *testing.T) { }), ExpectError: regexp.MustCompile("API Token of type 'ORGANIZATION' cannot have more than one role of the same type"), }, + // Test invalid workspace + { + Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ + Name: apiTokenName, + Type: string(iam.ORGANIZATION), + Roles: []apiTokenRole{ + { + Role: string(iam.ORGANIZATIONOWNER), + EntityId: organizationId, + EntityType: string(iam.ORGANIZATION), + }, + { + Role: string(iam.WORKSPACEOWNER), + EntityId: "clzjm8ixj001g01lmumcyo74q", + EntityType: string(iam.WORKSPACE), + }, + }, + }), + ExpectError: regexp.MustCompile("One or more workspaces is not in the organization, cannot set roles for workspaces that do not exist"), + }, + // Test invalid deployment + { + Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ + Name: apiTokenName, + Type: string(iam.ORGANIZATION), + Roles: []apiTokenRole{ + { + Role: string(iam.ORGANIZATIONOWNER), + EntityId: organizationId, + EntityType: string(iam.ORGANIZATION), + }, + { + Role: string(iam.WORKSPACEOWNER), + EntityId: workspaceId, + EntityType: string(iam.WORKSPACE), + }, + { + Role: "DEPLOYMENT_ADMIN", + EntityId: "clzk79utk030w01swob4ylsl0", + EntityType: string(iam.DEPLOYMENT), + }, + }, + }), + ExpectError: regexp.MustCompile("One or more deployments is not in the organization, cannot set roles for deployments that do not exist"), + }, // Create the organization api token { Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ @@ -171,6 +231,33 @@ func TestAcc_ResourceOrganizationApiToken(t *testing.T) { testAccCheckApiTokenExistence(t, checkApiTokensExistenceInput{name: apiTokenName, organization: true, shouldExist: true}), ), }, + // Test invalid expiry period update + { + Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ + Name: apiTokenName, + Description: "new description", + Type: string(iam.ORGANIZATION), + Roles: []apiTokenRole{ + { + Role: string(iam.ORGANIZATIONOWNER), + EntityId: organizationId, + EntityType: string(iam.ORGANIZATION), + }, + { + Role: string(iam.WORKSPACEOWNER), + EntityId: workspaceId, + EntityType: string(iam.WORKSPACE), + }, + { + Role: "DEPLOYMENT_ADMIN", + EntityId: deploymentId, + EntityType: string(iam.DEPLOYMENT), + }, + }, + ExpiryPeriodInDays: 1, + }), + ExpectError: regexp.MustCompile("API Token expiry period cannot be updated"), + }, // Change the resource type and remove roles and optional fields { Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ @@ -306,6 +393,41 @@ func TestAcc_ResourceWorkspaceApiToken(t *testing.T) { }), ExpectError: regexp.MustCompile("API Token of type 'WORKSPACE' cannot have more than one role of the same type"), }, + // Test invalid workspace + { + Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ + Name: apiTokenName, + Type: string(iam.WORKSPACE), + Roles: []apiTokenRole{ + { + Role: string(iam.WORKSPACEOWNER), + EntityId: "clzjm8ixj001g01lmumcyo74q", + EntityType: string(iam.WORKSPACE), + }, + }, + }), + ExpectError: regexp.MustCompile("One or more workspaces is not in the organization, cannot set roles for workspaces that do not exist"), + }, + // Test invalid deployment + { + Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ + Name: apiTokenName, + Type: string(iam.WORKSPACE), + Roles: []apiTokenRole{ + { + Role: string(iam.WORKSPACEOWNER), + EntityId: workspaceId, + EntityType: string(iam.WORKSPACE), + }, + { + Role: "DEPLOYMENT_ADMIN", + EntityId: "clzk79utk030w01swob4ylsl0", + EntityType: string(iam.DEPLOYMENT), + }, + }, + }), + ExpectError: regexp.MustCompile("One or more deployments is not in the organization, cannot set roles for deployments that do not exist"), + }, // Create the workspace api token { Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ @@ -500,6 +622,21 @@ func TestAcc_ResourceDeploymentApiToken(t *testing.T) { }), ExpectError: regexp.MustCompile("No matching role found for the specified entity type 'DEPLOYMENT'"), }, + // Test invalid deployment + { + Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ + Name: apiTokenName, + Type: string(iam.DEPLOYMENT), + Roles: []apiTokenRole{ + { + Role: "DEPLOYMENT_ADMIN", + EntityId: "clzk79utk030w01swob4ylsl0", + EntityType: string(iam.DEPLOYMENT), + }, + }, + }), + ExpectError: regexp.MustCompile("One or more deployments is not in the organization, cannot set roles for deployments that do not exist"), + }, // Create the deployment api token { Config: astronomerprovider.ProviderConfig(t, astronomerprovider.HOSTED) + apiToken(apiTokenInput{ diff --git a/internal/provider/resources/resource_team_test.go b/internal/provider/resources/resource_team_test.go index 87cca390..e70b05a9 100644 --- a/internal/provider/resources/resource_team_test.go +++ b/internal/provider/resources/resource_team_test.go @@ -88,7 +88,7 @@ func TestAcc_ResourceTeam(t *testing.T) { }, }, }), - ExpectError: regexp.MustCompile("Unable to mutate Team roles, not every deployment role has a corresponding workspace role"), + ExpectError: regexp.MustCompile("Unable to mutate roles, not every deployment role has a corresponding workspace role"), }, // Test failure: check for multiple roles with same entity id { diff --git a/internal/provider/resources/resource_user_roles.go b/internal/provider/resources/resource_user_roles.go index 7dc1a533..5d3961fb 100644 --- a/internal/provider/resources/resource_user_roles.go +++ b/internal/provider/resources/resource_user_roles.go @@ -192,6 +192,14 @@ func (r *UserRolesResource) Read( ) return } + if userRoles.JSON200.Status != iam.ACTIVE { + tflog.Error(ctx, "failed to get user_roles", map[string]interface{}{"error": err}) + resp.Diagnostics.AddError( + "Client Error", + fmt.Sprintf("User '%s' is not 'ACTIVE'", userId), + ) + return + } statusCode, diagnostic := clients.NormalizeAPIError(ctx, userRoles.HTTPResponse, userRoles.Body) // If the resource no longer exists, it is recommended to ignore the errors // and call RemoveResource to remove the resource from the state. The next Terraform plan will recreate the resource. @@ -206,7 +214,7 @@ func (r *UserRolesResource) Read( // Generate subjectRoles from the get user API response subjectRoles := iam.SubjectRoles{ - OrganizationRole: lo.ToPtr(iam.SubjectRolesOrganizationRole(*(*string)(userRoles.JSON200.OrganizationRole))), + OrganizationRole: lo.ToPtr(iam.SubjectRolesOrganizationRole(*userRoles.JSON200.OrganizationRole)), WorkspaceRoles: userRoles.JSON200.WorkspaceRoles, DeploymentRoles: userRoles.JSON200.DeploymentRoles, } diff --git a/internal/provider/resources/resource_user_roles_test.go b/internal/provider/resources/resource_user_roles_test.go index 9392198d..3cd9b53e 100644 --- a/internal/provider/resources/resource_user_roles_test.go +++ b/internal/provider/resources/resource_user_roles_test.go @@ -53,7 +53,7 @@ func TestAcc_ResourceUserRoles(t *testing.T) { }, }, }), - ExpectError: regexp.MustCompile("Unable to mutate Team roles, not every deployment role has a corresponding workspace role"), + ExpectError: regexp.MustCompile("Unable to mutate roles, not every deployment role has a corresponding workspace role"), }, // Test failure: check for multiple roles with same entity id {