Skip to content

Commit

Permalink
New Resource: azurerm_cognitive_account_rai_policy (#28013)
Browse files Browse the repository at this point in the history
* Add new resource `azurerm_cognitive_account_rai_policy`

* Optimize code and dependancies for RAI policy

* Resolve golint issues by pre-allocation

* Apply suggestions from code review

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

* Update according to review comment.

* Change custom_blocklist.name to custom_blocklist.id

* Rename custom_blocklist.id to custom_blocklist.rai_blocklist_id as suggested

* Remove custom_blocklist from the RAI Policy resource

* Resolve lint issue

---------

Co-authored-by: stephybun <[email protected]>
  • Loading branch information
liuwuliuyun and stephybun authored Jan 30, 2025
1 parent b094a9e commit 9375225
Show file tree
Hide file tree
Showing 21 changed files with 1,581 additions and 0 deletions.
9 changes: 9 additions & 0 deletions internal/services/cognitive/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
"github.com/hashicorp/go-azure-sdk/resource-manager/cognitive/2024-10-01/cognitiveservicesaccounts"
"github.com/hashicorp/go-azure-sdk/resource-manager/cognitive/2024-10-01/deployments"
"github.com/hashicorp/go-azure-sdk/resource-manager/cognitive/2024-10-01/raiblocklists"
"github.com/hashicorp/go-azure-sdk/resource-manager/cognitive/2024-10-01/raipolicies"
"github.com/hashicorp/terraform-provider-azurerm/internal/common"
)

type Client struct {
AccountsClient *cognitiveservicesaccounts.CognitiveServicesAccountsClient
DeploymentsClient *deployments.DeploymentsClient
RaiBlocklistsClient *raiblocklists.RaiBlocklistsClient
RaiPoliciesClient *raipolicies.RaiPoliciesClient
}

func NewClient(o *common.ClientOptions) (*Client, error) {
Expand All @@ -31,6 +33,12 @@ func NewClient(o *common.ClientOptions) (*Client, error) {
}
o.Configure(deploymentsClient.Client, o.Authorizers.ResourceManager)

raiPoliciesClient, err := raipolicies.NewRaiPoliciesClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building Rai Policies client: %+v", err)
}
o.Configure(raiPoliciesClient.Client, o.Authorizers.ResourceManager)

raiBlobklistsClient, err := raiblocklists.NewRaiBlocklistsClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building Rai Blocklists client: %+v", err)
Expand All @@ -41,5 +49,6 @@ func NewClient(o *common.ClientOptions) (*Client, error) {
AccountsClient: accountsClient,
DeploymentsClient: deploymentsClient,
RaiBlocklistsClient: raiBlobklistsClient,
RaiPoliciesClient: raiPoliciesClient,
}, nil
}
338 changes: 338 additions & 0 deletions internal/services/cognitive/cognitive_account_rai_policy_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
package cognitive

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-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-sdk/resource-manager/cognitive/2024-10-01/raipolicies"
"github.com/hashicorp/terraform-provider-azurerm/internal/locks"
"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"
)

var _ sdk.ResourceWithUpdate = &CognitiveAccountRaiPolicyResource{}

type CognitiveAccountRaiPolicyResource struct{}

type AccountRaiPolicyContentFilter struct {
Name string `tfschema:"name"`
FilterEnabled bool `tfschema:"filter_enabled"`
BlockEnabled bool `tfschema:"block_enabled"`
SeverityThreshold string `tfschema:"severity_threshold"`
Source string `tfschema:"source"`
}

type AccountRaiPolicyCustomBlock struct {
Id string `tfschema:"rai_blocklist_id"`
BlockEnabled bool `tfschema:"block_enabled"`
Source string `tfschema:"source"`
}

type AccountRaiPolicyResourceModel struct {
Name string `tfschema:"name"`
AccountId string `tfschema:"cognitive_account_id"`
BasePolicyName string `tfschema:"base_policy_name"`
ContentFilter []AccountRaiPolicyContentFilter `tfschema:"content_filter"`
Mode string `tfschema:"mode"`
Tags map[string]string `tfschema:"tags"`
}

func (r CognitiveAccountRaiPolicyResource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},

"cognitive_account_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: raipolicies.ValidateAccountID,
},

"base_policy_name": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},

"content_filter": {
Type: pluginsdk.TypeList,
Required: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringIsNotEmpty,
},
"filter_enabled": {
Type: pluginsdk.TypeBool,
Required: true,
},
"block_enabled": {
Type: pluginsdk.TypeBool,
Required: true,
},
"severity_threshold": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(raipolicies.PossibleValuesForContentLevel(), false),
},
"source": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(raipolicies.PossibleValuesForRaiPolicyContentSource(), false),
},
},
},
},

"mode": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(raipolicies.PossibleValuesForRaiPolicyMode(), false),
},

"tags": commonschema.Tags(),
}
}

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

func (r CognitiveAccountRaiPolicyResource) ModelObject() interface{} {
return &AccountRaiPolicyResourceModel{}
}

func (r CognitiveAccountRaiPolicyResource) ResourceType() string {
return "azurerm_cognitive_account_rai_policy"
}

func (r CognitiveAccountRaiPolicyResource) Create() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.Cognitive.RaiPoliciesClient
subscriptionId := metadata.Client.Account.SubscriptionId

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

cognitiveAccountId, err := raipolicies.ParseAccountID(model.AccountId)
if err != nil {
return err
}

id := raipolicies.NewRaiPolicyID(subscriptionId, cognitiveAccountId.ResourceGroupName, cognitiveAccountId.AccountName, model.Name)
existing, err := client.Get(ctx, id)
if err != nil {
if !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)
}

locks.ByID(cognitiveAccountId.ID())
defer locks.UnlockByID(cognitiveAccountId.ID())

raiPolicy := raipolicies.RaiPolicy{
Name: pointer.To(model.Name),
Properties: &raipolicies.RaiPolicyProperties{
BasePolicyName: pointer.To(model.BasePolicyName),
ContentFilters: expandRaiPolicyContentFilters(model.ContentFilter),
},
Tags: pointer.To(model.Tags),
}

if model.Mode != "" {
raiPolicy.Properties.Mode = pointer.To(raipolicies.RaiPolicyMode(model.Mode))
}

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

metadata.SetID(id)

return nil
},
}
}

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

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

cognitiveAccountId := raipolicies.NewAccountID(id.SubscriptionId, id.ResourceGroupName, id.AccountName)

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 := AccountRaiPolicyResourceModel{
Name: id.RaiPolicyName,
AccountId: cognitiveAccountId.ID(),
}

if model := resp.Model; model != nil {
state.Tags = pointer.From(model.Tags)

if props := model.Properties; props != nil {
state.BasePolicyName = pointer.From(props.BasePolicyName)
state.ContentFilter = flattenRaiPolicyContentFilters(props.ContentFilters)
state.Mode = string(pointer.From(props.Mode))
}
}

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

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

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

var model AccountRaiPolicyResourceModel
if err := metadata.Decode(&model); err != nil {
return 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: `properties` was nil", id)
}

cognitiveAccountId := raipolicies.NewAccountID(id.SubscriptionId, id.ResourceGroupName, id.AccountName)

locks.ByID(cognitiveAccountId.ID())
defer locks.UnlockByID(cognitiveAccountId.ID())

payload := existing.Model

if metadata.ResourceData.HasChange("content_filter") {
payload.Properties.ContentFilters = expandRaiPolicyContentFilters(model.ContentFilter)
}

if metadata.ResourceData.HasChange("mode") {
payload.Properties.Mode = pointer.To(raipolicies.RaiPolicyMode(model.Mode))
}

if metadata.ResourceData.HasChange("tags") {
payload.Tags = pointer.To(model.Tags)
}

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

return nil
},
}
}

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

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

cognitiveAccountId := raipolicies.NewAccountID(id.SubscriptionId, id.ResourceGroupName, id.AccountName)

locks.ByID(cognitiveAccountId.ID())
defer locks.UnlockByID(cognitiveAccountId.ID())

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

return nil
},
}
}

func (r CognitiveAccountRaiPolicyResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return raipolicies.ValidateRaiPolicyID
}

func expandRaiPolicyContentFilters(filters []AccountRaiPolicyContentFilter) *[]raipolicies.RaiPolicyContentFilter {
if filters == nil {
return nil
}

contentFilters := make([]raipolicies.RaiPolicyContentFilter, 0, len(filters))
for _, filter := range filters {
contentFilters = append(contentFilters, raipolicies.RaiPolicyContentFilter{
Name: pointer.To(filter.Name),
Enabled: pointer.To(filter.FilterEnabled),
Blocking: pointer.To(filter.BlockEnabled),
SeverityThreshold: pointer.To(raipolicies.ContentLevel(filter.SeverityThreshold)),
Source: pointer.To(raipolicies.RaiPolicyContentSource(filter.Source)),
})
}
return &contentFilters
}

func flattenRaiPolicyContentFilters(filters *[]raipolicies.RaiPolicyContentFilter) []AccountRaiPolicyContentFilter {
contentFilters := make([]AccountRaiPolicyContentFilter, 0)
if filters == nil {
return contentFilters
}

for _, filter := range *filters {
contentFilters = append(contentFilters, AccountRaiPolicyContentFilter{
Name: pointer.From(filter.Name),
FilterEnabled: pointer.From(filter.Enabled),
BlockEnabled: pointer.From(filter.Blocking),
SeverityThreshold: string(pointer.From(filter.SeverityThreshold)),
Source: string(pointer.From(filter.Source)),
})
}
return contentFilters
}
Loading

0 comments on commit 9375225

Please sign in to comment.