From c108605ef2b2e48b5c09d3ec2a981e903905ac5c Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Thu, 8 May 2025 23:54:51 +0200 Subject: [PATCH] feat(provider): add users data-source --- CHANGELOG.md | 3 +- docs/data-sources/users.md | 53 +++++ .../data-sources/forgejo_users/data-source.tf | 1 + go.mod | 1 + go.sum | 2 + internal/client/users.go | 56 +++++ internal/provider/provider.go | 4 +- internal/provider/users_data_source.go | 211 ++++++++++++++++++ 8 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 docs/data-sources/users.md create mode 100644 examples/data-sources/forgejo_users/data-source.tf create mode 100644 internal/client/users.go create mode 100644 internal/provider/users_data_source.go diff --git a/CHANGELOG.md b/CHANGELOG.md index e2247d6..955ad70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,4 +6,5 @@ All notable changes to this project will be documented in this file. ### Added -- Added provider configuration +- Added provider configuration. +- Added users data-source. diff --git a/docs/data-sources/users.md b/docs/data-sources/users.md new file mode 100644 index 0000000..b4529ce --- /dev/null +++ b/docs/data-sources/users.md @@ -0,0 +1,53 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "forgejo_users Data Source - terraform-provider-forgejo" +subcategory: "" +description: |- + Use this data source to retrieve information about existing forgejo users. +--- + +# forgejo_users (Data Source) + +Use this data source to retrieve information about existing forgejo users. + +## Example Usage + +```terraform +data "forgejo_users" "example" {} +``` + + +## Schema + +### Read-Only + +- `elements` (Attributes List) The list of users. (see [below for nested schema](#nestedatt--elements)) + + +### Nested Schema for `elements` + +Read-Only: + +- `active` (Boolean) Whether the user is active or not. +- `avatar_url` (String) The user's avatar URL. +- `created` (String) The user's creation date and time. +- `description` (String) A description string. +- `email` (String) The user's email address. +- `followers_count` (Number) The number of followers. +- `following_count` (Number) The number of followings. +- `full_name` (String) The user's full name. +- `html_url` (String) The URL to this user's Forgejo profile page. +- `id` (Number) The identifier of the user. +- `is_admin` (Boolean) Whether the user is an admin or not. +- `language` (String) The user's chosen language. +- `last_login` (String) The user's last login date and time. +- `location` (String) The user's advertised location. +- `login` (String) The login of the user. +- `login_name` (String) The user's authentication sign-in name. +- `prohibit_login` (Boolean) Whether the user is allowed to log in or not. +- `pronouns` (String) The user's advertised pronouns. +- `restricted` (Boolean) Whether the user is restricted or not. +- `source_id` (Number) The identifier of the users authentication source. +- `starred_repos_count` (Number) The number of repositoties starred by the user. +- `visibility` (String) The user's visibility option: limited, private, public. +- `website` (String) The user's advertised website. diff --git a/examples/data-sources/forgejo_users/data-source.tf b/examples/data-sources/forgejo_users/data-source.tf new file mode 100644 index 0000000..9b8ba57 --- /dev/null +++ b/examples/data-sources/forgejo_users/data-source.tf @@ -0,0 +1 @@ +data "forgejo_users" "example" {} diff --git a/go.mod b/go.mod index 0915fb8..7ba4a90 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/hashicorp/hc-install v0.9.1 // indirect github.com/hashicorp/terraform-exec v0.22.0 // indirect github.com/hashicorp/terraform-json v0.24.0 // indirect + github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 // indirect github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.5 // indirect diff --git a/go.sum b/go.sum index 081565e..4061737 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/hashicorp/terraform-plugin-docs v0.21.0 h1:yoyA/Y719z9WdFJAhpUkI1jRbK github.com/hashicorp/terraform-plugin-docs v0.21.0/go.mod h1:J4Wott1J2XBKZPp/NkQv7LMShJYOcrqhQ2myXBcu64s= github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY= github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4= +github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3EM8fKMh6up9cJUFQ2iRaFsYLP8UJnCco= +github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= diff --git a/internal/client/users.go b/internal/client/users.go new file mode 100644 index 0000000..be2bcaf --- /dev/null +++ b/internal/client/users.go @@ -0,0 +1,56 @@ +package client + +import ( + "context" + "fmt" + "net/url" + "time" +) + +type User struct { + Active bool `json:"active"` + AvatarUrl string `json:"avatar_url"` + Created time.Time `json:"created"` + Description string `json:"description"` + Email string `json:"email"` + FollowerCount int64 `json:"followers_count"` + FollowingCount int64 `json:"following_count"` + FullName string `json:"full_name"` + HtmlUrl string `json:"html_url"` + Id int64 `json:"id"` + IsAdmin bool `json:"is_admin"` + Language string `json:"language"` + LastLogin time.Time `json:"last_login"` + Location string `json:"location"` + LoginName string `json:"login_name"` + Login string `json:"login"` + ProhibitLogin bool `json:"prohibit_login"` + Pronouns string `json:"pronouns"` + Restricted bool `json:"restricted"` + SourceId int64 `json:"source_id"` + StarredRepoCount int64 `json:"starred_repos_count"` + Visibility string `json:"visibility"` + Website string `json:"website"` +} + +func (c *Client) UsersList(ctx context.Context) ([]User, error) { + type Response struct { + Data []User `json:"data"` + Ok bool `json:"ok"` + } + var response Response + query := make(url.Values) + query.Set("limit", "50") + query.Set("page", "1") + uriRef := url.URL{ + Path: "api/v1/users/search", + RawQuery: query.Encode(), + } + if err := c.Send(ctx, "GET", &uriRef, nil, &response); err != nil { + return nil, fmt.Errorf("failed to search users: %w", err) + } + if !response.Ok { + return response.Data, fmt.Errorf("got a non OK status when querying users/search") + } + return response.Data, nil +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a932f16..55b5847 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -79,5 +79,7 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource { } func (p *Provider) DataSources(ctx context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{} + return []func() datasource.DataSource{ + NewUsersDataSource, + } } diff --git a/internal/provider/users_data_source.go b/internal/provider/users_data_source.go new file mode 100644 index 0000000..fad6a18 --- /dev/null +++ b/internal/provider/users_data_source.go @@ -0,0 +1,211 @@ +package provider + +import ( + "context" + "fmt" + + "git.adyxax.org/adyxax/terraform-provider-forgejo/internal/client" + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type UsersDataSource struct { + client *client.Client +} + +var _ datasource.DataSource = &UsersDataSource{} // Ensure provider defined types fully satisfy framework interfaces +func NewUsersDataSource() datasource.DataSource { + return &UsersDataSource{} +} + +type UsersDataSourceModel struct { + Elements []UserDataSourceModel `tfsdk:"elements"` +} +type UserDataSourceModel struct { + Active types.Bool `tfsdk:"active"` + AvatarUrl types.String `tfsdk:"avatar_url"` + Created timetypes.RFC3339 `tfsdk:"created"` + Description types.String `tfsdk:"description"` + Email types.String `tfsdk:"email"` + FollowerCount types.Int64 `tfsdk:"followers_count"` + FollowingCount types.Int64 `tfsdk:"following_count"` + FullName types.String `tfsdk:"full_name"` + HtmlUrl types.String `tfsdk:"html_url"` + Id types.Int64 `tfsdk:"id"` + IsAdmin types.Bool `tfsdk:"is_admin"` + Language types.String `tfsdk:"language"` + LastLogin timetypes.RFC3339 `tfsdk:"last_login"` + Location types.String `tfsdk:"location"` + LoginName types.String `tfsdk:"login_name"` + Login types.String `tfsdk:"login"` + ProhibitLogin types.Bool `tfsdk:"prohibit_login"` + Pronouns types.String `tfsdk:"pronouns"` + Restricted types.Bool `tfsdk:"restricted"` + SourceId types.Int64 `tfsdk:"source_id"` + StarredRepoCount types.Int64 `tfsdk:"starred_repos_count"` + Visibility types.String `tfsdk:"visibility"` + Website types.String `tfsdk:"website"` +} + +func (d *UsersDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_users" +} + +func (d *UsersDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "elements": schema.ListNestedAttribute{ + Computed: true, + MarkdownDescription: "The list of users.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "active": schema.BoolAttribute{ + Computed: true, + MarkdownDescription: "Whether the user is active or not.", + }, + "avatar_url": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user's avatar URL.", + }, + "created": schema.StringAttribute{ + Computed: true, + CustomType: timetypes.RFC3339Type{}, + MarkdownDescription: "The user's creation date and time.", + }, + "description": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "A description string.", + }, + "email": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user's email address.", + }, + "followers_count": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "The number of followers.", + }, + "following_count": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "The number of followings.", + }, + "full_name": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user's full name.", + }, + "html_url": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The URL to this user's Forgejo profile page.", + }, + "id": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "The identifier of the user.", + }, + "is_admin": schema.BoolAttribute{ + Computed: true, + MarkdownDescription: "Whether the user is an admin or not.", + }, + "language": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user's chosen language.", + }, + "last_login": schema.StringAttribute{ + Computed: true, + CustomType: timetypes.RFC3339Type{}, + MarkdownDescription: "The user's last login date and time.", + }, + "location": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user's advertised location.", + }, + "login_name": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user's authentication sign-in name.", + }, + "login": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The login of the user.", + }, + "prohibit_login": schema.BoolAttribute{ + Computed: true, + MarkdownDescription: "Whether the user is allowed to log in or not.", + }, + "pronouns": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user's advertised pronouns.", + }, + "restricted": schema.BoolAttribute{ + Computed: true, + MarkdownDescription: "Whether the user is restricted or not.", + }, + "source_id": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "The identifier of the users authentication source.", + }, + "starred_repos_count": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "The number of repositoties starred by the user.", + }, + "visibility": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user's visibility option: limited, private, public.", + }, + "website": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user's advertised website.", + }, + }, + }, + }, + }, + MarkdownDescription: "Use this data source to retrieve information about existing forgejo users.", + } +} + +func (d *UsersDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + d.client, _ = req.ProviderData.(*client.Client) +} + +func (d *UsersDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data UsersDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + users, err := d.client.UsersList(ctx) + if err != nil { + resp.Diagnostics.AddError("ListUsers", fmt.Sprintf("Unable to list users, got error: %s", err)) + return + } + userList := make([]UserDataSourceModel, len(users)) + for i, user := range users { + userList[i] = UserDataSourceModel{ + Active: types.BoolValue(user.Active), + AvatarUrl: types.StringValue(user.AvatarUrl), + Created: timetypes.NewRFC3339TimeValue(user.Created), + Description: types.StringValue(user.Description), + Email: types.StringValue(user.Email), + FollowerCount: types.Int64Value(user.FollowerCount), + FollowingCount: types.Int64Value(user.FollowingCount), + FullName: types.StringValue(user.FullName), + HtmlUrl: types.StringValue(user.HtmlUrl), + Id: types.Int64Value(user.Id), + IsAdmin: types.BoolValue(user.IsAdmin), + Language: types.StringValue(user.Language), + LastLogin: timetypes.NewRFC3339TimeValue(user.LastLogin), + Location: types.StringValue(user.Location), + LoginName: types.StringValue(user.LoginName), + Login: types.StringValue(user.Login), + ProhibitLogin: types.BoolValue(user.ProhibitLogin), + Pronouns: types.StringValue(user.Pronouns), + Restricted: types.BoolValue(user.Restricted), + SourceId: types.Int64Value(user.SourceId), + StarredRepoCount: types.Int64Value(user.StarredRepoCount), + Visibility: types.StringValue(user.Visibility), + Website: types.StringValue(user.Website), + } + } + data.Elements = userList + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +}