diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index 278889f16de1..a43420eca78f 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -234,7 +234,7 @@ service/monitor: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_monitor_((.|\n)*)###' service/mssql: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(mssql_database\W+|mssql_database_extended_auditing_policy\W+|mssql_database_vulnerability_assessment_rule_baseline\W+|mssql_elasticpool\W+|mssql_failover_group\W+|mssql_firewall_rule\W+|mssql_job\W+|mssql_job_agent\W+|mssql_job_credential\W+|mssql_job_schedule\W+|mssql_job_target_group\W+|mssql_outbound_firewall_rule\W+|mssql_server\W+|mssql_server_dns_alias\W+|mssql_server_extended_auditing_policy\W+|mssql_server_microsoft_support_auditing_policy\W+|mssql_server_security_alert_policy\W+|mssql_server_transparent_data_encryption\W+|mssql_server_vulnerability_assessment\W+|mssql_virtual_machine\W+|mssql_virtual_machine_availability_group_listener\W+|mssql_virtual_machine_group\W+|mssql_virtual_network_rule\W+)((.|\n)*)###' + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(mssql_database\W+|mssql_database_extended_auditing_policy\W+|mssql_database_vulnerability_assessment_rule_baseline\W+|mssql_elasticpool\W+|mssql_failover_group\W+|mssql_firewall_rule\W+|mssql_job\W+|mssql_job_agent\W+|mssql_job_credential\W+|mssql_job_schedule\W+|mssql_job_step\W+|mssql_job_target_group\W+|mssql_outbound_firewall_rule\W+|mssql_server\W+|mssql_server_dns_alias\W+|mssql_server_extended_auditing_policy\W+|mssql_server_microsoft_support_auditing_policy\W+|mssql_server_security_alert_policy\W+|mssql_server_transparent_data_encryption\W+|mssql_server_vulnerability_assessment\W+|mssql_virtual_machine\W+|mssql_virtual_machine_availability_group_listener\W+|mssql_virtual_machine_group\W+|mssql_virtual_network_rule\W+)((.|\n)*)###' service/mssqlmanagedinstance: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_mssql_managed_((.|\n)*)###' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index dee7dc6d6699..3fdc568eac91 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -30,7 +30,7 @@ For example: “`resource_name_here` - description of change e.g. adding propert ## Changes to existing Resource / Data Source - [ ] I have added an explanation of what my changes do and why I'd like you to include them (This may be covered by linking to an issue above, but may benefit from additional explanation). -- [ ] I have written new tests for my resource or datasource changes & updated any relevent documentation. +- [ ] I have written new tests for my resource or datasource changes & updated any relevant documentation. - [ ] I have successfully run tests with my changes locally. If not, please provide details on testing challenges that prevented you running the tests. - [ ] (For changes that include a **state migration only**). I have manually tested the migration path between relevant versions of the provider. diff --git a/internal/services/mssql/client/client.go b/internal/services/mssql/client/client.go index 6290f2b8c0f3..7eaccb93d87c 100644 --- a/internal/services/mssql/client/client.go +++ b/internal/services/mssql/client/client.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobagents" "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobcredentials" "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobs" + "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps" "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobtargetgroups" "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/longtermretentionpolicies" "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/outboundfirewallrules" @@ -56,6 +57,7 @@ type Client struct { JobAgentsClient *jobagents.JobAgentsClient JobCredentialsClient *jobcredentials.JobCredentialsClient JobsClient *jobs.JobsClient + JobStepsClient *jobsteps.JobStepsClient JobTargetGroupsClient *jobtargetgroups.JobTargetGroupsClient LongTermRetentionPoliciesClient *longtermretentionpolicies.LongTermRetentionPoliciesClient OutboundFirewallRulesClient *outboundfirewallrules.OutboundFirewallRulesClient @@ -158,6 +160,12 @@ func NewClient(o *common.ClientOptions) (*Client, error) { } o.Configure(jobsClient.Client, o.Authorizers.ResourceManager) + jobStepsClient, err := jobsteps.NewJobStepsClientWithBaseURI(o.Environment.ResourceManager) + if err != nil { + return nil, fmt.Errorf("building Job Steps Client: %+v", err) + } + o.Configure(jobStepsClient.Client, o.Authorizers.ResourceManager) + jobTargetGroupsClient, err := jobtargetgroups.NewJobTargetGroupsClientWithBaseURI(o.Environment.ResourceManager) if err != nil { return nil, fmt.Errorf("building Job Target Groups Client: %+v", err) @@ -309,6 +317,7 @@ func NewClient(o *common.ClientOptions) (*Client, error) { ElasticPoolsClient: elasticPoolsClient, GeoBackupPoliciesClient: geoBackupPoliciesClient, JobsClient: jobsClient, + JobStepsClient: jobStepsClient, JobTargetGroupsClient: jobTargetGroupsClient, LongTermRetentionPoliciesClient: longTermRetentionPoliciesClient, ReplicationLinksClient: replicationLinksClient, diff --git a/internal/services/mssql/mssql_job_step_resource.go b/internal/services/mssql/mssql_job_step_resource.go new file mode 100644 index 000000000000..683a96803482 --- /dev/null +++ b/internal/services/mssql/mssql_job_step_resource.go @@ -0,0 +1,453 @@ +package mssql + +import ( + "context" + "fmt" + "math" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" + "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobcredentials" + "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps" + "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobtargetgroups" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type MsSqlJobStepResource struct{} + +type MsSqlJobStepResourceModel struct { + Name string `tfschema:"name"` + JobID string `tfschema:"job_id"` + JobCredentialID string `tfschema:"job_credential_id"` + JobStepIndex int64 `tfschema:"job_step_index"` + JobTargetGroupID string `tfschema:"job_target_group_id"` + SqlScript string `tfschema:"sql_script"` + InitialRetryIntervalSeconds int64 `tfschema:"initial_retry_interval_seconds"` + MaximumRetryIntervalSeconds int64 `tfschema:"maximum_retry_interval_seconds"` + OutputTarget []JobStepOutputTarget `tfschema:"output_target"` + RetryAttempts int64 `tfschema:"retry_attempts"` + RetryIntervalBackoffMultiplier float64 `tfschema:"retry_interval_backoff_multiplier"` + TimeoutSeconds int64 `tfschema:"timeout_seconds"` +} + +type JobStepOutputTarget struct { + JobCredentialId string `tfschema:"job_credential_id"` + MsSqlDatabaseId string `tfschema:"mssql_database_id"` + TableName string `tfschema:"table_name"` + SchemaName string `tfschema:"schema_name"` +} + +var ( + _ sdk.ResourceWithUpdate = MsSqlJobStepResource{} + _ sdk.ResourceWithCustomizeDiff = MsSqlJobStepResource{} +) + +func (MsSqlJobStepResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + ForceNew: true, + }, + "job_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: jobsteps.ValidateJobID, + ForceNew: true, + }, + "job_credential_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: jobcredentials.ValidateCredentialID, + }, + "job_step_index": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "job_target_group_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: jobtargetgroups.ValidateTargetGroupID, + }, + "sql_script": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "initial_retry_interval_seconds": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 1, + ValidateFunc: validation.IntBetween(1, 2147483), + }, + "maximum_retry_interval_seconds": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 120, + ValidateFunc: validation.IntBetween(1, 2147483), + }, + "output_target": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "job_credential_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: jobcredentials.ValidateCredentialID, + }, + "mssql_database_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: commonids.ValidateSqlDatabaseID, + }, + "table_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "schema_name": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "dbo", + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + "retry_attempts": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 10, + ValidateFunc: validation.IntBetween(1, math.MaxInt32), + }, + "retry_interval_backoff_multiplier": { + Type: pluginsdk.TypeFloat, + Optional: true, + Default: 2.0, + ValidateFunc: validation.FloatAtLeast(1), + }, + "timeout_seconds": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 43200, + ValidateFunc: validation.IntBetween(1, 2147483), + }, + } +} + +func (r MsSqlJobStepResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} + +func (r MsSqlJobStepResource) CustomizeDiff() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 10 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var model MsSqlJobStepResourceModel + if err := metadata.DecodeDiff(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + if model.MaximumRetryIntervalSeconds <= model.InitialRetryIntervalSeconds { + return fmt.Errorf("`maximum_retry_interval_seconds` must be greater than `initial_retry_interval_seconds`") + } + + return nil + }, + } +} + +func (r MsSqlJobStepResource) ModelObject() interface{} { + return &MsSqlJobStepResourceModel{} +} + +func (r MsSqlJobStepResource) ResourceType() string { + return "azurerm_mssql_job_step" +} + +func (r MsSqlJobStepResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MSSQL.JobStepsClient + + var model MsSqlJobStepResourceModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + job, err := jobsteps.ParseJobID(model.JobID) + if err != nil { + return err + } + + id := jobsteps.NewStepID(job.SubscriptionId, job.ResourceGroupName, job.ServerName, job.JobAgentName, job.JobName, model.Name) + + existing, err := client.Get(ctx, id) + if err != nil && !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + + if !response.WasNotFound(existing.HttpResponse) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + parameters := jobsteps.JobStep{ + Name: pointer.To(model.Name), + Properties: pointer.To(jobsteps.JobStepProperties{ + Action: jobsteps.JobStepAction{ + Value: model.SqlScript, + }, + Credential: pointer.To(model.JobCredentialID), + ExecutionOptions: pointer.To(jobsteps.JobStepExecutionOptions{ + InitialRetryIntervalSeconds: pointer.To(model.InitialRetryIntervalSeconds), + MaximumRetryIntervalSeconds: pointer.To(model.MaximumRetryIntervalSeconds), + RetryAttempts: pointer.To(model.RetryAttempts), + RetryIntervalBackoffMultiplier: pointer.To(model.RetryIntervalBackoffMultiplier), + TimeoutSeconds: pointer.To(model.TimeoutSeconds), + }), + StepId: pointer.To(model.JobStepIndex), + TargetGroup: model.JobTargetGroupID, + }), + } + + target, err := expandOutputTarget(model.OutputTarget) + if err != nil { + return fmt.Errorf("expanding `output_target`: %+v", err) + } + parameters.Properties.Output = target + + if _, err := client.CreateOrUpdate(ctx, id, parameters); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r MsSqlJobStepResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MSSQL.JobStepsClient + + id, err := jobsteps.ParseStepID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, *id) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + state := MsSqlJobStepResourceModel{ + Name: id.StepName, + JobID: jobsteps.NewJobID(id.SubscriptionId, id.ResourceGroupName, id.ServerName, id.JobAgentName, id.JobName).ID(), + } + + if model := resp.Model; model != nil { + if props := model.Properties; props != nil { + if v := pointer.From(props.Credential); v != "" { + credentialID, err := jobcredentials.ParseCredentialID(v) + if err != nil { + return err + } + state.JobCredentialID = credentialID.ID() + } + + state.JobStepIndex = pointer.From(props.StepId) + state.JobTargetGroupID = props.TargetGroup + state.SqlScript = props.Action.Value + state.InitialRetryIntervalSeconds = pointer.From(props.ExecutionOptions.InitialRetryIntervalSeconds) + state.MaximumRetryIntervalSeconds = pointer.From(props.ExecutionOptions.MaximumRetryIntervalSeconds) + + target, err := flattenOutputTarget(props.Output) + if err != nil { + return fmt.Errorf("flattening `output_target`: %+v", err) + } + state.OutputTarget = target + + state.RetryAttempts = pointer.From(props.ExecutionOptions.RetryAttempts) + state.RetryIntervalBackoffMultiplier = pointer.From(props.ExecutionOptions.RetryIntervalBackoffMultiplier) + state.TimeoutSeconds = pointer.From(props.ExecutionOptions.TimeoutSeconds) + } + } + + return metadata.Encode(&state) + }, + } +} + +func (r MsSqlJobStepResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MSSQL.JobStepsClient + + id, err := jobsteps.ParseStepID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var config MsSqlJobStepResourceModel + if err := metadata.Decode(&config); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + existing, err := client.Get(ctx, *id) + if err != nil { + if response.WasNotFound(existing.HttpResponse) { + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + if existing.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", id) + } + + if existing.Model.Properties == nil { + return fmt.Errorf("retrieving %s: `model.Properties` was nil", id) + } + props := existing.Model.Properties + + if metadata.ResourceData.HasChange("job_credential_id") { + props.Credential = pointer.To(config.JobCredentialID) + } + + if metadata.ResourceData.HasChange("job_step_index") { + props.StepId = pointer.To(config.JobStepIndex) + } + + if metadata.ResourceData.HasChange("job_target_group_id") { + props.TargetGroup = config.JobTargetGroupID + } + + if metadata.ResourceData.HasChange("sql_script") { + props.Action.Value = config.SqlScript + } + + if metadata.ResourceData.HasChange("initial_retry_interval_seconds") { + props.ExecutionOptions.InitialRetryIntervalSeconds = pointer.To(config.InitialRetryIntervalSeconds) + } + + if metadata.ResourceData.HasChange("maximum_retry_interval_seconds") { + props.ExecutionOptions.MaximumRetryIntervalSeconds = pointer.To(config.MaximumRetryIntervalSeconds) + } + + if metadata.ResourceData.HasChange("output_target") { + target, err := expandOutputTarget(config.OutputTarget) + if err != nil { + return fmt.Errorf("expanding `output_target`: %+v", err) + } + props.Output = target + } + + if metadata.ResourceData.HasChange("retry_attempts") { + props.ExecutionOptions.RetryAttempts = pointer.To(config.RetryAttempts) + } + + if metadata.ResourceData.HasChange("retry_interval_backoff_multiplier") { + props.ExecutionOptions.RetryIntervalBackoffMultiplier = pointer.To(config.RetryIntervalBackoffMultiplier) + } + + if metadata.ResourceData.HasChange("timeout_seconds") { + props.ExecutionOptions.TimeoutSeconds = pointer.To(config.TimeoutSeconds) + } + + if _, err := client.CreateOrUpdate(ctx, *id, *existing.Model); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return nil + }, + } +} + +func (r MsSqlJobStepResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MSSQL.JobStepsClient + + id, err := jobsteps.ParseStepID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if _, err := client.Delete(ctx, *id); err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + return nil + }, + } +} + +func (r MsSqlJobStepResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return jobsteps.ValidateStepID +} + +func expandOutputTarget(input []JobStepOutputTarget) (*jobsteps.JobStepOutput, error) { + if len(input) == 0 { + return nil, nil + } + + target := input[0] + databaseId, err := commonids.ParseSqlDatabaseID(target.MsSqlDatabaseId) + if err != nil { + return nil, err + } + + return pointer.To(jobsteps.JobStepOutput{ + Credential: pointer.To(target.JobCredentialId), + DatabaseName: databaseId.DatabaseName, + ResourceGroupName: pointer.To(databaseId.ResourceGroupName), + SchemaName: pointer.To(target.SchemaName), + ServerName: databaseId.ServerName, + SubscriptionId: pointer.To(databaseId.SubscriptionId), + TableName: target.TableName, + }), nil +} + +func flattenOutputTarget(input *jobsteps.JobStepOutput) ([]JobStepOutputTarget, error) { + if input == nil { + return []JobStepOutputTarget{}, nil + } + + credentialID := "" + if v := pointer.From(input.Credential); v != "" { + id, err := jobcredentials.ParseCredentialID(v) + if err != nil { + return nil, err + } + credentialID = id.ID() + } + + databaseId := commonids.NewSqlDatabaseID(pointer.From(input.SubscriptionId), pointer.From(input.ResourceGroupName), input.ServerName, input.DatabaseName) + + return []JobStepOutputTarget{ + { + JobCredentialId: credentialID, + MsSqlDatabaseId: databaseId.ID(), + TableName: input.TableName, + SchemaName: pointer.From(input.SchemaName), + }, + }, nil +} diff --git a/internal/services/mssql/mssql_job_step_resource_test.go b/internal/services/mssql/mssql_job_step_resource_test.go new file mode 100644 index 000000000000..6fd8f8a4201a --- /dev/null +++ b/internal/services/mssql/mssql_job_step_resource_test.go @@ -0,0 +1,235 @@ +package mssql_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type MsSqlJobStepTestResource struct{} + +func TestAccMsSqlJobStep_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_job_step", "test") + r := MsSqlJobStepTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccMsSqlJobStep_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_job_step", "test") + r := MsSqlJobStepTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccMsSqlJobStep_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_job_step", "test") + r := MsSqlJobStepTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccMsSqlJobStep_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_job_step", "test") + r := MsSqlJobStepTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (MsSqlJobStepTestResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := jobsteps.ParseStepID(state.ID) + if err != nil { + return nil, err + } + + resp, err := client.MSSQL.JobStepsClient.Get(ctx, *id) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + + return pointer.To(resp.Model != nil), nil +} + +func (r MsSqlJobStepTestResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_mssql_job_step" "test" { + name = "acctest-job-step-%[2]d" + job_id = azurerm_mssql_job.test.id + job_credential_id = azurerm_mssql_job_credential.test.id + job_target_group_id = azurerm_mssql_job_target_group.test.id + + job_step_index = 1 + sql_script = < **Note:** This value must be greater than or equal to 1 and less than or equal to the number of job steps in the Elastic Job. + +* `job_target_group_id` - (Required) The ID of the Elastic Job Target Group. + +* `sql_script` - (Required) The T-SQL script to be executed by this Elastic Job Step. + +-> **Note:** While Azure places no restrictions on the script provided here, it is recommended to ensure the script is idempotent. + +--- + +* `initial_retry_interval_seconds` - (Optional) The initial retry interval in seconds. Defaults to `1`. + +* `maximum_retry_interval_seconds` - (Optional) The maximum retry interval in seconds. Defaults to `120`. + +~> **Note:** `maximum_retry_interval_seconds` must be greater than `initial_retry_interval_seconds`. + +* `output_target` - (Optional) An `output_target` block as defined below. + +* `retry_attempts` - (Optional) The number of retry attempts. Defaults to `10`. + +* `retry_interval_backoff_multiplier` - (Optional) The multiplier for time between retries. Defaults to `2`. + +* `timeout_seconds` - (Optional) The execution timeout in seconds for this Elastic Job Step. Defaults to `43200`. + +--- + +A `output_target` block supports the following: + +* `job_credential_id` - (Required) The ID of the Elastic Job Credential to use when connecting to the output destination. + +* `mssql_database_id` - (Required) The ID of the output database. + +* `table_name` - (Required) The name of the output table. + +* `schema_name` - (Optional) The name of the output schema. Defaults to `dbo`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Elastic Job Step. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Elastic Job Step. +* `read` - (Defaults to 5 minutes) Used when retrieving the Elastic Job Step. +* `update` - (Defaults to 30 minutes) Used when updating the Elastic Job Step. +* `delete` - (Defaults to 30 minutes) Used when deleting the Elastic Job Step. + +## Import + +Elastic Job Steps can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_mssql_job_step.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Sql/servers/myserver1/jobAgents/myjobagent1/jobs/myjob1/steps/myjobstep1 +```