From 39e8ab7a374ac74b321db48caa44f3c29e0de335 Mon Sep 17 00:00:00 2001 From: sreallymatt <106555974+sreallymatt@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:45:41 -0700 Subject: [PATCH 1/6] add elastic job step resource --- internal/services/mssql/client/client.go | 9 + .../services/mssql/mssql_job_step_resource.go | 445 ++++++++++++++++++ .../mssql/mssql_job_step_resource_test.go | 250 ++++++++++ internal/services/mssql/registration.go | 1 + .../sql/2023-08-01-preview/jobsteps/README.md | 123 +++++ .../sql/2023-08-01-preview/jobsteps/client.go | 26 + .../2023-08-01-preview/jobsteps/constants.go | 124 +++++ .../sql/2023-08-01-preview/jobsteps/id_job.go | 148 ++++++ .../2023-08-01-preview/jobsteps/id_step.go | 157 ++++++ .../2023-08-01-preview/jobsteps/id_version.go | 157 ++++++ .../jobsteps/id_versionstep.go | 166 +++++++ .../jobsteps/method_createorupdate.go | 58 +++ .../jobsteps/method_delete.go | 47 ++ .../2023-08-01-preview/jobsteps/method_get.go | 53 +++ .../jobsteps/method_getbyversion.go | 53 +++ .../jobsteps/method_listbyjob.go | 105 +++++ .../jobsteps/method_listbyversion.go | 105 +++++ .../jobsteps/model_jobstep.go | 11 + .../jobsteps/model_jobstepaction.go | 10 + .../jobsteps/model_jobstepexecutionoptions.go | 12 + .../jobsteps/model_jobstepoutput.go | 15 + .../jobsteps/model_jobstepproperties.go | 13 + .../2023-08-01-preview/jobsteps/predicates.go | 27 ++ .../2023-08-01-preview/jobsteps/version.go | 10 + vendor/modules.txt | 1 + website/docs/r/mssql_job_step.html.markdown | 153 ++++++ 26 files changed, 2279 insertions(+) create mode 100644 internal/services/mssql/mssql_job_step_resource.go create mode 100644 internal/services/mssql/mssql_job_step_resource_test.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/README.md create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/client.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/constants.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/id_job.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/id_step.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/id_version.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/id_versionstep.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/method_createorupdate.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/method_delete.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/method_get.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/method_getbyversion.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/method_listbyjob.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/method_listbyversion.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/model_jobstep.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/model_jobstepaction.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/model_jobstepexecutionoptions.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/model_jobstepoutput.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/model_jobstepproperties.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/predicates.go create mode 100644 vendor/github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/jobsteps/version.go create mode 100644 website/docs/r/mssql_job_step.html.markdown 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..108f735826d8 --- /dev/null +++ b/internal/services/mssql/mssql_job_step_resource.go @@ -0,0 +1,445 @@ +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"` + OutputOptions []JobStepOutputOptions `tfschema:"output_options"` + RetryAttempts int64 `tfschema:"retry_attempts"` + RetryIntervalBackoffMultiplier float64 `tfschema:"retry_interval_backoff_multiplier"` + TimeoutSeconds int64 `tfschema:"timeout_seconds"` +} + +type JobStepOutputOptions 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_options": { + 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, + }), + } + + if len(model.OutputOptions) != 0 { + outputOptions := model.OutputOptions[0] + + databaseId, err := commonids.ParseSqlDatabaseID(outputOptions.MsSqlDatabaseId) + if err != nil { + return err + } + + parameters.Properties.Output = pointer.To(jobsteps.JobStepOutput{ + Credential: pointer.To(outputOptions.JobCredentialId), + DatabaseName: databaseId.DatabaseName, + ResourceGroupName: pointer.To(databaseId.ResourceGroupName), + SchemaName: pointer.To(outputOptions.SchemaName), + ServerName: databaseId.ServerName, + SubscriptionId: pointer.To(databaseId.SubscriptionId), + TableName: outputOptions.TableName, + }) + } + + 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 { + state.JobCredentialID = pointer.From(props.Credential) + 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) + state.OutputOptions = flattenOutputOptions(props.Output) + 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_options") { + options, err := expandOutputOptions(config.OutputOptions) + if err != nil { + return err + } + + props.Output = options + } + + 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 expandOutputOptions(input []JobStepOutputOptions) (*jobsteps.JobStepOutput, error) { + if len(input) == 0 { + return nil, nil + } + + options := input[0] + databaseId, err := commonids.ParseSqlDatabaseID(options.MsSqlDatabaseId) + if err != nil { + return nil, err + } + + return pointer.To(jobsteps.JobStepOutput{ + Credential: pointer.To(options.JobCredentialId), + DatabaseName: databaseId.DatabaseName, + ResourceGroupName: pointer.To(databaseId.ResourceGroupName), + SchemaName: pointer.To(options.SchemaName), + ServerName: databaseId.ServerName, + SubscriptionId: pointer.To(databaseId.SubscriptionId), + TableName: options.TableName, + }), nil +} + +func flattenOutputOptions(input *jobsteps.JobStepOutput) []JobStepOutputOptions { + if input == nil { + return []JobStepOutputOptions{} + } + + databaseId := commonids.NewSqlDatabaseID(pointer.From(input.SubscriptionId), pointer.From(input.ResourceGroupName), input.ServerName, input.DatabaseName) + + return []JobStepOutputOptions{ + { + JobCredentialId: pointer.From(input.Credential), + MsSqlDatabaseId: databaseId.ID(), + TableName: input.TableName, + SchemaName: pointer.From(input.SchemaName), + }, + } +} 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..607d51b83a10 --- /dev/null +++ b/internal/services/mssql/mssql_job_step_resource_test.go @@ -0,0 +1,250 @@ +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(), + }) +} + +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 TestAccMsSqlJobStep_removeOutputOptions(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(), + { + Config: r.basic(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_options` - (Optional) An `output_options` 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_options` 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 +``` From 5a8d83a91e4be3bb9a32c84ed55d95192ac3d49c Mon Sep 17 00:00:00 2001 From: sreallymatt <106555974+sreallymatt@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:51:11 -0700 Subject: [PATCH 2/6] minor fixes to tests, fix typo in pr template --- .github/pull_request_template.md | 2 +- internal/services/mssql/mssql_job_step_resource_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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/mssql_job_step_resource_test.go b/internal/services/mssql/mssql_job_step_resource_test.go index 607d51b83a10..c9df217d875e 100644 --- a/internal/services/mssql/mssql_job_step_resource_test.go +++ b/internal/services/mssql/mssql_job_step_resource_test.go @@ -130,7 +130,7 @@ resource "azurerm_mssql_job_step" "test" { job_step_index = 1 sql_script = < Date: Tue, 4 Feb 2025 16:02:28 -0700 Subject: [PATCH 3/6] Run make generate --- .github/labeler-issue-triage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)*)###' From e8094348dfa1897750c3ebd368e97b152f152159 Mon Sep 17 00:00:00 2001 From: sreallymatt <106555974+sreallymatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 09:18:01 -0700 Subject: [PATCH 4/6] resolve comments from review --- .../services/mssql/mssql_job_step_resource.go | 93 ++++++++++--------- .../mssql/mssql_job_step_resource_test.go | 19 +--- website/docs/r/mssql_job_step.html.markdown | 4 +- 3 files changed, 55 insertions(+), 61 deletions(-) diff --git a/internal/services/mssql/mssql_job_step_resource.go b/internal/services/mssql/mssql_job_step_resource.go index 108f735826d8..a4b4aacafc6b 100644 --- a/internal/services/mssql/mssql_job_step_resource.go +++ b/internal/services/mssql/mssql_job_step_resource.go @@ -20,21 +20,21 @@ import ( 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"` - OutputOptions []JobStepOutputOptions `tfschema:"output_options"` - RetryAttempts int64 `tfschema:"retry_attempts"` - RetryIntervalBackoffMultiplier float64 `tfschema:"retry_interval_backoff_multiplier"` - TimeoutSeconds int64 `tfschema:"timeout_seconds"` + 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 JobStepOutputOptions struct { +type JobStepOutputTarget struct { JobCredentialId string `tfschema:"job_credential_id"` MsSqlDatabaseId string `tfschema:"mssql_database_id"` TableName string `tfschema:"table_name"` @@ -92,7 +92,7 @@ func (MsSqlJobStepResource) Arguments() map[string]*pluginsdk.Schema { Default: 120, ValidateFunc: validation.IntBetween(1, 2147483), }, - "output_options": { + "output_target": { Type: pluginsdk.TypeList, Optional: true, MaxItems: 1, @@ -219,24 +219,11 @@ func (r MsSqlJobStepResource) Create() sdk.ResourceFunc { }), } - if len(model.OutputOptions) != 0 { - outputOptions := model.OutputOptions[0] - - databaseId, err := commonids.ParseSqlDatabaseID(outputOptions.MsSqlDatabaseId) - if err != nil { - return err - } - - parameters.Properties.Output = pointer.To(jobsteps.JobStepOutput{ - Credential: pointer.To(outputOptions.JobCredentialId), - DatabaseName: databaseId.DatabaseName, - ResourceGroupName: pointer.To(databaseId.ResourceGroupName), - SchemaName: pointer.To(outputOptions.SchemaName), - ServerName: databaseId.ServerName, - SubscriptionId: pointer.To(databaseId.SubscriptionId), - TableName: outputOptions.TableName, - }) + 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) @@ -275,13 +262,26 @@ func (r MsSqlJobStepResource) Read() sdk.ResourceFunc { if model := resp.Model; model != nil { if props := model.Properties; props != nil { - state.JobCredentialID = pointer.From(props.Credential) + 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) - state.OutputOptions = flattenOutputOptions(props.Output) + + 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) @@ -351,10 +351,10 @@ func (r MsSqlJobStepResource) Update() sdk.ResourceFunc { props.ExecutionOptions.MaximumRetryIntervalSeconds = pointer.To(config.MaximumRetryIntervalSeconds) } - if metadata.ResourceData.HasChange("output_options") { - options, err := expandOutputOptions(config.OutputOptions) + if metadata.ResourceData.HasChange("output_target") { + options, err := expandOutputTarget(config.OutputTarget) if err != nil { - return err + return fmt.Errorf("expanding `output_target`: %+v", err) } props.Output = options @@ -405,7 +405,7 @@ func (r MsSqlJobStepResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { return jobsteps.ValidateStepID } -func expandOutputOptions(input []JobStepOutputOptions) (*jobsteps.JobStepOutput, error) { +func expandOutputTarget(input []JobStepOutputTarget) (*jobsteps.JobStepOutput, error) { if len(input) == 0 { return nil, nil } @@ -427,19 +427,28 @@ func expandOutputOptions(input []JobStepOutputOptions) (*jobsteps.JobStepOutput, }), nil } -func flattenOutputOptions(input *jobsteps.JobStepOutput) []JobStepOutputOptions { +func flattenOutputTarget(input *jobsteps.JobStepOutput) ([]JobStepOutputTarget, error) { if input == nil { - return []JobStepOutputOptions{} + 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 []JobStepOutputOptions{ + return []JobStepOutputTarget{ { - JobCredentialId: pointer.From(input.Credential), + 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 index c9df217d875e..c7619fdb93d4 100644 --- a/internal/services/mssql/mssql_job_step_resource_test.go +++ b/internal/services/mssql/mssql_job_step_resource_test.go @@ -64,16 +64,8 @@ func TestAccMsSqlJobStep_update(t *testing.T) { ), }, 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), + Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -82,7 +74,7 @@ func TestAccMsSqlJobStep_complete(t *testing.T) { }) } -func TestAccMsSqlJobStep_removeOutputOptions(t *testing.T) { +func TestAccMsSqlJobStep_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_mssql_job_step", "test") r := MsSqlJobStepTestResource{} @@ -94,13 +86,6 @@ func TestAccMsSqlJobStep_removeOutputOptions(t *testing.T) { ), }, data.ImportStep(), - { - Config: r.basic(data), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), }) } diff --git a/website/docs/r/mssql_job_step.html.markdown b/website/docs/r/mssql_job_step.html.markdown index 3b1b3486c181..cafe1446840d 100644 --- a/website/docs/r/mssql_job_step.html.markdown +++ b/website/docs/r/mssql_job_step.html.markdown @@ -109,7 +109,7 @@ The following arguments are supported: ~> **Note:** `maximum_retry_interval_seconds` must be greater than `initial_retry_interval_seconds`. -* `output_options` - (Optional) An `output_options` block as defined below. +* `output_target` - (Optional) An `output_target` block as defined below. * `retry_attempts` - (Optional) The number of retry attempts. Defaults to `10`. @@ -119,7 +119,7 @@ The following arguments are supported: --- -A `output_options` block supports the following: +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. From b6326ec85688ec952598b75db476e2fedee27508 Mon Sep 17 00:00:00 2001 From: sreallymatt <106555974+sreallymatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 09:30:17 -0700 Subject: [PATCH 5/6] fix missed argument rename --- internal/services/mssql/mssql_job_step_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/mssql/mssql_job_step_resource_test.go b/internal/services/mssql/mssql_job_step_resource_test.go index c7619fdb93d4..6fd8f8a4201a 100644 --- a/internal/services/mssql/mssql_job_step_resource_test.go +++ b/internal/services/mssql/mssql_job_step_resource_test.go @@ -172,7 +172,7 @@ EOT retry_interval_backoff_multiplier = 4.5 timeout_seconds = 12345 - output_options { + output_target { job_credential_id = azurerm_mssql_job_credential.test.id mssql_database_id = azurerm_mssql_database.test.id table_name = "test" From bb562eb66bac862fa857ec8f89ed26e4f51f8240 Mon Sep 17 00:00:00 2001 From: sreallymatt <106555974+sreallymatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 11:01:02 -0700 Subject: [PATCH 6/6] options > target for consistency --- .../services/mssql/mssql_job_step_resource.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/internal/services/mssql/mssql_job_step_resource.go b/internal/services/mssql/mssql_job_step_resource.go index a4b4aacafc6b..683a96803482 100644 --- a/internal/services/mssql/mssql_job_step_resource.go +++ b/internal/services/mssql/mssql_job_step_resource.go @@ -352,12 +352,11 @@ func (r MsSqlJobStepResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("output_target") { - options, err := expandOutputTarget(config.OutputTarget) + target, err := expandOutputTarget(config.OutputTarget) if err != nil { return fmt.Errorf("expanding `output_target`: %+v", err) } - - props.Output = options + props.Output = target } if metadata.ResourceData.HasChange("retry_attempts") { @@ -410,20 +409,20 @@ func expandOutputTarget(input []JobStepOutputTarget) (*jobsteps.JobStepOutput, e return nil, nil } - options := input[0] - databaseId, err := commonids.ParseSqlDatabaseID(options.MsSqlDatabaseId) + target := input[0] + databaseId, err := commonids.ParseSqlDatabaseID(target.MsSqlDatabaseId) if err != nil { return nil, err } return pointer.To(jobsteps.JobStepOutput{ - Credential: pointer.To(options.JobCredentialId), + Credential: pointer.To(target.JobCredentialId), DatabaseName: databaseId.DatabaseName, ResourceGroupName: pointer.To(databaseId.ResourceGroupName), - SchemaName: pointer.To(options.SchemaName), + SchemaName: pointer.To(target.SchemaName), ServerName: databaseId.ServerName, SubscriptionId: pointer.To(databaseId.SubscriptionId), - TableName: options.TableName, + TableName: target.TableName, }), nil }