Skip to content

Commit

Permalink
initial automations client scaffold
Browse files Browse the repository at this point in the history
  • Loading branch information
parkedwards committed Nov 21, 2024
1 parent c3e3160 commit 18dd114
Show file tree
Hide file tree
Showing 2 changed files with 269 additions and 0 deletions.
114 changes: 114 additions & 0 deletions internal/api/automations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package api

import (
"context"

"github.com/google/uuid"
)

// AutomationsClient is a client for working with automations.
type AutomationsClient interface {
Get(ctx context.Context, id uuid.UUID) (*Automation, error)
Create(ctx context.Context, data AutomationCreate) (*Automation, error)
Update(ctx context.Context, id uuid.UUID, data AutomationUpdate) error
Delete(ctx context.Context, id uuid.UUID) error
}

type TriggerTypes interface {
// No methods needed - this is just for type union
}

type TriggerBase struct {
Type string `json:"type"`
ID string `json:"id"`
}

type EventTrigger struct {
TriggerBase
After []string `json:"after"`
Expect []string `json:"expect"`
ForEach []string `json:"for_each"`
Posture string `json:"posture"`
Threshold int `json:"threshold"`
Within int `json:"within"`
}

// Ensure EventTrigger implements TriggerTypes.
var _ TriggerTypes = (*EventTrigger)(nil)

type MetricTriggerOperator string

const (
LT MetricTriggerOperator = "<"
LTE MetricTriggerOperator = "<="
GT MetricTriggerOperator = ">"
GTE MetricTriggerOperator = ">="
)

type PrefectMetric string

type MetricTriggerQuery struct {
Name PrefectMetric `json:"name"`
Threshold float64 `json:"threshold"`
Operator MetricTriggerOperator `json:"operator"`
Range int `json:"range"` // duration in seconds, min 300
FiringFor int `json:"firing_for"` // duration in seconds, min 300
}

type MetricTrigger struct {
TriggerBase
Posture string `json:"posture"`
Metric MetricTriggerQuery `json:"metric"`
}

// Ensure MetricTrigger implements TriggerTypes.
var _ TriggerTypes = (*MetricTrigger)(nil)

type CompoundTrigger struct {
TriggerBase
Triggers []TriggerTypes `json:"triggers"`
Require interface{} `json:"require"` // int or "any"/"all"
Within *int `json:"within,omitempty"`
}

var _ TriggerTypes = (*CompoundTrigger)(nil)

type SequenceTrigger struct {
TriggerBase
Triggers []TriggerTypes `json:"triggers"`
Within *int `json:"within,omitempty"`
}

// Ensure SequenceTrigger implements TriggerTypes.
var _ TriggerTypes = (*SequenceTrigger)(nil)

type Action struct{}

type AutomationCore struct {
Name string `json:"name"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
Trigger TriggerTypes `json:"trigger"`
Actions []Action `json:"actions"`
ActionsOnTrigger []Action `json:"actions_on_trigger"`
ActionsOnResolve []Action `json:"actions_on_resolve"`
}

// Automation represents an automation response.
type Automation struct {
BaseModel
AutomationCore
AccountID uuid.UUID `json:"account"`
WorkspaceID uuid.UUID `json:"workspace"`
}

// AutomationCreate is the payload for creating automations.
type AutomationCreate struct {
AutomationCore
OwnerResource *string `json:"owner_resource"`
}

// AutomationUpdate is the payload for updating automations.
type AutomationUpdate struct {
AutomationCore
}
155 changes: 155 additions & 0 deletions internal/client/automations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package client

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

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

type AutomationsClient struct {
hc *http.Client
apiKey string
routePrefix string
}

// Automations is a factory that initializes and returns a AutomationsClient.
//
//nolint:ireturn // required to support PrefectClient mocking
func (c *Client) Automations(accountID uuid.UUID, workspaceID uuid.UUID) (api.AutomationsClient, error) {
if accountID == uuid.Nil {
accountID = c.defaultAccountID
}

if workspaceID == uuid.Nil {
workspaceID = c.defaultWorkspaceID
}

if helpers.IsCloudEndpoint(c.endpoint) && (accountID == uuid.Nil || workspaceID == uuid.Nil) {
return nil, fmt.Errorf("prefect Cloud endpoints require an account_id and workspace_id to be set")
}

return &AutomationsClient{
hc: c.hc,
apiKey: c.apiKey,
routePrefix: getWorkspaceScopedURL(c.endpoint, accountID, workspaceID, "automations"),
}, nil
}

func (c *AutomationsClient) Get(ctx context.Context, id uuid.UUID) (*api.Automation, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/%s", c.routePrefix, id), 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 {
errorBody, _ := io.ReadAll(resp.Body)

return nil, fmt.Errorf("status code %s, error=%s", resp.Status, errorBody)
}

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

return &automation, nil
}

func (c *AutomationsClient) Create(ctx context.Context, payload api.AutomationCreate) (*api.Automation, error) {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(&payload); err != nil {
return nil, fmt.Errorf("failed to encode create payload: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.routePrefix+"/", &buf)
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.StatusCreated {
errorBody, _ := io.ReadAll(resp.Body)

return nil, fmt.Errorf("status code %s, error=%s", resp.Status, errorBody)
}

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

return &automation, nil
}

func (c *AutomationsClient) Update(ctx context.Context, id uuid.UUID, payload api.AutomationUpdate) error {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(&payload); err != nil {
return fmt.Errorf("failed to encode update payload: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("%s/%s", c.routePrefix, id), &buf)
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}

setDefaultHeaders(req, c.apiKey)

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

if resp.StatusCode != http.StatusNoContent {
errorBody, _ := io.ReadAll(resp.Body)

return fmt.Errorf("status code %s, error=%s", resp.Status, errorBody)
}

return nil
}

func (c *AutomationsClient) Delete(ctx context.Context, id uuid.UUID) error {
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fmt.Sprintf("%s/%s", c.routePrefix, id), http.NoBody)
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}

setDefaultHeaders(req, c.apiKey)

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

if resp.StatusCode != http.StatusNoContent {
errorBody, _ := io.ReadAll(resp.Body)

return fmt.Errorf("status code %s, error=%s", resp.Status, errorBody)
}

return nil
}

0 comments on commit 18dd114

Please sign in to comment.