diff --git a/docs/data-sources/automation.md b/docs/data-sources/automation.md
new file mode 100644
index 00000000..17e6bffb
--- /dev/null
+++ b/docs/data-sources/automation.md
@@ -0,0 +1,263 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "prefect_automation Data Source - prefect"
+subcategory: ""
+description: |-
+ Get information about an existing Automation by its ID
+---
+
+# prefect_automation (Data Source)
+
+Get information about an existing Automation by its ID
+
+## Example Usage
+
+```terraform
+data "prefect_automation" "test" {
+ id = "7759cb06-ea19-48fc-b277-61db8bdb3de9"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `id` (String) Automation ID (UUID)
+
+### Optional
+
+- `account_id` (String) Account ID (UUID), defaults to the account set in the provider
+- `workspace_id` (String) Workspace ID (UUID), defaults to the workspace set in the provider
+
+### Read-Only
+
+- `actions` (Attributes List) List of actions to perform when the automation is triggered (see [below for nested schema](#nestedatt--actions))
+- `actions_on_resolve` (Attributes List) List of actions to perform when the automation is triggered (see [below for nested schema](#nestedatt--actions_on_resolve))
+- `actions_on_trigger` (Attributes List) List of actions to perform when the automation is triggered (see [below for nested schema](#nestedatt--actions_on_trigger))
+- `created` (String) Timestamp of when the resource was created (RFC3339)
+- `description` (String) Description of the automation
+- `enabled` (Boolean) Whether the automation is enabled
+- `name` (String) Name of the automation
+- `trigger` (Attributes) The criteria for which events this Automation covers and how it will respond (see [below for nested schema](#nestedatt--trigger))
+- `updated` (String) Timestamp of when the resource was updated (RFC3339)
+
+
+### Nested Schema for `actions`
+
+Read-Only:
+
+- `automation_id` (String) (Automation) ID of the automation to apply this action to
+- `block_document_id` (String) (Webhook / Notification) ID of the block to use
+- `body` (String) (Notification) Body of the notification
+- `deployment_id` (String) (Deployment) ID of the deployment to apply this action to
+- `job_variables` (String) (Deployment) (JSON) Job variables to pass to the created flow run. Use `jsonencode()`.
+- `message` (String) (Flow Run State Change) Message to associate with the state change
+- `name` (String) (Flow Run State Change) Name of the state to change the flow run to
+- `parameters` (String) (Deployment) (JSON) Parameters to pass to the deployment. Use `jsonencode()`.
+- `payload` (String) (Webhook) Payload to send when calling the webhook
+- `source` (String) (Deployment / Work Pool / Work Queue / Automation) Whether this action applies to a specific selected resource or to a specific resource by ID - 'selected' or 'inferred'
+- `state` (String) (Flow Run State Change) Type of state to change the flow run to
+- `subject` (String) (Notification) Subject of the notification
+- `type` (String) The type of action to perform
+- `work_pool_id` (String) (Work Pool) ID of the work pool to apply this action to
+- `work_queue_id` (String) (Work Queue) ID of the work queue to apply this action to
+
+
+
+### Nested Schema for `actions_on_resolve`
+
+Read-Only:
+
+- `automation_id` (String) (Automation) ID of the automation to apply this action to
+- `block_document_id` (String) (Webhook / Notification) ID of the block to use
+- `body` (String) (Notification) Body of the notification
+- `deployment_id` (String) (Deployment) ID of the deployment to apply this action to
+- `job_variables` (String) (Deployment) (JSON) Job variables to pass to the created flow run. Use `jsonencode()`.
+- `message` (String) (Flow Run State Change) Message to associate with the state change
+- `name` (String) (Flow Run State Change) Name of the state to change the flow run to
+- `parameters` (String) (Deployment) (JSON) Parameters to pass to the deployment. Use `jsonencode()`.
+- `payload` (String) (Webhook) Payload to send when calling the webhook
+- `source` (String) (Deployment / Work Pool / Work Queue / Automation) Whether this action applies to a specific selected resource or to a specific resource by ID - 'selected' or 'inferred'
+- `state` (String) (Flow Run State Change) Type of state to change the flow run to
+- `subject` (String) (Notification) Subject of the notification
+- `type` (String) The type of action to perform
+- `work_pool_id` (String) (Work Pool) ID of the work pool to apply this action to
+- `work_queue_id` (String) (Work Queue) ID of the work queue to apply this action to
+
+
+
+### Nested Schema for `actions_on_trigger`
+
+Read-Only:
+
+- `automation_id` (String) (Automation) ID of the automation to apply this action to
+- `block_document_id` (String) (Webhook / Notification) ID of the block to use
+- `body` (String) (Notification) Body of the notification
+- `deployment_id` (String) (Deployment) ID of the deployment to apply this action to
+- `job_variables` (String) (Deployment) (JSON) Job variables to pass to the created flow run. Use `jsonencode()`.
+- `message` (String) (Flow Run State Change) Message to associate with the state change
+- `name` (String) (Flow Run State Change) Name of the state to change the flow run to
+- `parameters` (String) (Deployment) (JSON) Parameters to pass to the deployment. Use `jsonencode()`.
+- `payload` (String) (Webhook) Payload to send when calling the webhook
+- `source` (String) (Deployment / Work Pool / Work Queue / Automation) Whether this action applies to a specific selected resource or to a specific resource by ID - 'selected' or 'inferred'
+- `state` (String) (Flow Run State Change) Type of state to change the flow run to
+- `subject` (String) (Notification) Subject of the notification
+- `type` (String) The type of action to perform
+- `work_pool_id` (String) (Work Pool) ID of the work pool to apply this action to
+- `work_queue_id` (String) (Work Queue) ID of the work queue to apply this action to
+
+
+
+### Nested Schema for `trigger`
+
+Read-Only:
+
+- `compound` (Attributes) A composite trigger that requires some number of triggers to have fired within the given time period (see [below for nested schema](#nestedatt--trigger--compound))
+- `event` (Attributes) A trigger that fires based on the presence or absence of events within a given period of time (see [below for nested schema](#nestedatt--trigger--event))
+- `metric` (Attributes) A trigger that fires based on the results of a metric query (see [below for nested schema](#nestedatt--trigger--metric))
+- `sequence` (Attributes) A composite trigger that requires triggers to fire in a specific order (see [below for nested schema](#nestedatt--trigger--sequence))
+
+
+### Nested Schema for `trigger.compound`
+
+Read-Only:
+
+- `require` (Dynamic) How many triggers must fire ('any', 'all', or a number)
+- `triggers` (Attributes List) The ordered list of triggers that must fire in sequence (see [below for nested schema](#nestedatt--trigger--compound--triggers))
+- `within` (Number) The time period in seconds over which the events must occur
+
+
+### Nested Schema for `trigger.compound.triggers`
+
+Read-Only:
+
+- `event` (Attributes) A trigger that fires based on the presence or absence of events within a given period of time (see [below for nested schema](#nestedatt--trigger--compound--triggers--event))
+- `metric` (Attributes) A trigger that fires based on the results of a metric query (see [below for nested schema](#nestedatt--trigger--compound--triggers--metric))
+
+
+### Nested Schema for `trigger.compound.triggers.event`
+
+Read-Only:
+
+- `after` (List of String) The event(s) which must first been seen to fire this trigger. If empty, then fire this trigger immediately
+- `expect` (List of String) The event(s) this trigger is expecting to see. If empty, this trigger will match any event
+- `for_each` (List of String) Evaluate the trigger separately for each distinct value of these labels on the resource
+- `match` (String) (JSON) Resource specification labels which this trigger will match. Use `jsonencode()`.
+- `match_related` (String) (JSON) Resource specification labels for related resources which this trigger will match. Use `jsonencode()`.
+- `posture` (String) The posture of this trigger, either Reactive or Proactive
+- `threshold` (Number) The number of events required for this trigger to fire (Reactive) or expected (Proactive)
+- `within` (Number) The time period in seconds over which the events must occur
+
+
+
+### Nested Schema for `trigger.compound.triggers.metric`
+
+Read-Only:
+
+- `match` (String) (JSON) Resource specification labels which this trigger will match. Use `jsonencode()`.
+- `match_related` (String) (JSON) Resource specification labels for related resources which this trigger will match. Use `jsonencode()`.
+- `metric` (Attributes) (see [below for nested schema](#nestedatt--trigger--compound--triggers--metric--metric))
+
+
+### Nested Schema for `trigger.compound.triggers.metric.metric`
+
+Read-Only:
+
+- `firing_for` (Number) The duration (seconds) for which the metric query must breach OR resolve continuously before the state is updated and actions are triggered.
+- `name` (String) The name of the metric to query
+- `operator` (String) The comparative operator used to evaluate the query result against the threshold value
+- `range` (Number) The lookback duration (seconds) for a metric query. This duration is used to determine the time range over which the query will be executed.
+- `threshold` (Number) The threshold value against which we'll compare the query results
+
+
+
+
+
+
+### Nested Schema for `trigger.event`
+
+Read-Only:
+
+- `after` (List of String) The event(s) which must first been seen to fire this trigger. If empty, then fire this trigger immediately
+- `expect` (List of String) The event(s) this trigger is expecting to see. If empty, this trigger will match any event
+- `for_each` (List of String) Evaluate the trigger separately for each distinct value of these labels on the resource
+- `match` (String) (JSON) Resource specification labels which this trigger will match. Use `jsonencode()`.
+- `match_related` (String) (JSON) Resource specification labels for related resources which this trigger will match. Use `jsonencode()`.
+- `posture` (String) The posture of this trigger, either Reactive or Proactive
+- `threshold` (Number) The number of events required for this trigger to fire (Reactive) or expected (Proactive)
+- `within` (Number) The time period in seconds over which the events must occur
+
+
+
+### Nested Schema for `trigger.metric`
+
+Read-Only:
+
+- `match` (String) (JSON) Resource specification labels which this trigger will match. Use `jsonencode()`.
+- `match_related` (String) (JSON) Resource specification labels for related resources which this trigger will match. Use `jsonencode()`.
+- `metric` (Attributes) (see [below for nested schema](#nestedatt--trigger--metric--metric))
+
+
+### Nested Schema for `trigger.metric.metric`
+
+Read-Only:
+
+- `firing_for` (Number) The duration (seconds) for which the metric query must breach OR resolve continuously before the state is updated and actions are triggered.
+- `name` (String) The name of the metric to query
+- `operator` (String) The comparative operator used to evaluate the query result against the threshold value
+- `range` (Number) The lookback duration (seconds) for a metric query. This duration is used to determine the time range over which the query will be executed.
+- `threshold` (Number) The threshold value against which we'll compare the query results
+
+
+
+
+### Nested Schema for `trigger.sequence`
+
+Read-Only:
+
+- `triggers` (Attributes List) The ordered list of triggers that must fire in sequence (see [below for nested schema](#nestedatt--trigger--sequence--triggers))
+- `within` (Number) The time period in seconds over which the events must occur
+
+
+### Nested Schema for `trigger.sequence.triggers`
+
+Read-Only:
+
+- `event` (Attributes) A trigger that fires based on the presence or absence of events within a given period of time (see [below for nested schema](#nestedatt--trigger--sequence--triggers--event))
+- `metric` (Attributes) A trigger that fires based on the results of a metric query (see [below for nested schema](#nestedatt--trigger--sequence--triggers--metric))
+
+
+### Nested Schema for `trigger.sequence.triggers.event`
+
+Read-Only:
+
+- `after` (List of String) The event(s) which must first been seen to fire this trigger. If empty, then fire this trigger immediately
+- `expect` (List of String) The event(s) this trigger is expecting to see. If empty, this trigger will match any event
+- `for_each` (List of String) Evaluate the trigger separately for each distinct value of these labels on the resource
+- `match` (String) (JSON) Resource specification labels which this trigger will match. Use `jsonencode()`.
+- `match_related` (String) (JSON) Resource specification labels for related resources which this trigger will match. Use `jsonencode()`.
+- `posture` (String) The posture of this trigger, either Reactive or Proactive
+- `threshold` (Number) The number of events required for this trigger to fire (Reactive) or expected (Proactive)
+- `within` (Number) The time period in seconds over which the events must occur
+
+
+
+### Nested Schema for `trigger.sequence.triggers.metric`
+
+Read-Only:
+
+- `match` (String) (JSON) Resource specification labels which this trigger will match. Use `jsonencode()`.
+- `match_related` (String) (JSON) Resource specification labels for related resources which this trigger will match. Use `jsonencode()`.
+- `metric` (Attributes) (see [below for nested schema](#nestedatt--trigger--sequence--triggers--metric--metric))
+
+
+### Nested Schema for `trigger.sequence.triggers.metric.metric`
+
+Read-Only:
+
+- `firing_for` (Number) The duration (seconds) for which the metric query must breach OR resolve continuously before the state is updated and actions are triggered.
+- `name` (String) The name of the metric to query
+- `operator` (String) The comparative operator used to evaluate the query result against the threshold value
+- `range` (Number) The lookback duration (seconds) for a metric query. This duration is used to determine the time range over which the query will be executed.
+- `threshold` (Number) The threshold value against which we'll compare the query results
diff --git a/examples/data-sources/prefect_automation/data-source.tf b/examples/data-sources/prefect_automation/data-source.tf
new file mode 100644
index 00000000..21f9ccdc
--- /dev/null
+++ b/examples/data-sources/prefect_automation/data-source.tf
@@ -0,0 +1,3 @@
+data "prefect_automation" "test" {
+ id = "7759cb06-ea19-48fc-b277-61db8bdb3de9"
+}
diff --git a/internal/provider/datasources/automation_datasource.go b/internal/provider/datasources/automation_datasource.go
new file mode 100644
index 00000000..f8b124fc
--- /dev/null
+++ b/internal/provider/datasources/automation_datasource.go
@@ -0,0 +1,308 @@
+package datasources
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/prefecthq/terraform-provider-prefect/internal/api"
+ "github.com/prefecthq/terraform-provider-prefect/internal/provider/customtypes"
+ "github.com/prefecthq/terraform-provider-prefect/internal/provider/helpers"
+ "github.com/prefecthq/terraform-provider-prefect/internal/utils"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &AutomationDataSource{}
+ _ datasource.DataSourceWithConfigure = &AutomationDataSource{}
+)
+
+type AutomationDataSource struct {
+ client api.PrefectClient
+}
+
+// NewAutomationDataSource returns a new AutomationDataSource.
+//
+//nolint:ireturn // required by Terraform API
+func NewAutomationDataSource() datasource.DataSource {
+ return &AutomationDataSource{}
+}
+
+// Metadata returns the data source type name.
+func (d *AutomationDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_automation"
+}
+
+// Schema defines the schema for the data source.
+func (d *AutomationDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Get information about an existing Automation by its ID",
+ Attributes: AutomationSchema(),
+ }
+}
+
+// Configure initializes runtime state for the data source.
+func (d *AutomationDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(api.PrefectClient)
+ if !ok {
+ resp.Diagnostics.Append(helpers.ConfigureTypeErrorDiagnostic("data source", req.ProviderData))
+
+ return
+ }
+
+ d.client = client
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (d *AutomationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var model AutomationDataSourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ client, err := d.client.Automations(model.AccountID.ValueUUID(), model.WorkspaceID.ValueUUID())
+ if err != nil {
+ resp.Diagnostics.Append(helpers.CreateClientErrorDiagnostic("Automation", err))
+
+ return
+ }
+
+ automationID, err := uuid.Parse(model.ID.ValueString())
+ if err != nil {
+ resp.Diagnostics.Append(helpers.ParseUUIDErrorDiagnostic("Automation", err))
+
+ return
+ }
+
+ automation, err := client.Get(ctx, automationID)
+ if err != nil {
+ resp.Diagnostics.Append(helpers.ResourceClientErrorDiagnostic("Automation", "get", err))
+
+ return
+ }
+
+ resp.Diagnostics.Append(mapAutomationAPIToTerraform(ctx, automation, &model)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &model)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+}
+
+// mapAutomationAPIToTerraform copies an Automation API object => Terraform model.
+// This helper is used when an API response needs to be translated for Terraform state.
+func mapAutomationAPIToTerraform(ctx context.Context, apiAutomation *api.Automation, tfModel *AutomationDataSourceModel) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ // Map base attributes
+ tfModel.ID = customtypes.NewUUIDValue(apiAutomation.ID)
+ tfModel.Created = customtypes.NewTimestampPointerValue(apiAutomation.Created)
+ tfModel.Updated = customtypes.NewTimestampPointerValue(apiAutomation.Updated)
+ tfModel.Name = types.StringValue(apiAutomation.Name)
+ tfModel.Description = types.StringValue(apiAutomation.Description)
+ tfModel.Enabled = types.BoolValue(apiAutomation.Enabled)
+
+ // Map actions
+ actions, diagnostics := mapActionsAPIToTerraform(apiAutomation.Actions)
+ diags.Append(diagnostics...)
+ actionsOnTrigger, diagnostics := mapActionsAPIToTerraform(apiAutomation.ActionsOnTrigger)
+ diags.Append(diagnostics...)
+ actionsOnResolve, diagnostics := mapActionsAPIToTerraform(apiAutomation.ActionsOnResolve)
+ diags.Append(diagnostics...)
+ if diags.HasError() {
+ return diags
+ }
+ tfModel.Actions = actions
+ tfModel.ActionsOnTrigger = actionsOnTrigger
+ tfModel.ActionsOnResolve = actionsOnResolve
+
+ // NOTE: .Trigger has to a pointer here, as the user won't be expected to set a
+ // `trigger` attribute on the dataource - we only expect them to provide an
+ // Automation ID to query. Therefore, we'll need to initialize Trigger here,
+ // so that we can set the corresponding, nested attributes from the API call.
+ tfModel.Trigger = &TriggerModel{}
+
+ // Map trigger
+ switch apiAutomation.Trigger.Type {
+ case utils.TriggerTypeEvent, utils.TriggerTypeMetric:
+ diags.Append(mapTriggerAPIToTerraform(ctx, &apiAutomation.Trigger, &tfModel.Trigger.ResourceTriggerModel)...)
+
+ case utils.TriggerTypeCompound, utils.TriggerTypeSequence:
+
+ // Iterate through API's composite triggers, map them to a Terraform model.
+ compositeTriggers := make([]ResourceTriggerModel, 0)
+ for i := range apiAutomation.Trigger.Triggers {
+ apiTrigger := apiAutomation.Trigger.Triggers[i]
+ resourceTriggerModel := ResourceTriggerModel{}
+ diags.Append(mapTriggerAPIToTerraform(ctx, &apiTrigger, &resourceTriggerModel)...)
+ compositeTriggers = append(compositeTriggers, resourceTriggerModel)
+ }
+
+ within := types.Float64PointerValue(apiAutomation.Trigger.Within)
+
+ if apiAutomation.Trigger.Type == utils.TriggerTypeCompound {
+ // NOTE: we'll carry forward whatever .Require value that
+ // exists in State, so we don't need to re-set it here.
+ tfModel.Trigger.Compound = &CompoundTriggerAttributesModel{
+ Within: within,
+ Triggers: compositeTriggers,
+ }
+ }
+
+ if apiAutomation.Trigger.Type == utils.TriggerTypeSequence {
+ tfModel.Trigger.Sequence = &SequenceTriggerAttributesModel{
+ Within: within,
+ Triggers: compositeTriggers,
+ }
+ }
+ default:
+ diags.AddError("Invalid Trigger Type", fmt.Sprintf("Invalid trigger type: %s", apiAutomation.Trigger.Type))
+ }
+
+ return diags
+}
+
+// mapTriggerAPIToTerraform maps an `event` or `metric` trigger
+// from an Automation API object => Terraform model.
+// We map these separately, so we can re-use this helper for `compound` and `sequence` triggers.
+func mapTriggerAPIToTerraform(ctx context.Context, apiTrigger *api.Trigger, tfTriggerModel *ResourceTriggerModel) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ // Parse Match and MatchRelated (JSON) regardless of type,
+ // as they are common on Resource Trigger schemas.
+ matchByteSlice, err := json.Marshal(apiTrigger.Match)
+ if err != nil {
+ diags.Append(helpers.SerializeDataErrorDiagnostic("match", "Automation trigger match", err))
+
+ return diags
+ }
+ matchRelatedByteSlice, err := json.Marshal(apiTrigger.MatchRelated)
+ if err != nil {
+ diags.Append(helpers.SerializeDataErrorDiagnostic("match_related", "Automation trigger match related", err))
+
+ return diags
+ }
+
+ switch apiTrigger.Type {
+ case utils.TriggerTypeEvent:
+ tfTriggerModel.Event = &EventTriggerModel{
+ Posture: types.StringPointerValue(apiTrigger.Posture),
+ Threshold: types.Int64PointerValue(apiTrigger.Threshold),
+ Within: types.Float64PointerValue(apiTrigger.Within),
+ }
+
+ // Set Match and MatchRelated, which we parsed above.
+ tfTriggerModel.Event.Match = jsontypes.NewNormalizedValue(string(matchByteSlice))
+ tfTriggerModel.Event.MatchRelated = jsontypes.NewNormalizedValue(string(matchRelatedByteSlice))
+
+ // Parse and set After, Expect, and ForEach (lists)
+ after, diagnostics := types.ListValueFrom(ctx, types.StringType, apiTrigger.After)
+ diags.Append(diagnostics...)
+ expect, diagnostics := types.ListValueFrom(ctx, types.StringType, apiTrigger.Expect)
+ diags.Append(diagnostics...)
+ forEach, diagnostics := types.ListValueFrom(ctx, types.StringType, apiTrigger.ForEach)
+ diags.Append(diagnostics...)
+
+ if diags.HasError() {
+ return diags
+ }
+
+ tfTriggerModel.Event.After = after
+ tfTriggerModel.Event.Expect = expect
+ tfTriggerModel.Event.ForEach = forEach
+
+ case utils.TriggerTypeMetric:
+ tfTriggerModel.Metric = &MetricTriggerModel{
+ Metric: MetricQueryModel{
+ Name: types.StringValue(apiTrigger.Metric.Name),
+ Threshold: types.Float64Value(apiTrigger.Metric.Threshold),
+ Operator: types.StringValue(apiTrigger.Metric.Operator),
+ Range: types.Float64Value(apiTrigger.Metric.Range),
+ FiringFor: types.Float64Value(apiTrigger.Metric.FiringFor),
+ },
+ }
+
+ // Set Match and MatchRelated, which we parsed above.
+ tfTriggerModel.Metric.Match = jsontypes.NewNormalizedValue(string(matchByteSlice))
+ tfTriggerModel.Metric.MatchRelated = jsontypes.NewNormalizedValue(string(matchRelatedByteSlice))
+
+ default:
+ diags.AddError("Invalid Trigger Type", fmt.Sprintf("Invalid trigger type: %s", apiTrigger.Type))
+ }
+
+ return diags
+}
+
+// mapActionsAPIToTerraform maps an Automation API Actions payload => Terraform model.
+// This helper is used when an API response needs to be translated for Terraform state.
+// NOTE: we are re-constructing the response list every time (rather than updating member items in-place),
+// because there is no guarantee that the API returns the list in the same order as the Terraform model.
+func mapActionsAPIToTerraform(apiActions []api.Action) ([]ActionModel, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ tfActionsModel := make([]ActionModel, 0)
+
+ for i := range apiActions {
+ action := apiActions[i]
+
+ actionModel := ActionModel{}
+ actionModel.Type = types.StringValue(action.Type)
+ actionModel.Source = types.StringPointerValue(action.Source)
+ actionModel.AutomationID = customtypes.NewUUIDPointerValue(action.AutomationID)
+ actionModel.BlockDocumentID = customtypes.NewUUIDPointerValue(action.BlockDocumentID)
+ actionModel.DeploymentID = customtypes.NewUUIDPointerValue(action.DeploymentID)
+ actionModel.WorkPoolID = customtypes.NewUUIDPointerValue(action.WorkPoolID)
+ actionModel.WorkQueueID = customtypes.NewUUIDPointerValue(action.WorkQueueID)
+ actionModel.Subject = types.StringPointerValue(action.Subject)
+ actionModel.Body = types.StringPointerValue(action.Body)
+ actionModel.Payload = types.StringPointerValue(action.Payload)
+ actionModel.Name = types.StringPointerValue(action.Name)
+ actionModel.State = types.StringPointerValue(action.State)
+ actionModel.Message = types.StringPointerValue(action.Message)
+
+ // Only set parameters and job variables if they are set in the API.
+ // Otherwise, the string `"null"` is set to the Terraform model, which will
+ // create an inconsistent result error if no value is set in HCL.
+ if action.Parameters != nil {
+ byteSlice, err := json.Marshal(action.Parameters)
+ if err != nil {
+ diags.Append(helpers.SerializeDataErrorDiagnostic("parameters", "Automation action parameters", err))
+
+ return nil, diags
+ }
+ actionModel.Parameters = jsontypes.NewNormalizedValue(string(byteSlice))
+ } else {
+ actionModel.Parameters = jsontypes.NewNormalizedValue("{}")
+ }
+
+ if action.JobVariables != nil {
+ byteSlice, err := json.Marshal(action.JobVariables)
+ if err != nil {
+ diags.Append(helpers.SerializeDataErrorDiagnostic("job_variables", "Automation action job variables", err))
+
+ return nil, diags
+ }
+ actionModel.JobVariables = jsontypes.NewNormalizedValue(string(byteSlice))
+ } else {
+ actionModel.JobVariables = jsontypes.NewNormalizedValue("{}")
+ }
+
+ tfActionsModel = append(tfActionsModel, actionModel)
+ }
+
+ return tfActionsModel, diags
+}
diff --git a/internal/provider/datasources/automation_model.go b/internal/provider/datasources/automation_model.go
new file mode 100644
index 00000000..4a34bd32
--- /dev/null
+++ b/internal/provider/datasources/automation_model.go
@@ -0,0 +1,119 @@
+package datasources
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/prefecthq/terraform-provider-prefect/internal/provider/customtypes"
+)
+
+// AutomationResourceModel defines the Terraform resource model.
+type AutomationDataSourceModel struct {
+ ID customtypes.UUIDValue `tfsdk:"id"`
+ Created customtypes.TimestampValue `tfsdk:"created"`
+ Updated customtypes.TimestampValue `tfsdk:"updated"`
+ AccountID customtypes.UUIDValue `tfsdk:"account_id"`
+ WorkspaceID customtypes.UUIDValue `tfsdk:"workspace_id"`
+
+ Name types.String `tfsdk:"name"`
+ Description types.String `tfsdk:"description"`
+ Enabled types.Bool `tfsdk:"enabled"`
+ Trigger *TriggerModel `tfsdk:"trigger"`
+
+ Actions []ActionModel `tfsdk:"actions"`
+ ActionsOnTrigger []ActionModel `tfsdk:"actions_on_trigger"`
+ ActionsOnResolve []ActionModel `tfsdk:"actions_on_resolve"`
+}
+
+// ResourceTriggerModel comprises the event and metric trigger models.
+type ResourceTriggerModel struct {
+ Event *EventTriggerModel `tfsdk:"event"`
+ Metric *MetricTriggerModel `tfsdk:"metric"`
+}
+
+// TriggerModel comprises all trigger types, including compound and sequence.
+type TriggerModel struct {
+ ResourceTriggerModel
+ Compound *CompoundTriggerAttributesModel `tfsdk:"compound"`
+ Sequence *SequenceTriggerAttributesModel `tfsdk:"sequence"`
+}
+
+// EventTriggerModel represents an event-based trigger.
+type EventTriggerModel struct {
+ Posture types.String `tfsdk:"posture"`
+ Match jsontypes.Normalized `tfsdk:"match"`
+ MatchRelated jsontypes.Normalized `tfsdk:"match_related"`
+ After types.List `tfsdk:"after"`
+ Expect types.List `tfsdk:"expect"`
+ ForEach types.List `tfsdk:"for_each"`
+ Threshold types.Int64 `tfsdk:"threshold"`
+ Within types.Float64 `tfsdk:"within"`
+}
+
+// MetricTriggerModel represents a metric-based trigger.
+type MetricTriggerModel struct {
+ Match jsontypes.Normalized `tfsdk:"match"`
+ MatchRelated jsontypes.Normalized `tfsdk:"match_related"`
+ Metric MetricQueryModel `tfsdk:"metric"`
+}
+
+// MetricQueryModel represents the metric query configuration.
+type MetricQueryModel struct {
+ Name types.String `tfsdk:"name"`
+ Operator types.String `tfsdk:"operator"`
+ Threshold types.Float64 `tfsdk:"threshold"`
+ Range types.Float64 `tfsdk:"range"`
+ FiringFor types.Float64 `tfsdk:"firing_for"`
+}
+
+// SequenceTriggerAttributesModel represents the attributes of a sequence trigger.
+type SequenceTriggerAttributesModel struct {
+ Triggers []ResourceTriggerModel `tfsdk:"triggers"`
+ Within types.Float64 `tfsdk:"within"`
+}
+
+// CompoundTriggerAttributesModel represents the attributes of a compound trigger.
+type CompoundTriggerAttributesModel struct {
+ Triggers []ResourceTriggerModel `tfsdk:"triggers"`
+ Within types.Float64 `tfsdk:"within"`
+ Require types.Dynamic `tfsdk:"require"`
+}
+
+// ActionModel represents a single action in an automation.
+type ActionModel struct {
+ // On all actions
+ Type types.String `tfsdk:"type"`
+
+ // On Deployment, Work Pool, Work Queue, and Automation actions
+ Source types.String `tfsdk:"source"`
+
+ // On Automation actions
+ AutomationID customtypes.UUIDValue `tfsdk:"automation_id"`
+
+ // On Webhook and Notification actions
+ BlockDocumentID customtypes.UUIDValue `tfsdk:"block_document_id"`
+
+ // On Deployment actions
+ DeploymentID customtypes.UUIDValue `tfsdk:"deployment_id"`
+
+ // On Work Pool actions
+ WorkPoolID customtypes.UUIDValue `tfsdk:"work_pool_id"`
+
+ // On Work Queue actions
+ WorkQueueID customtypes.UUIDValue `tfsdk:"work_queue_id"`
+
+ // On Run Deployment action
+ Parameters jsontypes.Normalized `tfsdk:"parameters"`
+ JobVariables jsontypes.Normalized `tfsdk:"job_variables"`
+
+ // On Send Notification action
+ Subject types.String `tfsdk:"subject"`
+ Body types.String `tfsdk:"body"`
+
+ // On Call Webhook action
+ Payload types.String `tfsdk:"payload"`
+
+ // On Flow Run State Change action
+ Name types.String `tfsdk:"name"`
+ State types.String `tfsdk:"state"`
+ Message types.String `tfsdk:"message"`
+}
diff --git a/internal/provider/datasources/automation_schema.go b/internal/provider/datasources/automation_schema.go
new file mode 100644
index 00000000..15ff0b14
--- /dev/null
+++ b/internal/provider/datasources/automation_schema.go
@@ -0,0 +1,296 @@
+package datasources
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/prefecthq/terraform-provider-prefect/internal/provider/customtypes"
+ "github.com/prefecthq/terraform-provider-prefect/internal/utils"
+)
+
+// AutomationSchema returns the total schema for an Automation.
+// This includes all of the root-level attributes for an Automation,
+// as well as the Trigger and Actions schemas.
+func AutomationSchema() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Required: true,
+ CustomType: customtypes.UUIDType{},
+ Description: "Automation ID (UUID)",
+ },
+ "created": schema.StringAttribute{
+ Computed: true,
+ CustomType: customtypes.TimestampType{},
+ Description: "Timestamp of when the resource was created (RFC3339)",
+ },
+ "updated": schema.StringAttribute{
+ Computed: true,
+ CustomType: customtypes.TimestampType{},
+ Description: "Timestamp of when the resource was updated (RFC3339)",
+ },
+ "name": schema.StringAttribute{
+ Description: "Name of the automation",
+ Computed: true,
+ },
+ "description": schema.StringAttribute{
+ Description: "Description of the automation",
+ Computed: true,
+ },
+ "enabled": schema.BoolAttribute{
+ Description: "Whether the automation is enabled",
+ Computed: true,
+ },
+ "account_id": schema.StringAttribute{
+ Optional: true,
+ CustomType: customtypes.UUIDType{},
+ Description: "Account ID (UUID), defaults to the account set in the provider",
+ },
+ "workspace_id": schema.StringAttribute{
+ Optional: true,
+ CustomType: customtypes.UUIDType{},
+ Description: "Workspace ID (UUID), defaults to the workspace set in the provider",
+ },
+ "trigger": TriggerSchema(),
+ "actions": ActionsSchema(),
+ "actions_on_trigger": ActionsSchema(),
+ "actions_on_resolve": ActionsSchema(),
+ }
+}
+
+// TriggerSchema returns the combined resource schema for an Automation Trigger.
+// This combines Resource Triggers and Composite Triggers.
+// We construct the TriggerSchema this way (and not in a single schema from the start)
+// because Composite Triggers are higher-order Triggers that utilize Resource Triggers.
+func TriggerSchema() schema.SingleNestedAttribute {
+ // (1) We start with the Resource Trigger Schema Attributes
+ combinedAttributes := ResourceTriggerSchemaAttributes()
+
+ // (2) Here we add Composite Triggers to the schema
+ combinedAttributes["compound"] = schema.SingleNestedAttribute{
+ Computed: true,
+ Description: "A composite trigger that requires some number of triggers to have fired within the given time period",
+ Attributes: func() map[string]schema.Attribute {
+ attrs := CompositeTriggerSchemaAttributes()
+ attrs["require"] = schema.DynamicAttribute{
+ Computed: true,
+ Description: "How many triggers must fire ('any', 'all', or a number)",
+ }
+
+ return attrs
+ }(),
+ }
+ combinedAttributes["sequence"] = schema.SingleNestedAttribute{
+ Computed: true,
+ Description: "A composite trigger that requires triggers to fire in a specific order",
+ Attributes: CompositeTriggerSchemaAttributes(),
+ }
+
+ // (3) We return the combined Triggers schema
+ return schema.SingleNestedAttribute{
+ Computed: true,
+ Description: "The criteria for which events this Automation covers and how it will respond",
+ Attributes: combinedAttributes,
+ }
+}
+
+// ResourceTriggerSchemaAttributes returns the attributes for a Resource Trigger.
+// A Resource Trigger is an `event` or `metric` trigger.
+func ResourceTriggerSchemaAttributes() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "event": schema.SingleNestedAttribute{
+ Computed: true,
+ Description: "A trigger that fires based on the presence or absence of events within a given period of time",
+ Attributes: map[string]schema.Attribute{
+ "posture": schema.StringAttribute{
+ Computed: true,
+ Description: "The posture of this trigger, either Reactive or Proactive",
+ Validators: []validator.String{
+ stringvalidator.OneOf("Reactive", "Proactive"),
+ },
+ },
+ "match": schema.StringAttribute{
+ Computed: true,
+ Description: "(JSON) Resource specification labels which this trigger will match. Use `jsonencode()`.",
+ CustomType: jsontypes.NormalizedType{},
+ },
+ "match_related": schema.StringAttribute{
+ Computed: true,
+ Description: "(JSON) Resource specification labels for related resources which this trigger will match. Use `jsonencode()`.",
+ CustomType: jsontypes.NormalizedType{},
+ },
+ "after": schema.ListAttribute{
+ Computed: true,
+ Description: "The event(s) which must first been seen to fire this trigger. If empty, then fire this trigger immediately",
+ ElementType: types.StringType,
+ },
+ "expect": schema.ListAttribute{
+ Computed: true,
+ Description: "The event(s) this trigger is expecting to see. If empty, this trigger will match any event",
+ ElementType: types.StringType,
+ },
+ "for_each": schema.ListAttribute{
+ Computed: true,
+ Description: "Evaluate the trigger separately for each distinct value of these labels on the resource",
+ ElementType: types.StringType,
+ },
+ "threshold": schema.Int64Attribute{
+ Computed: true,
+ Description: "The number of events required for this trigger to fire (Reactive) or expected (Proactive)",
+ },
+ "within": schema.Float64Attribute{
+ Computed: true,
+ Description: "The time period in seconds over which the events must occur",
+ },
+ },
+ },
+ "metric": schema.SingleNestedAttribute{
+ Computed: true,
+ Description: "A trigger that fires based on the results of a metric query",
+ Attributes: map[string]schema.Attribute{
+ "match": schema.StringAttribute{
+ Computed: true,
+ Description: "(JSON) Resource specification labels which this trigger will match. Use `jsonencode()`.",
+ CustomType: jsontypes.NormalizedType{},
+ },
+ "match_related": schema.StringAttribute{
+ Computed: true,
+ Description: "(JSON) Resource specification labels for related resources which this trigger will match. Use `jsonencode()`.",
+ CustomType: jsontypes.NormalizedType{},
+ },
+ "metric": schema.SingleNestedAttribute{
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Computed: true,
+ Description: "The name of the metric to query",
+ },
+ "operator": schema.StringAttribute{
+ Computed: true,
+ Description: "The comparative operator used to evaluate the query result against the threshold value",
+ },
+ "threshold": schema.Float64Attribute{
+ Computed: true,
+ Description: "The threshold value against which we'll compare the query results",
+ },
+ "range": schema.Float64Attribute{
+ Computed: true,
+ Description: "The lookback duration (seconds) for a metric query. This duration is used to determine the time range over which the query will be executed.",
+ },
+ "firing_for": schema.Float64Attribute{
+ Computed: true,
+ Description: "The duration (seconds) for which the metric query must breach OR resolve continuously before the state is updated and actions are triggered.",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+// CompositeTriggerSchemaAttributes returns the shared attributes for a Composite Trigger.
+// A Composite Trigger is a `compound` or `sequence` trigger.
+func CompositeTriggerSchemaAttributes() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "triggers": schema.ListNestedAttribute{
+ Computed: true,
+ Description: "The ordered list of triggers that must fire in sequence",
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: ResourceTriggerSchemaAttributes(),
+ },
+ },
+ "within": schema.Float64Attribute{
+ Computed: true,
+ Description: "The time period in seconds over which the events must occur",
+ },
+ }
+}
+
+// ActionsSchema returns the schema for an Automation's Actions.
+// Actions are polymorphic and can have different schemas based on the action type.
+// In the resource schema here, we only make `type` required. The other attributes
+// are needed based on the action type, which we'll validate in the resource layer.
+func ActionsSchema() schema.ListNestedAttribute {
+ return schema.ListNestedAttribute{
+ Description: "List of actions to perform when the automation is triggered",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Computed: true,
+ Description: "The type of action to perform",
+ Validators: []validator.String{
+ stringvalidator.OneOf(utils.AllAutomationActionTypes...),
+ },
+ },
+ "source": schema.StringAttribute{
+ Description: "(Deployment / Work Pool / Work Queue / Automation) Whether this action applies to a specific selected resource or to a specific resource by ID - 'selected' or 'inferred'",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("selected", "inferred"),
+ },
+ },
+ "deployment_id": schema.StringAttribute{
+ Description: "(Deployment) ID of the deployment to apply this action to",
+ Computed: true,
+ CustomType: customtypes.UUIDType{},
+ },
+ "parameters": schema.StringAttribute{
+ Description: "(Deployment) (JSON) Parameters to pass to the deployment. Use `jsonencode()`.",
+ Computed: true,
+ CustomType: jsontypes.NormalizedType{},
+ },
+ "job_variables": schema.StringAttribute{
+ Description: "(Deployment) (JSON) Job variables to pass to the created flow run. Use `jsonencode()`.",
+ Computed: true,
+ CustomType: jsontypes.NormalizedType{},
+ },
+ "name": schema.StringAttribute{
+ Description: "(Flow Run State Change) Name of the state to change the flow run to",
+ Computed: true,
+ },
+ "state": schema.StringAttribute{
+ Description: "(Flow Run State Change) Type of state to change the flow run to",
+ Computed: true,
+ },
+ "message": schema.StringAttribute{
+ Description: "(Flow Run State Change) Message to associate with the state change",
+ Computed: true,
+ },
+ "work_queue_id": schema.StringAttribute{
+ Description: "(Work Queue) ID of the work queue to apply this action to",
+ Computed: true,
+ CustomType: customtypes.UUIDType{},
+ },
+ "block_document_id": schema.StringAttribute{
+ Description: "(Webhook / Notification) ID of the block to use",
+ Computed: true,
+ CustomType: customtypes.UUIDType{},
+ },
+ "subject": schema.StringAttribute{
+ Description: "(Notification) Subject of the notification",
+ Computed: true,
+ },
+ "body": schema.StringAttribute{
+ Description: "(Notification) Body of the notification",
+ Computed: true,
+ },
+ "payload": schema.StringAttribute{
+ Description: "(Webhook) Payload to send when calling the webhook",
+ Computed: true,
+ },
+ "automation_id": schema.StringAttribute{
+ Description: "(Automation) ID of the automation to apply this action to",
+ Computed: true,
+ CustomType: customtypes.UUIDType{},
+ },
+ "work_pool_id": schema.StringAttribute{
+ Description: "(Work Pool) ID of the work pool to apply this action to",
+ Computed: true,
+ CustomType: customtypes.UUIDType{},
+ },
+ },
+ },
+ }
+}
diff --git a/internal/provider/datasources/automation_test.go b/internal/provider/datasources/automation_test.go
new file mode 100644
index 00000000..d0200502
--- /dev/null
+++ b/internal/provider/datasources/automation_test.go
@@ -0,0 +1,376 @@
+package datasources_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/prefecthq/terraform-provider-prefect/internal/provider/helpers"
+ "github.com/prefecthq/terraform-provider-prefect/internal/testutils"
+)
+
+type automationFixtureConfig struct {
+ EphemeralWorkspace string
+ EphemeralWorkspaceResourceName string
+ AutomationResourceName string
+}
+
+func fixtureAccAutomationResourceEventTrigger(cfg automationFixtureConfig) string {
+ tmpl := `
+{{ .EphemeralWorkspace }}
+
+resource "prefect_automation" "{{ .AutomationResourceName }}" {
+ workspace_id = {{ .EphemeralWorkspaceResourceName }}.id
+
+ name = "test-event-automation"
+ description = "description for test-event-automation"
+ enabled = true
+
+ trigger = {
+ event = {
+ posture = "Reactive"
+ match = jsonencode({
+ "prefect.resource.id" : "prefect.flow-run.*"
+ })
+ match_related = jsonencode({
+ "prefect.resource.id" : ["prefect.flow.ce6ec0c9-4b51-483b-a776-43c085b6c4f8"]
+ "prefect.resource.role" : "flow"
+ })
+ after = ["prefect.flow-run.completed"]
+ expect = ["prefect.flow-run.failed"]
+ for_each = ["prefect.resource.id"]
+ threshold = 1
+ within = 60
+ }
+ }
+
+ actions = [
+ {
+ type = "run-deployment"
+ source = "selected"
+ deployment_id = "123e4567-e89b-12d3-a456-426614174000"
+ parameters = jsonencode({
+ param1 = "value1"
+ param2 = "value2"
+ })
+ job_variables = jsonencode({
+ string_var = "value1"
+ int_var = 2
+ bool_var = true
+ })
+ }
+ ]
+}
+
+data "prefect_automation" "{{ .AutomationResourceName }}" {
+ id = prefect_automation.{{ .AutomationResourceName }}.id
+ workspace_id = {{ .EphemeralWorkspaceResourceName }}.id
+}
+`
+
+ return helpers.RenderTemplate(tmpl, cfg)
+}
+
+func fixtureAccAutomationResourceMetricTrigger(cfg automationFixtureConfig) string {
+ tmpl := `
+{{ .EphemeralWorkspace }}
+
+resource "prefect_automation" "{{ .AutomationResourceName }}" {
+ workspace_id = {{ .EphemeralWorkspaceResourceName }}.id
+
+ name = "test-metric-automation"
+ description = "description for test-metric-automation"
+ enabled = true
+
+ trigger = {
+ metric = {
+ match = jsonencode({
+ "prefect.resource.id" = "prefect.flow-run.*"
+ })
+ match_related = jsonencode({
+ "prefect.resource.id" = "prefect.flow-run.*"
+ "prefect.resource.role" = "flow"
+ })
+ metric = {
+ name = "duration"
+ operator = ">="
+ threshold = 0.5
+ range = 30
+ firing_for = 60
+ }
+ }
+ }
+
+ actions = [
+ {
+ type = "change-flow-run-state"
+ state = "FAILED"
+ name = "Failed by automation"
+ message = "Flow run failed"
+ }
+ ]
+}
+
+data "prefect_automation" "{{ .AutomationResourceName }}" {
+ id = prefect_automation.{{ .AutomationResourceName }}.id
+ workspace_id = {{ .EphemeralWorkspaceResourceName }}.id
+}
+`
+
+ return helpers.RenderTemplate(tmpl, cfg)
+}
+
+func fixtureAccAutomationResourceCompoundTrigger(cfg automationFixtureConfig) string {
+ tmpl := `
+{{ .EphemeralWorkspace }}
+
+resource "prefect_automation" "{{ .AutomationResourceName }}" {
+ workspace_id = {{ .EphemeralWorkspaceResourceName }}.id
+
+ name = "test-compound-automation"
+ description = "description for test-compound-automation"
+ enabled = false
+
+ trigger = {
+ compound = {
+ require = "any"
+ within = 302
+ triggers = [
+ {
+ event = {
+ expect = ["prefect.flow-run.Failed"]
+ match = jsonencode({
+ "prefect.resource.id" = "prefect.flow-run.*"
+ })
+ match_related = jsonencode({
+ "prefect.resource.id" = "prefect.flow-run.*"
+ "prefect.resource.role" = "flow"
+ })
+ posture = "Reactive"
+ threshold = 1
+ within = 0
+ }
+ },
+ {
+ event = {
+ expect = ["prefect.flow-run.Completed"]
+ match = jsonencode({
+ "prefect.resource.id" = "prefect.flow-run.*"
+ })
+ match_related = jsonencode({
+ "prefect.resource.id" = "prefect.flow-run.*"
+ "prefect.resource.role" = "flow"
+ })
+ posture = "Reactive"
+ threshold = 1
+ within = 0
+ }
+ }
+ ]
+ }
+ }
+
+ actions = [
+ {
+ type = "run-deployment"
+ source = "inferred"
+ job_variables = jsonencode({
+ var1 = "value1"
+ var2 = "value2"
+ var3 = "value3"
+ })
+ }
+ ]
+}
+
+data "prefect_automation" "{{ .AutomationResourceName }}" {
+ id = prefect_automation.{{ .AutomationResourceName }}.id
+ workspace_id = {{ .EphemeralWorkspaceResourceName }}.id
+}
+`
+
+ return helpers.RenderTemplate(tmpl, cfg)
+}
+
+func fixtureAccAutomationResourceSequenceTrigger(cfg automationFixtureConfig) string {
+ tmpl := `
+{{ .EphemeralWorkspace }}
+
+resource "prefect_automation" "{{ .AutomationResourceName }}" {
+ workspace_id = {{ .EphemeralWorkspaceResourceName }}.id
+
+ name = "test-sequence-automation"
+ description = "description for test-sequence-automation"
+ enabled = true
+
+ trigger = {
+ sequence = {
+ within = 180
+ triggers = [
+ {
+ event = {
+ expect = ["prefect.flow-run.Pending"]
+ match = jsonencode({
+ "prefect.resource.id" = "prefect.flow-run.*"
+ })
+ posture = "Reactive"
+ threshold = 1
+ within = 0
+ }
+ },
+ {
+ event = {
+ expect = ["prefect.flow-run.Running"]
+ match = jsonencode({
+ "prefect.resource.id" = "prefect.flow-run.*"
+ })
+ match_related = jsonencode({
+ "prefect.resource.role" = "flow"
+ "prefect.resource.id" = ["prefect.flow.ce6ec0c9-4b51-483b-a776-43c085b6c4f8"]
+ })
+ posture = "Reactive"
+ threshold = 1
+ within = 0
+ }
+ },
+ {
+ event = {
+ expect = ["prefect.flow-run.Completed"]
+ match = jsonencode({
+ "prefect.resource.id" = "prefect.flow-run.*"
+ })
+ match_related = jsonencode({
+ "prefect.resource.id" = ["prefect.flow-run.*"]
+ "prefect.resource.role" = "flow"
+ })
+ posture = "Reactive"
+ threshold = 1
+ within = 0
+ }
+ }
+ ]
+ }
+ }
+
+ actions = [
+ {
+ type = "send-notification"
+ block_document_id = "123e4567-e89b-12d3-a456-426614174000"
+ subject = "Flow run failed"
+ body = "Flow run failed at this time"
+ }
+ ]
+}
+
+data "prefect_automation" "{{ .AutomationResourceName }}" {
+ id = prefect_automation.{{ .AutomationResourceName }}.id
+ workspace_id = {{ .EphemeralWorkspaceResourceName }}.id
+}
+`
+
+ return helpers.RenderTemplate(tmpl, cfg)
+}
+
+//nolint:paralleltest // we use the resource.ParallelTest helper instead
+func TestAccDatasource_automation(t *testing.T) {
+ eventTriggerAutomationResourceName := testutils.NewRandomPrefixedString()
+ eventTriggerAutomationResourceNameAndPath := fmt.Sprintf("prefect_automation.%s", eventTriggerAutomationResourceName)
+ eventTriggerAutomationDataSourceNameAndPath := fmt.Sprintf("data.prefect_automation.%s", eventTriggerAutomationResourceName)
+
+ metricTriggerAutomationResourceName := testutils.NewRandomPrefixedString()
+ metricTriggerAutomationResourceNameAndPath := fmt.Sprintf("prefect_automation.%s", metricTriggerAutomationResourceName)
+ metricTriggerAutomationDataSourceNameAndPath := fmt.Sprintf("data.prefect_automation.%s", metricTriggerAutomationResourceName)
+
+ compoundTriggerAutomationResourceName := testutils.NewRandomPrefixedString()
+ compoundTriggerAutomationResourceNameAndPath := fmt.Sprintf("prefect_automation.%s", compoundTriggerAutomationResourceName)
+ compoundTriggerAutomationDataSourceNameAndPath := fmt.Sprintf("data.prefect_automation.%s", compoundTriggerAutomationResourceName)
+
+ sequenceTriggerAutomationResourceName := testutils.NewRandomPrefixedString()
+ sequenceTriggerAutomationResourceNameAndPath := fmt.Sprintf("prefect_automation.%s", sequenceTriggerAutomationResourceName)
+ sequenceTriggerAutomationDataSourceNameAndPath := fmt.Sprintf("data.prefect_automation.%s", sequenceTriggerAutomationResourceName)
+
+ ephemeralWorkspace := testutils.NewEphemeralWorkspace()
+
+ resource.ParallelTest(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
+ PreCheck: func() { testutils.AccTestPreCheck(t) },
+ Steps: []resource.TestStep{
+ {
+ Config: fixtureAccAutomationResourceEventTrigger(automationFixtureConfig{
+ EphemeralWorkspace: ephemeralWorkspace.Resource,
+ EphemeralWorkspaceResourceName: testutils.WorkspaceResourceName,
+ AutomationResourceName: eventTriggerAutomationResourceName,
+ }),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttrSet(eventTriggerAutomationDataSourceNameAndPath, "id"),
+ resource.TestCheckResourceAttrPair(eventTriggerAutomationDataSourceNameAndPath, "name", eventTriggerAutomationResourceNameAndPath, "name"),
+ resource.TestCheckResourceAttrPair(eventTriggerAutomationDataSourceNameAndPath, "description", eventTriggerAutomationResourceNameAndPath, "description"),
+ resource.TestCheckResourceAttrPair(eventTriggerAutomationDataSourceNameAndPath, "enabled", eventTriggerAutomationResourceNameAndPath, "enabled"),
+ resource.TestCheckResourceAttrSet(eventTriggerAutomationDataSourceNameAndPath, "trigger.event.posture"),
+ resource.TestCheckNoResourceAttr(eventTriggerAutomationDataSourceNameAndPath, "trigger.compound"),
+ resource.TestCheckNoResourceAttr(eventTriggerAutomationDataSourceNameAndPath, "trigger.metric"),
+ resource.TestCheckNoResourceAttr(eventTriggerAutomationDataSourceNameAndPath, "trigger.sequence"),
+ resource.TestCheckResourceAttr(eventTriggerAutomationDataSourceNameAndPath, "actions.#", "1"),
+ resource.TestCheckResourceAttr(eventTriggerAutomationDataSourceNameAndPath, "actions.0.type", "run-deployment"),
+ ),
+ },
+ {
+ Config: fixtureAccAutomationResourceMetricTrigger(automationFixtureConfig{
+ EphemeralWorkspace: ephemeralWorkspace.Resource,
+ EphemeralWorkspaceResourceName: testutils.WorkspaceResourceName,
+ AutomationResourceName: metricTriggerAutomationResourceName,
+ }),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttrSet(metricTriggerAutomationDataSourceNameAndPath, "id"),
+ resource.TestCheckResourceAttrPair(metricTriggerAutomationDataSourceNameAndPath, "name", metricTriggerAutomationResourceNameAndPath, "name"),
+ resource.TestCheckResourceAttrPair(metricTriggerAutomationDataSourceNameAndPath, "description", metricTriggerAutomationResourceNameAndPath, "description"),
+ resource.TestCheckResourceAttrPair(metricTriggerAutomationDataSourceNameAndPath, "enabled", metricTriggerAutomationResourceNameAndPath, "enabled"),
+ resource.TestCheckResourceAttrSet(metricTriggerAutomationDataSourceNameAndPath, "trigger.metric.metric.name"),
+ resource.TestCheckNoResourceAttr(metricTriggerAutomationDataSourceNameAndPath, "trigger.compound"),
+ resource.TestCheckNoResourceAttr(metricTriggerAutomationDataSourceNameAndPath, "trigger.event"),
+ resource.TestCheckNoResourceAttr(metricTriggerAutomationDataSourceNameAndPath, "trigger.sequence"),
+ resource.TestCheckResourceAttr(metricTriggerAutomationDataSourceNameAndPath, "actions.#", "1"),
+ resource.TestCheckResourceAttr(metricTriggerAutomationDataSourceNameAndPath, "actions.0.type", "change-flow-run-state"),
+ ),
+ },
+ {
+ Config: fixtureAccAutomationResourceCompoundTrigger(automationFixtureConfig{
+ EphemeralWorkspace: ephemeralWorkspace.Resource,
+ EphemeralWorkspaceResourceName: testutils.WorkspaceResourceName,
+ AutomationResourceName: compoundTriggerAutomationResourceName,
+ }),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttrSet(compoundTriggerAutomationDataSourceNameAndPath, "id"),
+ resource.TestCheckResourceAttrPair(compoundTriggerAutomationDataSourceNameAndPath, "name", compoundTriggerAutomationResourceNameAndPath, "name"),
+ resource.TestCheckResourceAttrPair(compoundTriggerAutomationDataSourceNameAndPath, "description", compoundTriggerAutomationResourceNameAndPath, "description"),
+ resource.TestCheckResourceAttrPair(compoundTriggerAutomationDataSourceNameAndPath, "enabled", compoundTriggerAutomationResourceNameAndPath, "enabled"),
+ resource.TestCheckResourceAttr(compoundTriggerAutomationDataSourceNameAndPath, "trigger.compound.triggers.#", "2"),
+ resource.TestCheckNoResourceAttr(compoundTriggerAutomationDataSourceNameAndPath, "trigger.event"),
+ resource.TestCheckNoResourceAttr(compoundTriggerAutomationDataSourceNameAndPath, "trigger.metric"),
+ resource.TestCheckNoResourceAttr(compoundTriggerAutomationDataSourceNameAndPath, "trigger.sequence"),
+ resource.TestCheckResourceAttr(compoundTriggerAutomationDataSourceNameAndPath, "actions.#", "1"),
+ resource.TestCheckResourceAttr(compoundTriggerAutomationDataSourceNameAndPath, "actions.0.type", "run-deployment"),
+ ),
+ },
+ {
+ Config: fixtureAccAutomationResourceSequenceTrigger(automationFixtureConfig{
+ EphemeralWorkspace: ephemeralWorkspace.Resource,
+ EphemeralWorkspaceResourceName: testutils.WorkspaceResourceName,
+ AutomationResourceName: sequenceTriggerAutomationResourceName,
+ }),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttrSet(sequenceTriggerAutomationDataSourceNameAndPath, "id"),
+ resource.TestCheckResourceAttrPair(sequenceTriggerAutomationDataSourceNameAndPath, "name", sequenceTriggerAutomationResourceNameAndPath, "name"),
+ resource.TestCheckResourceAttrPair(sequenceTriggerAutomationDataSourceNameAndPath, "description", sequenceTriggerAutomationResourceNameAndPath, "description"),
+ resource.TestCheckResourceAttrPair(sequenceTriggerAutomationDataSourceNameAndPath, "enabled", sequenceTriggerAutomationResourceNameAndPath, "enabled"),
+ resource.TestCheckResourceAttr(sequenceTriggerAutomationDataSourceNameAndPath, "trigger.sequence.triggers.#", "3"),
+ resource.TestCheckNoResourceAttr(sequenceTriggerAutomationDataSourceNameAndPath, "trigger.compound"),
+ resource.TestCheckNoResourceAttr(sequenceTriggerAutomationDataSourceNameAndPath, "trigger.event"),
+ resource.TestCheckNoResourceAttr(sequenceTriggerAutomationDataSourceNameAndPath, "trigger.metric"),
+ resource.TestCheckResourceAttr(sequenceTriggerAutomationDataSourceNameAndPath, "actions.#", "1"),
+ resource.TestCheckResourceAttr(sequenceTriggerAutomationDataSourceNameAndPath, "actions.0.type", "send-notification"),
+ ),
+ },
+ },
+ })
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index ab261d4b..714b640b 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -230,6 +230,7 @@ func (p *PrefectProvider) DataSources(_ context.Context) []func() datasource.Dat
datasources.NewAccountMemberDataSource,
datasources.NewAccountMembersDataSource,
datasources.NewAccountRoleDataSource,
+ datasources.NewAutomationDataSource,
datasources.NewBlockDataSource,
datasources.NewServiceAccountDataSource,
datasources.NewTeamDataSource,
@@ -247,18 +248,18 @@ func (p *PrefectProvider) DataSources(_ context.Context) []func() datasource.Dat
func (p *PrefectProvider) Resources(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
resources.NewAccountResource,
- resources.NewFlowResource,
- resources.NewDeploymentResource,
+ resources.NewAutomationResource,
+ resources.NewBlockAccessResource,
+ resources.NewBlockResource,
resources.NewDeploymentAccessResource,
+ resources.NewDeploymentResource,
resources.NewDeploymentScheduleResource,
+ resources.NewFlowResource,
resources.NewServiceAccountResource,
resources.NewVariableResource,
resources.NewWorkPoolResource,
resources.NewWorkspaceAccessResource,
resources.NewWorkspaceResource,
resources.NewWorkspaceRoleResource,
- resources.NewBlockResource,
- resources.NewBlockAccessResource,
- resources.NewAutomationResource,
}
}