Skip to content

Commit

Permalink
Merge pull request #29049 from hashicorp/f-aws_auditmanager_framework…
Browse files Browse the repository at this point in the history
…_share

New Resource: `aws_auditmanager_framework_share`
  • Loading branch information
jar-b committed Jan 24, 2023
2 parents 1d0cd27 + 80d7c75 commit 2c415da
Show file tree
Hide file tree
Showing 5 changed files with 576 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/29049.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_auditmanager_framework_share
```
1 change: 1 addition & 0 deletions internal/service/auditmanager/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ var (
ResourceAssessmentReport = newResourceAssessmentReport
ResourceControl = newResourceControl
ResourceFramework = newResourceFramework
ResourceFrameworkShare = newResourceFrameworkShare
)
255 changes: 255 additions & 0 deletions internal/service/auditmanager/framework_share.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package auditmanager

import (
"context"
"errors"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/auditmanager"
awstypes "github.com/aws/aws-sdk-go-v2/service/auditmanager/types"
"github.com/hashicorp/terraform-plugin-framework/path"
"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/types"
sdkv2resource "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/enum"
"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"
)

func init() {
_sp.registerFrameworkResourceFactory(newResourceFrameworkShare)
}

func newResourceFrameworkShare(_ context.Context) (resource.ResourceWithConfigure, error) {
return &resourceFrameworkShare{}, nil
}

const (
ResNameFrameworkShare = "FrameworkShare"
)

type resourceFrameworkShare struct {
framework.ResourceWithConfigure
}

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

func (r *resourceFrameworkShare) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"comment": schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"destination_account": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"destination_region": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"framework_id": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"id": framework.IDAttribute(),
"status": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}

func (r *resourceFrameworkShare) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
conn := r.Meta().AuditManagerClient()

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

in := auditmanager.StartAssessmentFrameworkShareInput{
DestinationAccount: aws.String(plan.DestinationAccount.ValueString()),
DestinationRegion: aws.String(plan.DestinationRegion.ValueString()),
FrameworkId: aws.String(plan.FrameworkID.ValueString()),
}
if !plan.Comment.IsNull() {
in.Comment = aws.String(plan.Comment.ValueString())
}
out, err := conn.StartAssessmentFrameworkShare(ctx, &in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.AuditManager, create.ErrActionCreating, ResNameFrameworkShare, plan.FrameworkID.String(), nil),
err.Error(),
)
return
}
if out == nil || out.AssessmentFrameworkShareRequest == nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.AuditManager, create.ErrActionCreating, ResNameFrameworkShare, plan.FrameworkID.String(), nil),
errors.New("empty output").Error(),
)
return
}

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

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

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

out, err := FindFrameworkShareByID(ctx, conn, state.ID.ValueString())
if tfresource.NotFound(err) {
resp.State.RemoveResource(ctx)
return
}
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.AuditManager, create.ErrActionReading, ResNameFrameworkShare, state.ID.String(), nil),
err.Error(),
)
return
}

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

// Update is a no-op. Changing any of account, region, or framework_id will result
// a destroy and replace.
func (r *resourceFrameworkShare) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
}

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

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

// Framework share requests in certain statuses must be revoked before deletion
if CanBeRevoked(state.Status.ValueString()) {
in := auditmanager.UpdateAssessmentFrameworkShareInput{
RequestId: aws.String(state.ID.ValueString()),
RequestType: awstypes.ShareRequestTypeSent,
Action: awstypes.ShareRequestActionRevoke,
}
_, err := conn.UpdateAssessmentFrameworkShare(ctx, &in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.AuditManager, create.ErrActionDeleting, ResNameFrameworkShare, state.ID.String(), nil),
err.Error(),
)
}
}

in := auditmanager.DeleteAssessmentFrameworkShareInput{
RequestId: aws.String(state.ID.ValueString()),
RequestType: awstypes.ShareRequestTypeSent,
}
_, err := conn.DeleteAssessmentFrameworkShare(ctx, &in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.AuditManager, create.ErrActionDeleting, ResNameFrameworkShare, state.ID.String(), nil),
err.Error(),
)
}
}

func (r *resourceFrameworkShare) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}

func FindFrameworkShareByID(ctx context.Context, conn *auditmanager.Client, id string) (*awstypes.AssessmentFrameworkShareRequest, error) {
in := &auditmanager.ListAssessmentFrameworkShareRequestsInput{
RequestType: awstypes.ShareRequestTypeSent,
}
pages := auditmanager.NewListAssessmentFrameworkShareRequestsPaginator(conn, in)

for pages.HasMorePages() {
page, err := pages.NextPage(ctx)
if err != nil {
return nil, err
}

for _, share := range page.AssessmentFrameworkShareRequests {
if id == aws.ToString(share.Id) {
return &share, nil
}
}
}

return nil, &sdkv2resource.NotFoundError{
LastRequest: in,
}
}

// CanBeRevoked verifies a framework share is in a status which can be revoked
func CanBeRevoked(status string) bool {
nonRevokable := enum.Slice(
awstypes.ShareRequestStatusDeclined,
awstypes.ShareRequestStatusExpired,
awstypes.ShareRequestStatusFailed,
awstypes.ShareRequestStatusRevoked,
)
for _, s := range nonRevokable {
if s == status {
return false
}
}
return true
}

type resourceFrameworkShareData struct {
Comment types.String `tfsdk:"comment"`
DestinationAccount types.String `tfsdk:"destination_account"`
DestinationRegion types.String `tfsdk:"destination_region"`
FrameworkID types.String `tfsdk:"framework_id"`
ID types.String `tfsdk:"id"`
Status types.String `tfsdk:"status"`
}

// refreshFromOutput writes state data from an AWS response object
func (rd *resourceFrameworkShareData) refreshFromOutput(ctx context.Context, out *awstypes.AssessmentFrameworkShareRequest) {
if out == nil {
return
}

rd.Comment = flex.StringToFramework(ctx, out.Comment)
rd.DestinationAccount = flex.StringToFramework(ctx, out.DestinationAccount)
rd.DestinationRegion = flex.StringToFramework(ctx, out.DestinationRegion)
rd.FrameworkID = flex.StringToFramework(ctx, out.FrameworkId)
rd.ID = flex.StringToFramework(ctx, out.Id)
rd.Status = flex.StringValueToFramework(ctx, out.Status)
}
Loading

0 comments on commit 2c415da

Please sign in to comment.