Skip to content

Commit

Permalink
feat: add bot datasource
Browse files Browse the repository at this point in the history
  • Loading branch information
parkedwards committed Oct 21, 2023
1 parent feec9cd commit b064d59
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 9 deletions.
31 changes: 31 additions & 0 deletions internal/api/bots.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package api

import (
"context"
"time"

"github.com/google/uuid"
)

// BotsClient is a client for working with Service Accounts

Check failure on line 10 in internal/api/bots.go

View workflow job for this annotation

GitHub Actions / Lint

Comment should end in a period (godot)
type BotsClient interface {
Get(ctx context.Context, id uuid.UUID) (*Bot, error)
}

// BotAPIKey represents the nested API Key
// included in a Service Account response

Check failure on line 16 in internal/api/bots.go

View workflow job for this annotation

GitHub Actions / Lint

Comment should end in a period (godot)
type BotAPIKey struct {
BaseModel
Name string `json:"name"`
Key *string `json:"key"`
Expiration *time.Time `json:"expiration"`
}

// Bot is the base representation of a Service Account

Check failure on line 24 in internal/api/bots.go

View workflow job for this annotation

GitHub Actions / Lint

Comment should end in a period (godot)
type Bot struct {
BaseModel
Name string `json:"name"`
AccountID uuid.UUID `json:"account_id"`
AccountRoleName string `json:"account_role_name"`
APIKey *BotAPIKey `json:"api_key"`
}
1 change: 1 addition & 0 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "github.com/google/uuid"
// PrefectClient returns clients for different aspects of our API.
type PrefectClient interface {
Accounts() (AccountsClient, error)
Bots() (BotsClient, error)
Workspaces(accountID uuid.UUID) (WorkspacesClient, error)
WorkPools(accountID uuid.UUID, workspaceID uuid.UUID) (WorkPoolsClient, error)
Variables(accountID uuid.UUID, workspaceID uuid.UUID) (VariablesClient, error)
Expand Down
57 changes: 57 additions & 0 deletions internal/client/bots.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package client

import (
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/google/uuid"
"github.com/prefecthq/terraform-provider-prefect/internal/api"
)

var _ = api.BotsClient(&BotsClient{})

// BotsClient is a client for working with service accounts.
type BotsClient struct {
hc *http.Client
apiKey string
routePrefix string
}

// Bots returns a BotsClient.
func (c *Client) Bots() (api.BotsClient, error) {

Check failure on line 23 in internal/client/bots.go

View workflow job for this annotation

GitHub Actions / Lint

Bots returns interface (github.com/prefecthq/terraform-provider-prefect/internal/api.BotsClient) (ireturn)
return &BotsClient{
hc: c.hc,
apiKey: c.apiKey,
routePrefix: fmt.Sprintf("%s/api/accounts/%s/bots", c.endpoint, c.defaultAccountID),
}, nil
}

// Get a single bot by ID
func (c *BotsClient) Get(ctx context.Context, id uuid.UUID) (*api.Bot, error) {
path := fmt.Sprintf("%s/%s", c.routePrefix, id.String())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, http.NoBody)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}

setDefaultHeaders(req, c.apiKey)

resp, err := c.hc.Do(req)
if err != nil {
return nil, fmt.Errorf("http error: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("status code %s", resp.Status)
}

var bot api.Bot
if err := json.NewDecoder(resp.Body).Decode(&bot); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}

return &bot, nil
}
142 changes: 142 additions & 0 deletions internal/provider/datasources/bot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package datasources

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/prefecthq/terraform-provider-prefect/internal/api"
"github.com/prefecthq/terraform-provider-prefect/internal/provider/customtypes"
)

// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &BotDataSource{}
_ datasource.DataSourceWithConfigure = &BotDataSource{}
)

// BotDataSource contains state for the data source modeling a Prefect Service Account.
type BotDataSource struct {
client api.PrefectClient
}

// AccountDataSourceModel defines the Terraform data source model.
// the TF data source configuration will be unmarsheled into this struct
// NOTE: the APIKey field is not included in bot fetches and
// is excluded from this datasource model
type BotDataSourceModel struct {
ID customtypes.UUIDValue `tfsdk:"id"`
Created customtypes.TimestampValue `tfsdk:"created"`
Updated customtypes.TimestampValue `tfsdk:"updated"`

Name types.String `tfsdk:"name"`
AccountID customtypes.UUIDValue `tfsdk:"account_id"`
AccountRoleName types.String `tfsdk:"account_role_name"`
}

func NewBotDataSource() datasource.DataSource {

Check failure on line 39 in internal/provider/datasources/bot.go

View workflow job for this annotation

GitHub Actions / Lint

NewBotDataSource returns interface (github.com/hashicorp/terraform-plugin-framework/datasource.DataSource) (ireturn)
return &BotDataSource{}
}

// Metadata returns the data source type name.
func (d *BotDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_bot"
}

func (d *BotDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Data Source representing a Prefect Service Account",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Required: true,
CustomType: customtypes.UUIDType{},
Description: "Service Account UUID",
},
"created": schema.StringAttribute{
Computed: true,
CustomType: customtypes.TimestampType{},
Description: "Date and time of the Service Account creation in RFC 3339 format",
},
"updated": schema.StringAttribute{
Computed: true,
CustomType: customtypes.TimestampType{},
Description: "Date and time that the Service Account was last updated in RFC 3339 format",
},
"name": schema.StringAttribute{
Computed: true,
Description: "Name of the Service Account",
},
"account_id": schema.StringAttribute{
Computed: true,
CustomType: customtypes.UUIDType{},
Description: "Account UUID where Service Account resides",
},
"account_role_name": schema.StringAttribute{
Computed: true,
Description: "Name of the Service Account",
},
},
}
}

// Configure adds the provider-configured client to the data source.
func (d *BotDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(api.PrefectClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected api.PrefectClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

d.client = client
}

func (d *BotDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var model BotDataSourceModel

// Populate the model from data source configuration and emit diagnostics on error
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}

client, err := d.client.Bots()
if err != nil {
resp.Diagnostics.AddError(
"Error creating the Bots client",
fmt.Sprintf("Could not create Bots client, unexpected error: %s. This is a bug in the provider, please report this to the maintainers.", err.Error()),
)
return

Check failure on line 118 in internal/provider/datasources/bot.go

View workflow job for this annotation

GitHub Actions / Lint

return with no blank line before (nlreturn)
}

bot, err := client.Get(ctx, model.ID.ValueUUID())
if err != nil {
resp.Diagnostics.AddError(
"Failed to fetch Bot and refresh state",
fmt.Sprintf("Could not fetch Bot, unexpected error: %s", err.Error()),
)
return

Check failure on line 127 in internal/provider/datasources/bot.go

View workflow job for this annotation

GitHub Actions / Lint

return with no blank line before (nlreturn)
}

model.ID = customtypes.NewUUIDValue(bot.ID)
model.Created = customtypes.NewTimestampPointerValue(bot.Created)
model.Updated = customtypes.NewTimestampPointerValue(bot.Updated)

model.Name = types.StringValue(bot.Name)
model.AccountID = customtypes.NewUUIDValue(bot.AccountID)
model.AccountRoleName = types.StringValue(bot.AccountRoleName)

resp.Diagnostics.Append(resp.State.Set(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}
}
10 changes: 1 addition & 9 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,6 @@ func (p *PrefectProvider) Configure(ctx context.Context, req provider.ConfigureR
)
}

if config.APIKey.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("api_key"),
"Unknown Prefect API Key",
"The Prefect API Key is not known at configuration time. "+
"Potential resolutions: target apply the source of the value first, set the value statically in the configuration, set the PREFECT_API_URL environment variable, or remove the value.",
)
}

if config.AccountID.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("account_id"),
Expand Down Expand Up @@ -207,6 +198,7 @@ func (p *PrefectProvider) DataSources(_ context.Context) []func() datasource.Dat
datasources.NewWorkPoolDataSource,
datasources.NewWorkPoolsDataSource,
datasources.NewWorkspaceDataSource,
datasources.NewBotDataSource,
}
}

Expand Down

0 comments on commit b064d59

Please sign in to comment.