From a40dc697b3c85c8a61a42afdd206d61af5944fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roope=20Karhap=C3=A4=C3=A4?= Date: Wed, 25 Sep 2024 10:51:04 +0300 Subject: [PATCH] feat: add new data source for marking ext identities This data source can be used to mark external identity for organization user. This is a light weight method to map user from external service to aiven internal user. --- CHANGELOG.md | 1 + docs/data-sources/external_identity.md | 23 +++ internal/plugin/provider.go | 2 + .../external_identity_data_source.go | 141 ++++++++++++++++++ .../external_identity_data_source_test.go | 65 ++++++++ 5 files changed, 232 insertions(+) create mode 100644 docs/data-sources/external_identity.md create mode 100644 internal/plugin/service/externalidentity/external_identity_data_source.go create mode 100644 internal/plugin/service/externalidentity/external_identity_data_source_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb36b524..05ef94e24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ nav_order: 1 - Remove `aiven_valkey` from beta resources - Remove `aiven_valkey_user` from beta resources +- Add capability to map external service user with internal aiven user with external_identity data source ## [4.25.0] - 2024-09-17 diff --git a/docs/data-sources/external_identity.md b/docs/data-sources/external_identity.md new file mode 100644 index 000000000..176caa88c --- /dev/null +++ b/docs/data-sources/external_identity.md @@ -0,0 +1,23 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "aiven_external_identity Data Source - terraform-provider-aiven" +subcategory: "" +description: |- + Map external service user with internal aiven user +--- + +# aiven_external_identity (Data Source) + +Map external service user with internal aiven user + + + + +## Schema + +### Required + +- `external_service_name` (String) the name of the external service where external_user_id exists +- `external_user_id` (String) the identity of the user on external platform +- `internal_user_id` (String) the identity of the user on aiven platform +- `organization_id` (String) the organization where internal_user_id exists diff --git a/internal/plugin/provider.go b/internal/plugin/provider.go index 5e46333ef..4349f6b88 100644 --- a/internal/plugin/provider.go +++ b/internal/plugin/provider.go @@ -13,6 +13,7 @@ import ( "github.com/aiven/terraform-provider-aiven/internal/common" "github.com/aiven/terraform-provider-aiven/internal/plugin/errmsg" + "github.com/aiven/terraform-provider-aiven/internal/plugin/service/externalidentity" "github.com/aiven/terraform-provider-aiven/internal/plugin/service/organization" "github.com/aiven/terraform-provider-aiven/internal/plugin/util" ) @@ -134,6 +135,7 @@ func (p *AivenProvider) DataSources(context.Context) []func() datasource.DataSou // List of data sources that are currently available in the provider. dataSources := []func() datasource.DataSource{ organization.NewOrganizationDataSource, + externalidentity.NewExternalIdentityDataSource, } // Add to a list of data sources that are currently in beta. diff --git a/internal/plugin/service/externalidentity/external_identity_data_source.go b/internal/plugin/service/externalidentity/external_identity_data_source.go new file mode 100644 index 000000000..ea5022c74 --- /dev/null +++ b/internal/plugin/service/externalidentity/external_identity_data_source.go @@ -0,0 +1,141 @@ +package externalidentity + +import ( + "context" + "fmt" + + "github.com/aiven/aiven-go-client/v2" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/aiven/terraform-provider-aiven/internal/plugin/util" +) + +var ( + _ datasource.DataSource = &externalIdentityDataSource{} + _ datasource.DataSourceWithConfigure = &externalIdentityDataSource{} + + _ util.TypeNameable = &externalIdentityDataSource{} +) + +func NewExternalIdentityDataSource() datasource.DataSource { + return &externalIdentityDataSource{} +} + +type externalIdentityDataSource struct { + client *aiven.Client + typeName string +} + +// externalIdentityDataSourceModel is the model for the external_identity data source. +type externalIdentityDataSourceModel struct { + OrganizationID types.String `tfsdk:"organization_id"` + InternalUserID types.String `tfsdk:"internal_user_id"` + ExternalUserID types.String `tfsdk:"external_user_id"` + ExternalServiceName types.String `tfsdk:"external_service_name"` +} + +// Metadata returns the metadata for the external_identity data source. +func (r *externalIdentityDataSource) Metadata( + _ context.Context, + req datasource.MetadataRequest, + resp *datasource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_external_identity" + r.typeName = resp.TypeName +} + +// TypeName returns the data source type name for the external_identity data source. +func (r *externalIdentityDataSource) TypeName() string { + return r.typeName +} + +// Schema defines the schema for the external_identity data source. +func (r *externalIdentityDataSource) Schema( + _ context.Context, + _ datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Map external service user with internal aiven user", + Attributes: map[string]schema.Attribute{ + "organization_id": schema.StringAttribute{ + Description: "the organization where internal_user_id exists", + Required: true, + }, + "internal_user_id": schema.StringAttribute{ + Description: "the identity of the user on aiven platform", + Required: true, + }, + "external_user_id": schema.StringAttribute{ + Description: "the identity of the user on external platform", + Required: true, + }, + "external_service_name": schema.StringAttribute{ + Description: "the name of the external service where external_user_id exists", + Required: true, + }, + }, + } +} + +// Configure sets up the external_identity data source. +func (r *externalIdentityDataSource) Configure( + _ context.Context, + req datasource.ConfigureRequest, + resp *datasource.ConfigureResponse, +) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*aiven.Client) + if !ok { + resp.Diagnostics = util.DiagErrorUnexpectedProviderDataType(resp.Diagnostics, req.ProviderData) + + return + } + + r.client = client +} + +// Read reads an external_identity data source. +func (r *externalIdentityDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state externalIdentityDataSourceModel + + if !util.ConfigToModel(ctx, &req.Config, &state, &resp.Diagnostics) { + return + } + + organizationID := state.OrganizationID.ValueString() + internalUserID := state.InternalUserID.ValueString() + externalUserID := state.ExternalUserID.ValueString() + externalServiceName := state.ExternalServiceName.ValueString() + + rm, err := r.client.OrganizationUser.List(ctx, organizationID) + if err != nil { + resp.Diagnostics = util.DiagErrorReadingDataSource(resp.Diagnostics, r, err) + } + + var user *aiven.OrganizationMemberInfo + for _, member := range rm.Users { + if member.UserID == internalUserID { + user = &member + } + } + + if user == nil { + err := fmt.Errorf("organization user %s not found in organization %s", internalUserID, organizationID) + resp.Diagnostics = util.DiagErrorReadingDataSource(resp.Diagnostics, r, err) + } + + state.OrganizationID = types.StringValue(organizationID) + state.InternalUserID = types.StringValue(internalUserID) + state.ExternalUserID = types.StringValue(externalUserID) + state.ExternalServiceName = types.StringValue(externalServiceName) + + if !util.ModelToPlanState(ctx, state, &resp.State, &resp.Diagnostics) { + return + } +} diff --git a/internal/plugin/service/externalidentity/external_identity_data_source_test.go b/internal/plugin/service/externalidentity/external_identity_data_source_test.go new file mode 100644 index 000000000..15d30fb77 --- /dev/null +++ b/internal/plugin/service/externalidentity/external_identity_data_source_test.go @@ -0,0 +1,65 @@ +package externalidentity_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + acc "github.com/aiven/terraform-provider-aiven/internal/acctest" +) + +// TestExternalIdentityDataSource tests the external_identity datasource. +func TestExternalIdentityDataSource(t *testing.T) { + deps := acc.CommonTestDependencies(t) + + organizationName := deps.OrganizationName() + userID := deps.OrganizationUserID(true) + prefix := acc.DefaultResourceNamePrefix + suffix := acctest.RandStringFromCharSet(acc.DefaultRandomSuffixLength, acctest.CharSetAlphaNum) + userGroupName := fmt.Sprintf("%s-usr-group-%s", prefix, suffix) + resourceName := "data.aiven_external_identity.foo" + externalUserID := "alice" + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testExternalIdentityDataSourceBasic(organizationName, userGroupName, *userID, externalUserID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "external_user_id", "alice"), + resource.TestCheckResourceAttr(resourceName, "internal_user_id", *userID), + ), + }, + }, + }) +} + +func testExternalIdentityDataSourceBasic(organizationName string, userGroupName string, userID string, externalUserID string) string { + return fmt.Sprintf(` + data "aiven_organization" "foo" { + name = "%s" + } + + resource "aiven_organization_user_group" "foo" { + organization_id = data.aiven_organization.foo.id + name = "%s" + description = "Terraform acceptance tests" + } + + resource "aiven_organization_user_group_member" "foo" { + organization_id = data.aiven_organization.foo.id + group_id = aiven_organization_user_group.foo.group_id + user_id = "%s" + } + + data "aiven_external_identity" "foo" { + organization_id = data.aiven_organization.foo.id + internal_user_id = "%[3]s" + external_user_id = "%s" + external_service_name = "github" + } +`, organizationName, userGroupName, userID, externalUserID) +}