diff --git a/internal/provider/data_source_all_usergroups.go b/internal/provider/data_source_all_usergroups.go index 4504347..a427c98 100644 --- a/internal/provider/data_source_all_usergroups.go +++ b/internal/provider/data_source_all_usergroups.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + "github.com/essent/terraform-provider-slack/internal/slackExt" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" @@ -21,7 +23,7 @@ func NewAllUserGroupsDataSource() datasource.DataSource { } type AllUserGroupsDataSource struct { - client *slack.Client + client slackExt.Client } type AllUserGroupsDataSourceModel struct { @@ -113,7 +115,7 @@ func (d *AllUserGroupsDataSource) Read(ctx context.Context, req datasource.ReadR return } - userGroups, err := d.client.GetUserGroups(slack.GetUserGroupsOptionIncludeUsers(true)) + userGroups, err := d.client.GetUserGroups(ctx, slack.GetUserGroupsOptionIncludeUsers(true)) if err != nil { resp.Diagnostics.AddError( "Client Error", diff --git a/internal/provider/data_source_all_users.go b/internal/provider/data_source_all_users.go index c28c0cb..46e3a05 100644 --- a/internal/provider/data_source_all_users.go +++ b/internal/provider/data_source_all_users.go @@ -7,11 +7,12 @@ import ( "context" "fmt" + "github.com/essent/terraform-provider-slack/internal/slackExt" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/slack-go/slack" ) var _ datasource.DataSource = &AllUsersDataSource{} @@ -21,7 +22,7 @@ func NewAllUsersDataSource() datasource.DataSource { } type AllUsersDataSource struct { - client *slack.Client + client slackExt.Client } type AllUsersDataSourceModel struct { diff --git a/internal/provider/data_source_user.go b/internal/provider/data_source_user.go index 163fceb..5a6c2bc 100644 --- a/internal/provider/data_source_user.go +++ b/internal/provider/data_source_user.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + "github.com/essent/terraform-provider-slack/internal/slackExt" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -24,7 +26,7 @@ func NewUserDataSource() datasource.DataSource { } type UserDataSource struct { - client *slack.Client + client slackExt.Client } type UserDataSourceModel struct { @@ -95,9 +97,9 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r ) if !data.ID.IsNull() { - user, err = d.client.GetUserInfo(data.ID.ValueString()) + user, err = d.client.GetUserInfo(ctx, data.ID.ValueString()) } else { - user, err = d.client.GetUserByEmail(data.Email.ValueString()) + user, err = d.client.GetUserByEmail(ctx, data.Email.ValueString()) } if err != nil { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a7bce2d..17b8304 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -8,6 +8,8 @@ import ( "fmt" "os" + "github.com/essent/terraform-provider-slack/internal/slackExt" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/provider" @@ -30,7 +32,7 @@ type SlackProviderModel struct { } type SlackProviderData struct { - Client *slack.Client + Client slackExt.Client } func (p *SlackProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { @@ -83,8 +85,8 @@ func (p *SlackProvider) Configure(ctx context.Context, req provider.ConfigureReq return } - resp.DataSourceData = &SlackProviderData{Client: client} - resp.ResourceData = &SlackProviderData{Client: client} + resp.DataSourceData = &SlackProviderData{Client: slackExt.New(client)} + resp.ResourceData = &SlackProviderData{Client: slackExt.New(client)} } func (p *SlackProvider) Resources(ctx context.Context) []func() resource.Resource { diff --git a/internal/slackExt/client.go b/internal/slackExt/client.go new file mode 100644 index 0000000..84170ed --- /dev/null +++ b/internal/slackExt/client.go @@ -0,0 +1,18 @@ +package slackExt + +import ( + "context" + + "github.com/slack-go/slack" +) + +type Client interface { + GetUserInfo(ctx context.Context, user string) (*slack.User, error) + GetUserByEmail(ctx context.Context, email string) (*slack.User, error) + GetUsersContext(ctx context.Context) ([]slack.User, error) + GetUserGroups(ctx context.Context, options ...slack.GetUserGroupsOption) ([]slack.UserGroup, error) +} + +func New(base *slack.Client) Client { + return &clientRateLimit{&clientImpl{base}} +} diff --git a/internal/slackExt/client_impl.go b/internal/slackExt/client_impl.go new file mode 100644 index 0000000..2e62d9c --- /dev/null +++ b/internal/slackExt/client_impl.go @@ -0,0 +1,27 @@ +package slackExt + +import ( + "context" + + "github.com/slack-go/slack" +) + +type clientImpl struct { + base *slack.Client +} + +func (c *clientImpl) GetUserInfo(ctx context.Context, user string) (*slack.User, error) { + return c.base.GetUserInfoContext(ctx, user) +} + +func (c *clientImpl) GetUserByEmail(ctx context.Context, email string) (*slack.User, error) { + return c.base.GetUserByEmailContext(ctx, email) +} + +func (c *clientImpl) GetUsersContext(ctx context.Context) ([]slack.User, error) { + return c.base.GetUsersContext(ctx) +} + +func (c *clientImpl) GetUserGroups(ctx context.Context, options ...slack.GetUserGroupsOption) ([]slack.UserGroup, error) { + return c.base.GetUserGroupsContext(ctx, options...) +} diff --git a/internal/slackExt/client_rate_limit.go b/internal/slackExt/client_rate_limit.go new file mode 100644 index 0000000..261251b --- /dev/null +++ b/internal/slackExt/client_rate_limit.go @@ -0,0 +1,54 @@ +package slackExt + +import ( + "context" + "time" + + "github.com/slack-go/slack" +) + +type clientRateLimit struct { + base Client +} + +func rateLimit[R any](ctx context.Context, f func() (R, error), getZeroValue func() R) (result R, err error) { + for { + result, err = f() + + if err == nil { + return result, nil + } + + if rateLimitedError, ok := err.(*slack.RateLimitedError); ok { + select { + case <-time.After(rateLimitedError.RetryAfter): + case <-ctx.Done(): + return getZeroValue(), ctx.Err() + } + } else { + return getZeroValue(), err + } + } +} + +func (c *clientRateLimit) GetUserInfo(ctx context.Context, user string) (result *slack.User, err error) { + return rateLimit(ctx, func() (*slack.User, error) { + return c.base.GetUserInfo(ctx, user) + }, func() *slack.User { return nil }) +} + +func (c *clientRateLimit) GetUserByEmail(ctx context.Context, email string) (*slack.User, error) { + return rateLimit(ctx, func() (*slack.User, error) { + return c.base.GetUserByEmail(ctx, email) + }, func() *slack.User { return nil }) +} + +func (c *clientRateLimit) GetUsersContext(ctx context.Context) ([]slack.User, error) { + return c.base.GetUsersContext(ctx) +} + +func (c *clientRateLimit) GetUserGroups(ctx context.Context, options ...slack.GetUserGroupsOption) ([]slack.UserGroup, error) { + return rateLimit(ctx, func() ([]slack.UserGroup, error) { + return c.base.GetUserGroups(ctx, options...) + }, func() []slack.UserGroup { return []slack.UserGroup{} }) +}