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, } }