From 8cd9e1e32f8acb10514c32611877c07a78a16659 Mon Sep 17 00:00:00 2001 From: Murad Biashimov Date: Tue, 15 Oct 2024 18:11:36 +0200 Subject: [PATCH] feat(aiven_organization_user_list): add datasource --- CHANGELOG.md | 6 +- docs/data-sources/organization_user_list.md | 53 ++++++ internal/schemautil/schemautil.go | 41 ++++- internal/sdkprovider/provider/provider.go | 1 + .../organization_user_list_data_source.go | 156 ++++++++++++++++++ ...organization_user_list_data_source_test.go | 37 +++++ 6 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 docs/data-sources/organization_user_list.md create mode 100644 internal/sdkprovider/service/organization/organization_user_list_data_source.go create mode 100644 internal/sdkprovider/service/organization/organization_user_list_data_source_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 70da4a2af..e7322e612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,17 @@ nav_order: 1 +## [MAJOR.MINOR.PATCH] - YYYY-MM-DD + +- Add `aiven_organization_user_list` resource +- Run client-side validation for `aiven_kafka_schema` AVRO type schema + ## [4.27.0] - 2024-10-09 - Remove `aiven_thanos` from beta resources - Removes `receiver_ingesting_remote_write_uri` and `store_uri` Thanos connection info fields - Adds `stringtype` to `flink_external_postgresql_user_config` service integration - Fix `terraform import` for services with additional disk space or read replica service integration -- Run client-side validation for `aiven_kafka_schema` AVRO type schema ## [4.26.0] - 2024-09-25 diff --git a/docs/data-sources/organization_user_list.md b/docs/data-sources/organization_user_list.md new file mode 100644 index 000000000..1e8984c39 --- /dev/null +++ b/docs/data-sources/organization_user_list.md @@ -0,0 +1,53 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "aiven_organization_user_list Data Source - terraform-provider-aiven" +subcategory: "" +description: |- + List of users of the organization +--- + +# aiven_organization_user_list (Data Source) + +List of users of the organization + + + + +## Schema + +### Required + +- `name` (String) Account ID. Example `a` + +### Read-Only + +- `id` (String) The ID of this resource. +- `users` (List of Object) List of users of the organization (see [below for nested schema](#nestedatt--users)) + + +### Nested Schema for `users` + +Read-Only: + +- `is_super_admin` (Boolean) +- `join_time` (String) +- `last_activity_time` (String) +- `user_id` (String) +- `user_info` (List of Object) (see [below for nested schema](#nestedobjatt--users--user_info)) + + +### Nested Schema for `users.user_info` + +Read-Only: + +- `city` (String) +- `country` (String) +- `create_time` (String) +- `department` (String) +- `is_application_user` (Boolean) +- `job_title` (String) +- `managed_by_scim` (Boolean) +- `managing_organization_id` (String) +- `real_name` (String) +- `state` (String) +- `user_email` (String) diff --git a/internal/schemautil/schemautil.go b/internal/schemautil/schemautil.go index 31aa869b3..e82e50725 100644 --- a/internal/schemautil/schemautil.go +++ b/internal/schemautil/schemautil.go @@ -386,24 +386,24 @@ func ResourceDataGet(d *schema.ResourceData, dto any, fns ...KVModifier) error { k, value = f(k, value) } - m[k] = serializeValue(value) + m[k] = serializeGet(value) } return Remarshal(&m, dto) } -func serializeValue(value any) any { +func serializeGet(value any) any { switch t := value.(type) { case *schema.Set: - return serializeValue(t.List()) + return serializeGet(t.List()) case []any: for i, v := range t { - t[i] = serializeValue(v) + t[i] = serializeGet(v) } return t case map[string]any: for k, v := range t { - t[k] = serializeValue(v) + t[k] = serializeGet(v) } } return value @@ -442,6 +442,7 @@ func ResourceDataSet(s map[string]*schema.Schema, d *schema.ResourceData, dto an } } + m = serializeSet(s, m) for k := range s { if v, ok := m[k]; ok { if err = d.Set(k, v); err != nil { @@ -452,6 +453,36 @@ func ResourceDataSet(s map[string]*schema.Schema, d *schema.ResourceData, dto an return nil } +func serializeSet(s map[string]*schema.Schema, m map[string]any) map[string]any { + for k, prop := range s { + value, ok := m[k] + if !ok { + continue + } + + res, ok := prop.Elem.(*schema.Resource) + if !ok { + continue + } + + // When we have an object, we need to convert it to a list. + // So there is no difference between a single object and a list of objects. + var items []any + switch element := value.(type) { + case map[string]any: + items = append(items, serializeSet(res.Schema, element)) + case []any: + for _, v := range element { + items = append(items, serializeSet(res.Schema, v.(map[string]any))) + } + } + + m[k] = items + } + + return m +} + // RenameAliases renames field names on object top level func RenameAliases(aliases map[string]string) KVModifier { return func(k string, v any) (string, any) { diff --git a/internal/sdkprovider/provider/provider.go b/internal/sdkprovider/provider/provider.go index 7b11a21a6..9794046d9 100644 --- a/internal/sdkprovider/provider/provider.go +++ b/internal/sdkprovider/provider/provider.go @@ -91,6 +91,7 @@ func Provider(version string) (*schema.Provider, error) { // organization "aiven_organizational_unit": organization.DatasourceOrganizationalUnit(), "aiven_organization_user": organization.DatasourceOrganizationUser(), + "aiven_organization_user_list": organization.DatasourceOrganizationUserList(), "aiven_organization_user_group": organization.DatasourceOrganizationUserGroup(), "aiven_organization_application_user": organization.DatasourceOrganizationApplicationUser(), diff --git a/internal/sdkprovider/service/organization/organization_user_list_data_source.go b/internal/sdkprovider/service/organization/organization_user_list_data_source.go new file mode 100644 index 000000000..8bc522ef7 --- /dev/null +++ b/internal/sdkprovider/service/organization/organization_user_list_data_source.go @@ -0,0 +1,156 @@ +package organization + +import ( + "context" + "fmt" + + avngen "github.com/aiven/go-client-codegen" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/aiven/terraform-provider-aiven/internal/common" + "github.com/aiven/terraform-provider-aiven/internal/schemautil" +) + +func datasourceOrganizationUserListSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Account ID. Example `a`", + }, + "users": { + Type: schema.TypeList, + Computed: true, + Description: "List of users of the organization", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_super_admin": { + Type: schema.TypeBool, + Computed: true, + Description: "Super admin state of the organization user", + }, + "join_time": { + Type: schema.TypeString, + Computed: true, + Description: "Join time", + }, + "last_activity_time": { + Type: schema.TypeString, + Computed: true, + Description: "Last activity time", + }, + "user_id": { + Type: schema.TypeString, + Computed: true, + Description: "User ID", + }, + "user_info": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "city": { + Type: schema.TypeString, + Computed: true, + Description: "City", + }, + "country": { + Type: schema.TypeString, + Computed: true, + Description: "Country", + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: "Creation time", + }, + "department": { + Type: schema.TypeString, + Computed: true, + Description: "Department", + }, + "is_application_user": { + Type: schema.TypeBool, + Computed: true, + Description: "Is Application User", + }, + "job_title": { + Type: schema.TypeString, + Computed: true, + Description: "Job Title", + }, + "managed_by_scim": { + Type: schema.TypeBool, + Computed: true, + Description: "Managed By Scim", + }, + "managing_organization_id": { + Type: schema.TypeString, + Computed: true, + Description: "Managing Organization ID", + }, + "real_name": { + Type: schema.TypeString, + Computed: true, + Description: "Real Name", + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: "State", + }, + "user_email": { + Type: schema.TypeString, + Computed: true, + Description: "User Email", + }, + }, + }, + }, + }, + }, + }, + } +} + +func DatasourceOrganizationUserList() *schema.Resource { + return &schema.Resource{ + ReadContext: common.WithGenClient(datasourceOrganizationUserListRead), + Description: "List of users of the organization", + Schema: datasourceOrganizationUserListSchema(), + } +} + +func datasourceOrganizationUserListRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) error { + orgs, err := client.UserOrganizationsList(ctx) + if err != nil { + return err + } + + var organizationID string + name := d.Get("name").(string) + for _, o := range orgs { + if o.OrganizationName == name { + organizationID = o.OrganizationId + break + } + } + + if organizationID == "" { + return fmt.Errorf("organization %q not found", name) + } + + list, err := client.OrganizationUserList(ctx, organizationID) + if err != nil { + return fmt.Errorf("cannot get organization %q user list: %w", organizationID, err) + } + + users := map[string]any{"users": list} + err = schemautil.ResourceDataSet(datasourceOrganizationUserListSchema(), d, users) + if err != nil { + return err + } + + d.SetId(organizationID) + return nil +} diff --git a/internal/sdkprovider/service/organization/organization_user_list_data_source_test.go b/internal/sdkprovider/service/organization/organization_user_list_data_source_test.go new file mode 100644 index 000000000..54d34c9e0 --- /dev/null +++ b/internal/sdkprovider/service/organization/organization_user_list_data_source_test.go @@ -0,0 +1,37 @@ +package organization_test + +import ( + "fmt" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + acc "github.com/aiven/terraform-provider-aiven/internal/acctest" +) + +func testAccAivenOrganizationUserList() string { + return fmt.Sprintf(` +data "aiven_organization_user_list" "org" { + name = "%s" +} +`, os.Getenv("AIVEN_ORGANIZATION_NAME")) +} + +func TestAccAivenOrganizationUserList_basic(t *testing.T) { + resourceName := "data.aiven_organization_user_list.org" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccAivenOrganizationUserList(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "users.#"), + resource.TestMatchResourceAttr(resourceName, "users.0.user_info.0.user_email", regexp.MustCompile(`.*@.*`)), + ), + }, + }, + }) +}