Skip to content

Commit

Permalink
Merge pull request #28518 from mattburgess/opensearchserverless-acces…
Browse files Browse the repository at this point in the history
…s-policy-resource

Add aws_opensearchserverless_access_policy resource
  • Loading branch information
johnsonaj authored Jun 12, 2023
2 parents 443dc17 + 35e0cbe commit b5d1282
Show file tree
Hide file tree
Showing 9 changed files with 637 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .changelog/28518.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_opensearchserverless_access_policy
```
264 changes: 264 additions & 0 deletions internal/service/opensearchserverless/access_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
package opensearchserverless

import (
"context"
"errors"
"fmt"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/opensearchserverless"
awstypes "github.com/aws/aws-sdk-go-v2/service/opensearchserverless/types"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/id"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/enum"
"github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @FrameworkResource
func newResourceAccessPolicy(_ context.Context) (resource.ResourceWithConfigure, error) {
return &resourceAccessPolicy{}, nil
}

type resourceAccessPolicyData struct {
Description types.String `tfsdk:"description"`
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Policy types.String `tfsdk:"policy"`
PolicyVersion types.String `tfsdk:"policy_version"`
Type types.String `tfsdk:"type"`
}

const (
ResNameAccessPolicy = "Access Policy"
)

type resourceAccessPolicy struct {
framework.ResourceWithConfigure
}

func (r *resourceAccessPolicy) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = "aws_opensearchserverless_access_policy"
}

func (r *resourceAccessPolicy) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"description": schema.StringAttribute{
Optional: true,
Validators: []validator.String{
stringvalidator.LengthBetween(1, 1000),
},
},
"id": framework.IDAttribute(),
"name": schema.StringAttribute{
Required: true,
Validators: []validator.String{
stringvalidator.LengthBetween(3, 32),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"policy": schema.StringAttribute{
Required: true,
Validators: []validator.String{
stringvalidator.LengthBetween(1, 20480),
},
},
"policy_version": schema.StringAttribute{
Computed: true,
},
"type": schema.StringAttribute{
Required: true,
Validators: []validator.String{
enum.FrameworkValidate[awstypes.AccessPolicyType](),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
}
}

func (r *resourceAccessPolicy) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan resourceAccessPolicyData

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)

if resp.Diagnostics.HasError() {
return
}

conn := r.Meta().OpenSearchServerlessClient(ctx)

in := &opensearchserverless.CreateAccessPolicyInput{
ClientToken: aws.String(id.UniqueId()),
Name: aws.String(plan.Name.ValueString()),
Policy: aws.String(plan.Policy.ValueString()),
Type: awstypes.AccessPolicyType(plan.Type.ValueString()),
}

if !plan.Description.IsNull() {
in.Description = aws.String(plan.Description.ValueString())
}

out, err := conn.CreateAccessPolicy(ctx, in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.OpenSearchServerless, create.ErrActionCreating, ResNameAccessPolicy, plan.Name.String(), nil),
err.Error(),
)
return
}

state := plan
resp.Diagnostics.Append(state.refreshFromOutput(ctx, out.AccessPolicyDetail)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *resourceAccessPolicy) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
conn := r.Meta().OpenSearchServerlessClient(ctx)

var state resourceAccessPolicyData
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

out, err := FindAccessPolicyByNameAndType(ctx, conn, state.ID.ValueString(), state.Type.ValueString())
if tfresource.NotFound(err) {
resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err))
resp.State.RemoveResource(ctx)
return
}

resp.Diagnostics.Append(state.refreshFromOutput(ctx, out)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *resourceAccessPolicy) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
conn := r.Meta().OpenSearchServerlessClient(ctx)

var plan, state resourceAccessPolicyData
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

if !plan.Description.Equal(state.Description) ||
!plan.Policy.Equal(state.Policy) {
input := &opensearchserverless.UpdateAccessPolicyInput{
ClientToken: aws.String(id.UniqueId()),
Name: flex.StringFromFramework(ctx, plan.Name),
PolicyVersion: flex.StringFromFramework(ctx, state.PolicyVersion),
Type: awstypes.AccessPolicyType(plan.Type.ValueString()),
}

if !plan.Description.Equal(state.Description) {
input.Description = aws.String(plan.Description.ValueString())
}

if !plan.Policy.Equal(state.Policy) {
input.Policy = aws.String(plan.Policy.ValueString())
}

out, err := conn.UpdateAccessPolicy(ctx, input)

if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("updating Security Policy (%s)", plan.Name.ValueString()), err.Error())
return
}
resp.Diagnostics.Append(state.refreshFromOutput(ctx, out.AccessPolicyDetail)...)
}

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *resourceAccessPolicy) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
conn := r.Meta().OpenSearchServerlessClient(ctx)

var state resourceAccessPolicyData
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

_, err := conn.DeleteAccessPolicy(ctx, &opensearchserverless.DeleteAccessPolicyInput{
ClientToken: aws.String(id.UniqueId()),
Name: aws.String(state.Name.ValueString()),
Type: awstypes.AccessPolicyType(state.Type.ValueString()),
})
if err != nil {
var nfe *awstypes.ResourceNotFoundException
if errors.As(err, &nfe) {
return
}
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.OpenSearchServerless, create.ErrActionDeleting, ResNameAccessPolicy, state.Name.String(), nil),
err.Error(),
)
}
}

func (r *resourceAccessPolicy) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
parts := strings.Split(req.ID, idSeparator)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
err := fmt.Errorf("unexpected format for ID (%[1]s), expected security-policy-name%[2]ssecurity-policy-type", req.ID, idSeparator)
resp.Diagnostics.AddError(fmt.Sprintf("importing Security Policy (%s)", req.ID), err.Error())
return
}

state := resourceAccessPolicyData{
ID: types.StringValue(parts[0]),
Name: types.StringValue(parts[0]),
Type: types.StringValue(parts[1]),
}

diags := resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}

// refreshFromOutput writes state data from an AWS response object
func (rd *resourceAccessPolicyData) refreshFromOutput(ctx context.Context, out *awstypes.AccessPolicyDetail) diag.Diagnostics {
var diags diag.Diagnostics

if out == nil {
return diags
}

rd.ID = flex.StringToFramework(ctx, out.Name)
rd.Description = flex.StringToFramework(ctx, out.Description)
rd.Name = flex.StringToFramework(ctx, out.Name)
rd.Type = flex.StringValueToFramework(ctx, out.Type)
rd.PolicyVersion = flex.StringToFramework(ctx, out.PolicyVersion)

policyBytes, err := out.Policy.MarshalSmithyDocument()
if err != nil {
diags.AddError(fmt.Sprintf("refreshing state for Security Policy (%s)", rd.Name), err.Error())
return diags
}

p := string(policyBytes)

rd.Policy = flex.StringToFramework(ctx, &p)

return diags
}
Loading

0 comments on commit b5d1282

Please sign in to comment.