Skip to content

Commit

Permalink
New resource: azurerm_mssql_job_target_group (#28492)
Browse files Browse the repository at this point in the history
* New resource: azurerm_mssql_job_target_group

* fix lint

* gofumpt and generate

* Minor fixes

* Apply suggestions from code review

Co-authored-by: stephybun <[email protected]>

* Move note under correct argument

* Add var to update method

* Change job_target.type to computed attr, move validation to CustomizeDiff

* Update resource documentation

* Update website/docs/r/mssql_job_target_group.html.markdown

Co-authored-by: stephybun <[email protected]>

* Remove excessive information

---------

Co-authored-by: stephybun <[email protected]>
  • Loading branch information
sreallymatt and stephybun authored Jan 30, 2025
1 parent 083f474 commit 9d4b610
Show file tree
Hide file tree
Showing 21 changed files with 1,591 additions and 1 deletion.
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\W+|mssql_job_agent\W+|mssql_job_credential\W+|mssql_job_schedule\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_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 @@ -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/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 @@ -55,6 +56,7 @@ type Client struct {
JobAgentsClient *jobagents.JobAgentsClient
JobCredentialsClient *jobcredentials.JobCredentialsClient
JobsClient *jobs.JobsClient
JobTargetGroupsClient *jobtargetgroups.JobTargetGroupsClient
LongTermRetentionPoliciesClient *longtermretentionpolicies.LongTermRetentionPoliciesClient
OutboundFirewallRulesClient *outboundfirewallrules.OutboundFirewallRulesClient
ReplicationLinksClient *replicationlinks.ReplicationLinksClient
Expand Down Expand Up @@ -156,6 +158,12 @@ func NewClient(o *common.ClientOptions) (*Client, error) {
}
o.Configure(jobsClient.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 @@ -301,6 +309,7 @@ func NewClient(o *common.ClientOptions) (*Client, error) {
ElasticPoolsClient: elasticPoolsClient,
GeoBackupPoliciesClient: geoBackupPoliciesClient,
JobsClient: jobsClient,
JobTargetGroupsClient: jobTargetGroupsClient,
LongTermRetentionPoliciesClient: longTermRetentionPoliciesClient,
ReplicationLinksClient: replicationLinksClient,
RestorableDroppedDatabasesClient: restorableDroppedDatabasesClient,
Expand Down
351 changes: 351 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,351 @@
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{}
_ sdk.ResourceWithCustomizeDiff = 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": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validate.ValidateMsSqlServerName,
},
"database_name": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validate.ValidateMsSqlDatabaseName,
},
"elastic_pool_name": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validate.ValidateMsSqlElasticPoolName,
},
"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),
},
"type": {
Type: pluginsdk.TypeString,
Computed: true,
},
},
},
},
}
}

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

func (r MsSqlJobTargetGroupResource) CustomizeDiff() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 10 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
var config MsSqlJobTargetGroupResourceModel
if err := metadata.DecodeDiff(&config); err != nil {
return fmt.Errorf("decoding: %+v", err)
}

for _, v := range config.JobTargets {
if v.DatabaseName != "" && v.ElasticPoolName != "" {
return fmt.Errorf("`database_name` and `elastic_pool_name` are mutually exclusive")
}

targetType := determineJobTargetType(v)
if isCredentialRequired(jobtargetgroups.JobTargetGroupMembershipType(v.MembershipType), targetType) {
if v.JobCredentialId == "" {
return fmt.Errorf("`job_credential_id` is required when `membership_type` is `%s` and `type` is `%s`", jobtargetgroups.JobTargetGroupMembershipTypeInclude, targetType)
}
}
}

return nil
},
}
}

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

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

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

payload := existing.Model

if metadata.ResourceData.HasChange("job_target") {
payload.Properties.Members = expandJobTargets(config.JobTargets)
}

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

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 {
targets := make([]jobtargetgroups.JobTarget, 0)
if len(input) == 0 {
return targets
}

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

targetType := determineJobTargetType(v)
t.Type = targetType

if isCredentialRequired(jobtargetgroups.JobTargetGroupMembershipType(v.MembershipType), targetType) {
t.RefreshCredential = pointer.To(v.JobCredentialId)
}

if targetType == jobtargetgroups.JobTargetTypeSqlDatabase {
t.DatabaseName = pointer.To(v.DatabaseName)
}

if targetType == jobtargetgroups.JobTargetTypeSqlElasticPool {
t.ElasticPoolName = pointer.To(v.ElasticPoolName)
}

targets = append(targets, t)
}

return targets
}

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
}

func determineJobTargetType(input MsSqlJobTarget) jobtargetgroups.JobTargetType {
switch {
case input.DatabaseName != "":
return jobtargetgroups.JobTargetTypeSqlDatabase
case input.ElasticPoolName != "":
return jobtargetgroups.JobTargetTypeSqlElasticPool
default:
return jobtargetgroups.JobTargetTypeSqlServer
}
}

func isCredentialRequired(membershipType jobtargetgroups.JobTargetGroupMembershipType, targetType jobtargetgroups.JobTargetType) bool {
return membershipType == jobtargetgroups.JobTargetGroupMembershipTypeInclude && targetType != jobtargetgroups.JobTargetTypeSqlDatabase
}
Loading

0 comments on commit 9d4b610

Please sign in to comment.