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: aws_auditmanager_framework_share #29049

Merged
merged 4 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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