Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

New resource: azurerm_mssql_job_target_group #28492

Merged
merged 12 commits into from
Jan 30, 2025
2 changes: 1 addition & 1 deletion .github/labeler-issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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_agent\W+|mssql_job_credential\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_agent\W+|mssql_job_credential\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)*)###'
Expand Down
9 changes: 9 additions & 0 deletions internal/services/mssql/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/geobackuppolicies"
"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/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"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/replicationlinks"
Expand Down Expand Up @@ -53,6 +54,7 @@ type Client struct {
GeoBackupPoliciesClient *geobackuppolicies.GeoBackupPoliciesClient
JobAgentsClient *jobagents.JobAgentsClient
JobCredentialsClient *jobcredentials.JobCredentialsClient
JobTargetGroupsClient *jobtargetgroups.JobTargetGroupsClient
LongTermRetentionPoliciesClient *longtermretentionpolicies.LongTermRetentionPoliciesClient
OutboundFirewallRulesClient *outboundfirewallrules.OutboundFirewallRulesClient
ReplicationLinksClient *replicationlinks.ReplicationLinksClient
Expand Down Expand Up @@ -148,6 +150,12 @@ func NewClient(o *common.ClientOptions) (*Client, error) {
}
o.Configure(jobCredentialsClient.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)
}
o.Configure(jobTargetGroupsClient.Client, o.Authorizers.ResourceManager)

longTermRetentionPoliciesClient, err := longtermretentionpolicies.NewLongTermRetentionPoliciesClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building Long Term Retention Policies Client: %+v", err)
Expand Down Expand Up @@ -292,6 +300,7 @@ func NewClient(o *common.ClientOptions) (*Client, error) {
DatabaseSecurityAlertPoliciesClient: databaseSecurityAlertPoliciesClient,
ElasticPoolsClient: elasticPoolsClient,
GeoBackupPoliciesClient: geoBackupPoliciesClient,
JobTargetGroupsClient: jobTargetGroupsClient,
LongTermRetentionPoliciesClient: longTermRetentionPoliciesClient,
ReplicationLinksClient: replicationLinksClient,
RestorableDroppedDatabasesClient: restorableDroppedDatabasesClient,
Expand Down
327 changes: 327 additions & 0 deletions internal/services/mssql/mssql_job_target_group_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
package mssql

import (
"context"
"fmt"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"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/jobtargetgroups"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/mssql/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
)

type MsSqlJobTargetGroupResource struct{}

type MsSqlJobTargetGroupResourceModel struct {
Name string `tfschema:"name"`
JobAgentID string `tfschema:"job_agent_id"`
JobTargets []MsSqlJobTarget `tfschema:"job_target"`
}

type MsSqlJobTarget struct {
ServerName string `tfschema:"server_name"`
Type string `tfschema:"type"`
DatabaseName string `tfschema:"database_name"`
ElasticPoolName string `tfschema:"elastic_pool_name"`
JobCredentialId string `tfschema:"job_credential_id"`
MembershipType string `tfschema:"membership_type"`
}

var _ sdk.ResourceWithUpdate = MsSqlJobTargetGroupResource{}

func (r MsSqlJobTargetGroupResource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringIsNotEmpty,
ForceNew: true,
},
"job_agent_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: jobs.ValidateJobAgentID,
ForceNew: true,
},
"job_target": {
Type: pluginsdk.TypeSet,
Optional: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"server_name": {
sreallymatt marked this conversation as resolved.
Show resolved Hide resolved
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validate.ValidateMsSqlServerName,
},
"type": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
string(jobtargetgroups.JobTargetTypeSqlDatabase),
string(jobtargetgroups.JobTargetTypeSqlElasticPool),
string(jobtargetgroups.JobTargetTypeSqlServer),
}, false),
},
"database_name": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validate.ValidateMsSqlDatabaseName,
},
"elastic_pool_name": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validate.ValidateMsSqlElasticPoolName,
},
sreallymatt marked this conversation as resolved.
Show resolved Hide resolved
"job_credential_id": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: jobcredentials.ValidateCredentialID,
},
"membership_type": {
Type: pluginsdk.TypeString,
Optional: true,
Default: string(jobtargetgroups.JobTargetGroupMembershipTypeInclude),
ValidateFunc: validation.StringInSlice(jobtargetgroups.PossibleValuesForJobTargetGroupMembershipType(), false),
},
},
},
},
}
}

func (r MsSqlJobTargetGroupResource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{}
}

func (r MsSqlJobTargetGroupResource) ModelObject() interface{} {
return &MsSqlJobTargetGroupResourceModel{}
}

func (r MsSqlJobTargetGroupResource) ResourceType() string {
return "azurerm_mssql_job_target_group"
}

func (r MsSqlJobTargetGroupResource) Create() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.MSSQL.JobTargetGroupsClient

var model MsSqlJobTargetGroupResourceModel
if err := metadata.Decode(&model); err != nil {
return err
}

jobAgent, err := jobtargetgroups.ParseJobAgentID(model.JobAgentID)
if err != nil {
return err
}

id := jobtargetgroups.NewTargetGroupID(jobAgent.SubscriptionId, jobAgent.ResourceGroupName, jobAgent.ServerName, jobAgent.JobAgentName, 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)
}

targets, err := expandJobTargets(model.JobTargets)
if err != nil {
return err
sreallymatt marked this conversation as resolved.
Show resolved Hide resolved
}

parameters := jobtargetgroups.JobTargetGroup{
Name: pointer.To(model.Name),
Properties: pointer.To(jobtargetgroups.JobTargetGroupProperties{
Members: targets,
}),
}

if _, err := client.CreateOrUpdate(ctx, id, parameters); err != nil {
return fmt.Errorf("creating %s: %+v", id, err)
}

metadata.SetID(id)
return nil
},
}
}

func (r MsSqlJobTargetGroupResource) Read() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 5 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.MSSQL.JobTargetGroupsClient

id, err := jobtargetgroups.ParseTargetGroupID(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 := MsSqlJobTargetGroupResourceModel{
Name: id.TargetGroupName,
JobAgentID: jobtargetgroups.NewJobAgentID(id.SubscriptionId, id.ResourceGroupName, id.ServerName, id.JobAgentName).ID(),
}

if model := resp.Model; model != nil {
if props := model.Properties; props != nil {
state.JobTargets = flattenJobTargets(props.Members)
}
}

return metadata.Encode(&state)
},
}
}

func (r MsSqlJobTargetGroupResource) Update() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.MSSQL.JobTargetGroupsClient

id, err := jobtargetgroups.ParseTargetGroupID(metadata.ResourceData.Id())
if err != nil {
return err
}

var config MsSqlJobTargetGroupResourceModel
if err := metadata.Decode(&config); err != nil {
return fmt.Errorf("decoding %+v", err)
}

existing, err := client.Get(ctx, *id)
if err != nil {
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)
}

if metadata.ResourceData.HasChange("job_target") {
targets, err := expandJobTargets(config.JobTargets)
if err != nil {
return err
sreallymatt marked this conversation as resolved.
Show resolved Hide resolved
}
existing.Model.Properties.Members = targets
}

if _, err := client.CreateOrUpdate(ctx, *id, *existing.Model); err != nil {
return fmt.Errorf("updating: %s: %+v", id, err)
}
sreallymatt marked this conversation as resolved.
Show resolved Hide resolved

return nil
},
}
}

func (r MsSqlJobTargetGroupResource) Delete() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.MSSQL.JobTargetGroupsClient

id, err := jobtargetgroups.ParseTargetGroupID(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 MsSqlJobTargetGroupResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return jobtargetgroups.ValidateTargetGroupID
}

func expandJobTargets(input []MsSqlJobTarget) ([]jobtargetgroups.JobTarget, error) {
targets := make([]jobtargetgroups.JobTarget, 0)
if len(input) == 0 {
return targets, nil
}

for _, v := range input {
t := jobtargetgroups.JobTarget{
MembershipType: pointer.To(jobtargetgroups.JobTargetGroupMembershipType(v.MembershipType)),
ServerName: pointer.To(v.ServerName),
Type: jobtargetgroups.JobTargetType(v.Type),
}

if v.MembershipType == string(jobtargetgroups.JobTargetGroupMembershipTypeInclude) && v.Type != string(jobtargetgroups.JobTargetTypeSqlDatabase) {
if v.JobCredentialId == "" {
return nil, fmt.Errorf("`job_credential_id` is required when `membership_type` is `%s` and `type` is `%s`", jobtargetgroups.JobTargetGroupMembershipTypeInclude, v.Type)
}

t.RefreshCredential = pointer.To(v.JobCredentialId)
}

switch v.Type {
case string(jobtargetgroups.JobTargetTypeSqlDatabase):
if v.DatabaseName == "" {
return nil, fmt.Errorf("`database_name` is required when `type` is `%s`", jobtargetgroups.JobTargetTypeSqlDatabase)
}

t.DatabaseName = pointer.To(v.DatabaseName)
case string(jobtargetgroups.JobTargetTypeSqlElasticPool):
if v.ElasticPoolName == "" {
return nil, fmt.Errorf("`elastic_pool_name` is required when `type` is `%s`", jobtargetgroups.JobTargetTypeSqlElasticPool)
}

t.ElasticPoolName = pointer.To(v.ElasticPoolName)
}
sreallymatt marked this conversation as resolved.
Show resolved Hide resolved

targets = append(targets, t)
}

return targets, nil
}

func flattenJobTargets(input []jobtargetgroups.JobTarget) []MsSqlJobTarget {
targets := make([]MsSqlJobTarget, 0)
if len(input) == 0 {
return targets
}

for _, v := range input {
t := MsSqlJobTarget{
DatabaseName: pointer.From(v.DatabaseName),
ElasticPoolName: pointer.From(v.ElasticPoolName),
MembershipType: string(pointer.From(v.MembershipType)),
JobCredentialId: pointer.From(v.RefreshCredential),
ServerName: pointer.From(v.ServerName),
Type: string(v.Type),
}

targets = append(targets, t)
}

return targets
}
Loading
Loading