Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deployments: support schedule #304

Merged
merged 19 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions docs/resources/deployment_schedule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "prefect_deployment_schedule Resource - prefect"
subcategory: ""
description: |-
The resource deployment_schedule represents a schedule for a deployment.
---

# prefect_deployment_schedule (Resource)

The resource `deployment_schedule` represents a schedule for a deployment.

## Example Usage

```terraform
provider "prefect" {}

data "prefect_workspace" "test" {
handle = "my-workspace"
}

resource "prefect_flow" "test" {
name = "my-flow"
workspace_id = data.prefect_workspace.test.id
tags = ["test"]
}

resource "prefect_deployment" "test" {
name = "my-deployment"
workspace_id = data.prefect_workspace.test.id
flow_id = prefect_flow.test.id
}

resource "prefect_deployment_schedule" "test" {
workspace_id = data.prefect_workspace.test.id
deployment_id = prefect_deployment.test.id

active = true
catchup = false
max_active_runs = 10
timezone = "America/New_York"

# Option: Interval schedule
interval = 30
anchor_date = "2024-01-01T00:00:00Z"

# Option: Cron schedule
cron = "0 0 * * *"
day_or = true

# Option: RRule schedule
rrule = "FREQ=DAILY;INTERVAL=1"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `deployment_id` (String) Deployment ID (UUID)

### Optional

- `account_id` (String) Account ID (UUID)
- `active` (Boolean) Whether or not the schedule is active.
- `anchor_date` (String) The anchor date of the schedule.
- `catchup` (Boolean) (Cloud only) Whether or not a worker should catch up on Late runs for the schedule.
- `cron` (String) The cron expression of the schedule.
- `day_or` (Boolean) Control croniter behavior for handling day and day_of_week entries.
- `id` (String) Deployment Schedule ID (UUID)
- `interval` (Number) The interval of the schedule.
- `max_active_runs` (Number) (Cloud only) The maximum number of active runs for the schedule.
- `max_scheduled_runs` (Number) The maximum number of scheduled runs for the schedule.
- `rrule` (String) The rrule expression of the schedule.
- `timezone` (String) The timezone of the schedule.
- `workspace_id` (String) Workspace ID (UUID)

### Read-Only

- `created` (String) Timestamp of when the resource was created (RFC3339)
- `updated` (String) Timestamp of when the resource was updated (RFC3339)
39 changes: 39 additions & 0 deletions examples/resources/prefect_deployment_schedule/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
provider "prefect" {}

data "prefect_workspace" "test" {
handle = "my-workspace"
}

resource "prefect_flow" "test" {
name = "my-flow"
workspace_id = data.prefect_workspace.test.id
tags = ["test"]
}

resource "prefect_deployment" "test" {
name = "my-deployment"
workspace_id = data.prefect_workspace.test.id
flow_id = prefect_flow.test.id
}

resource "prefect_deployment_schedule" "test" {
workspace_id = data.prefect_workspace.test.id
deployment_id = prefect_deployment.test.id

active = true
catchup = false
max_active_runs = 10
timezone = "America/New_York"

# Option: Interval schedule
interval = 30
anchor_date = "2024-01-01T00:00:00Z"

# Option: Cron schedule
cron = "0 0 * * *"
day_or = true

# Option: RRule schedule
rrule = "FREQ=DAILY;INTERVAL=1"
}

3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module github.com/prefecthq/terraform-provider-prefect

go 1.22.0
go 1.22.7

toolchain go1.22.9

require (
Expand Down
1 change: 1 addition & 0 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type PrefectClient interface {
Collections(accountID uuid.UUID, workspaceID uuid.UUID) (CollectionsClient, error)
Deployments(accountID uuid.UUID, workspaceID uuid.UUID) (DeploymentsClient, error)
DeploymentAccess(accountID uuid.UUID, workspaceID uuid.UUID) (DeploymentAccessClient, error)
DeploymentSchedule(accountID uuid.UUID, workspaceID uuid.UUID) (DeploymentScheduleClient, error)
Teams(accountID uuid.UUID) (TeamsClient, error)
Flows(accountID uuid.UUID, workspaceID uuid.UUID) (FlowsClient, error)
Workspaces(accountID uuid.UUID) (WorkspacesClient, error)
Expand Down
7 changes: 0 additions & 7 deletions internal/api/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,3 @@ type DeploymentUpdate struct {
WorkPoolName string `json:"work_pool_name,omitempty"`
WorkQueueName string `json:"work_queue_name,omitempty"`
}

// DeploymentFilter defines the search filter payload
// when searching for deployements by name.
// example request payload:
// {"deployments": {"handle": {"any_": ["test"]}}}.
type DeploymentFilter struct {
}
51 changes: 51 additions & 0 deletions internal/api/deployments_schedule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package api

import (
"context"

"github.com/google/uuid"
)

type DeploymentScheduleClient interface {
Create(ctx context.Context, deploymentID uuid.UUID, payload []DeploymentSchedulePayload) ([]*DeploymentSchedule, error)
Read(ctx context.Context, deploymentID uuid.UUID) ([]*DeploymentSchedule, error)
Update(ctx context.Context, deploymentID uuid.UUID, scheduleID uuid.UUID, payload DeploymentSchedulePayload) error
Delete(ctx context.Context, deploymentID uuid.UUID, scheduleID uuid.UUID) error
}

type DeploymentSchedule struct {
BaseModel
AccountID uuid.UUID `json:"account_id"`
WorkspaceID uuid.UUID `json:"workspace_id"`

DeploymentID uuid.UUID `json:"deployment_id"`

DeploymentSchedulePayload
}

type DeploymentSchedulePayload struct {
Active *bool `json:"active,omitempty"`
MaxScheduledRuns float32 `json:"max_scheduled_runs,omitempty"`

// Cloud only
MaxActiveRuns float32 `json:"max_active_runs,omitempty"`
Catchup bool `json:"catchup,omitempty"`

Schedule Schedule `json:"schedule,omitempty"`
}

type Schedule struct {
// All schedule kinds specify an interval.
Timezone string `json:"timezone,omitempty"`

// Schedule kind: interval
Interval float32 `json:"interval,omitempty"`
AnchorDate string `json:"anchor_date,omitempty"`

// Schedule kind: cron
Cron string `json:"cron,omitempty"`
DayOr bool `json:"day_or,omitempty"`

// Schedule kind: rrule
RRule string `json:"rrule,omitempty"`
}
161 changes: 161 additions & 0 deletions internal/client/deployment_schedule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
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"
)

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

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

// DeploymentSchedule returns a DeploymentScheduleClient.
//
//nolint:ireturn // required to support PrefectClient mocking
func (c *Client) DeploymentSchedule(accountID, workspaceID uuid.UUID) (api.DeploymentScheduleClient, 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 on either the provider or the resource")
}

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

func (c *DeploymentScheduleClient) Create(ctx context.Context, deploymentID uuid.UUID, payload []api.DeploymentSchedulePayload) ([]*api.DeploymentSchedule, error) {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(payload); err != nil {
return nil, fmt.Errorf("error encoding payload: %w", err)
}

url := fmt.Sprintf("%s/%s/%s", c.routePrefix, deploymentID, "schedules")
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, &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 schedules []*api.DeploymentSchedule
if err := json.NewDecoder(resp.Body).Decode(&schedules); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}

return schedules, nil
}

func (c *DeploymentScheduleClient) Read(ctx context.Context, deploymentID uuid.UUID) ([]*api.DeploymentSchedule, error) {
url := fmt.Sprintf("%s/%s/%s", c.routePrefix, deploymentID, "schedules")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, 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 schedules []*api.DeploymentSchedule
if err := json.NewDecoder(resp.Body).Decode(&schedules); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}

return schedules, nil
}

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

url := fmt.Sprintf("%s/%s/%s/%s", c.routePrefix, deploymentID.String(), "schedules", scheduleID.String())
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, url, &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 *DeploymentScheduleClient) Delete(ctx context.Context, deploymentID uuid.UUID, scheduleID uuid.UUID) error {
url := fmt.Sprintf("%s/%s/%s/%s", c.routePrefix, deploymentID.String(), "schedules", scheduleID.String())
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, 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
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ func (p *PrefectProvider) Resources(_ context.Context) []func() resource.Resourc
resources.NewFlowResource,
resources.NewDeploymentResource,
resources.NewDeploymentAccessResource,
resources.NewDeploymentScheduleResource,
resources.NewServiceAccountResource,
resources.NewVariableResource,
resources.NewWorkPoolResource,
Expand Down
2 changes: 2 additions & 0 deletions internal/provider/resources/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ func (r *DeploymentResource) Update(ctx context.Context, req resource.UpdateRequ
"Error creating deployment client",
fmt.Sprintf("Could not create deployment client, unexpected error: %s. This is a bug in the provider, please report this to the maintainers.", err.Error()),
)

return
}

deploymentID, err := uuid.Parse(model.ID.ValueString())
Expand Down
Loading
Loading