feat(provider): add users data-source

This commit is contained in:
Julien Dessaux 2025-05-08 23:54:51 +02:00
parent 349b11254d
commit 80fb21068f
Signed by: adyxax
GPG key ID: F92E51B86E07177E
8 changed files with 337 additions and 2 deletions

57
internal/client/users.go Normal file
View file

@ -0,0 +1,57 @@
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"`
Username string `json:"username"`
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
}

View file

@ -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,
}
}

View file

@ -0,0 +1,217 @@
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"`
Username types.String `tfsdk:"username"`
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.",
},
"username": schema.StringAttribute{
Computed: true,
MarkdownDescription: "The username.",
},
"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(int64(user.FollowerCount)),
FollowingCount: types.Int64Value(int64(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),
Username: types.StringValue(user.Username),
Visibility: types.StringValue(user.Visibility),
Website: types.StringValue(user.Website),
}
}
data.Elements = userList
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}