Skip to content

Commit

Permalink
Merge pull request #28841 from hashicorp/f-aws_resourceexplorer2_view
Browse files Browse the repository at this point in the history
New resource: `aws_resourceexplorer2_view`
  • Loading branch information
ewbankkit committed Jan 18, 2023
2 parents be81729 + f4d031a commit f435aa2
Show file tree
Hide file tree
Showing 16 changed files with 1,250 additions and 17 deletions.
3 changes: 3 additions & 0 deletions .changelog/28841.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_resourceexplorer2_view
```
2 changes: 2 additions & 0 deletions .ci/.golangci2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ linters-settings:
- validation.*
# Terraform Plugin Framework
- int64validator.*
- listvalidator.*
- stringvalidator.*
- SetDefaultCreateTimeout
- SetDefaultReadTimeout
- SetDefaultUpdateTimeout
Expand Down
2 changes: 1 addition & 1 deletion internal/acctest/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func DeleteFrameworkResource(factory func(context.Context) (fwresource.ResourceW
}

for name, v := range is.Attributes {
if strings.Contains(name, ".") {
if name == "%" || strings.Contains(name, ".") {
continue
}

Expand Down
42 changes: 42 additions & 0 deletions internal/framework/attrtypes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package framework

import (
"context"
"fmt"
"reflect"

"github.com/hashicorp/terraform-plugin-framework/attr"
)

// AttributeTypes returns a map of attribute types for the specified type T.
// T must be a struct and reflection is used to find exported fields of T with the `tfsdk` tag.
func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, error) {
var t T
val := reflect.ValueOf(t)
typ := val.Type()

if typ.Kind() != reflect.Struct {
return nil, fmt.Errorf("%T has unsupported type: %s", t, typ)
}

attributeTypes := make(map[string]attr.Type)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if field.PkgPath != "" {
continue // Skip unexported fields.
}
tag := field.Tag.Get(`tfsdk`)
if tag == "-" {
continue // Skip explicitly excluded fields.
}
if tag == "" {
return nil, fmt.Errorf(`%T needs a struct tag for "tfsdk" on %s`, t, field.Name)
}

if v, ok := val.Field(i).Interface().(attr.Value); ok {
attributeTypes[tag] = v.Type(ctx)
}
}

return attributeTypes, nil
}
57 changes: 57 additions & 0 deletions internal/framework/attrtypes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package framework_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
)

func TestAttributeTypes(t *testing.T) {
t.Parallel()

type struct1 struct{} //nolint:unused // Used as a type parameter.
type struct2 struct { //nolint:unused // Used as a type parameter.
ARN types.String `tfsdk:"arn"`
ID types.Int64 `tfsdk:"id"`
IncludeProperty types.Bool `tfsdk:"include_property"`
}

ctx := context.Background()
got, err := framework.AttributeTypes[struct1](ctx)

if err != nil {
t.Fatalf("unexpected error")
}

wanted := map[string]attr.Type{}

if diff := cmp.Diff(got, wanted); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}

_, err = framework.AttributeTypes[int](ctx)

if err == nil {
t.Fatalf("expected error")
}

got, err = framework.AttributeTypes[struct2](ctx)

if err != nil {
t.Fatalf("unexpected error")
}

wanted = map[string]attr.Type{
"arn": types.StringType,
"id": types.Int64Type,
"include_property": types.BoolType,
}

if diff := cmp.Diff(got, wanted); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}
}
20 changes: 20 additions & 0 deletions internal/framework/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,26 @@ func (r *ResourceWithConfigure) Configure(_ context.Context, request resource.Co
}
}

// ExpandTags returns the API tags for the specified "tags" value.
func (r *ResourceWithConfigure) ExpandTags(ctx context.Context, tags types.Map) tftags.KeyValueTags {
return r.Meta().DefaultTagsConfig.MergeTags(tftags.New(tags))
}

// FlattenTags returns the "tags" value from the specified API tags.
func (r *ResourceWithConfigure) FlattenTags(ctx context.Context, apiTags tftags.KeyValueTags) types.Map {
// AWS APIs often return empty lists of tags when none have been configured.
if v := apiTags.IgnoreAWS().IgnoreConfig(r.Meta().IgnoreTagsConfig).RemoveDefaultConfig(r.Meta().DefaultTagsConfig).Map(); len(v) == 0 {
return tftags.Null
} else {
return flex.FlattenFrameworkStringValueMapLegacy(ctx, v)
}
}

// FlattenTagsAll returns the "tags_all" value from the specified API tags.
func (r *ResourceWithConfigure) FlattenTagsAll(ctx context.Context, apiTags tftags.KeyValueTags) types.Map {
return flex.FlattenFrameworkStringValueMapLegacy(ctx, apiTags.IgnoreAWS().IgnoreConfig(r.Meta().IgnoreTagsConfig).Map())
}

// SetTagsAll calculates the new value for the `tags_all` attribute.
func (r *ResourceWithConfigure) SetTagsAll(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) {
defaultTagsConfig := r.Meta().DefaultTagsConfig
Expand Down
42 changes: 42 additions & 0 deletions internal/framework/boolplanmodifier/default_value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package boolplanmodifier

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type defaultValue struct {
val bool
}

// DefaultValue return a bool plan modifier that sets the specified value if the planned value is Null.
func DefaultValue(b bool) planmodifier.Bool {
return defaultValue{
val: b,
}
}

func (m defaultValue) Description(context.Context) string {
return fmt.Sprintf("If value is not configured, defaults to %t", m.val)
}

func (m defaultValue) MarkdownDescription(ctx context.Context) string {
return m.Description(ctx)
}

func (m defaultValue) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) {
if !req.ConfigValue.IsNull() {
return
}

// If the attribute plan is "known" and "not null", then a previous plan modifier in the sequence
// has already been applied, and we don't want to interfere.
if !req.PlanValue.IsUnknown() && !req.PlanValue.IsNull() {
return
}

resp.PlanValue = types.BoolValue(m.val)
}
67 changes: 67 additions & 0 deletions internal/framework/boolplanmodifier/default_value_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package boolplanmodifier

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestDefaultValue(t *testing.T) {
t.Parallel()

type testCase struct {
plannedValue types.Bool
currentValue types.Bool
defaultValue bool
expectedValue types.Bool
expectError bool
}
tests := map[string]testCase{
"default bool": {
plannedValue: types.BoolNull(),
currentValue: types.BoolValue(true),
defaultValue: true,
expectedValue: types.BoolValue(true),
},
"default bool on create": {
plannedValue: types.BoolNull(),
currentValue: types.BoolNull(),
defaultValue: true,
expectedValue: types.BoolValue(true),
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
t.Parallel()

ctx := context.Background()
request := planmodifier.BoolRequest{
Path: path.Root("test"),
PlanValue: test.plannedValue,
StateValue: test.currentValue,
}
response := planmodifier.BoolResponse{
PlanValue: request.PlanValue,
}
DefaultValue(test.defaultValue).PlanModifyBool(ctx, request, &response)

if !response.Diagnostics.HasError() && test.expectError {
t.Fatal("expected error, got no error")
}

if response.Diagnostics.HasError() && !test.expectError {
t.Fatalf("got unexpected error: %s", response.Diagnostics)
}

if diff := cmp.Diff(response.PlanValue, test.expectedValue); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}
})
}
}
2 changes: 2 additions & 0 deletions internal/service/resourceexplorer2/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ package resourceexplorer2
// Exports for use in tests only.
var (
FindIndex = findIndex
FindViewByARN = findViewByARN
ResourceIndex = newResourceIndex
ResourceView = newResourceView
)
23 changes: 7 additions & 16 deletions internal/service/resourceexplorer2/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,8 @@ func (r *resourceIndex) Create(ctx context.Context, request resource.CreateReque
}

conn := r.Meta().ResourceExplorer2Client()
defaultTagsConfig := r.Meta().DefaultTagsConfig
ignoreTagsConfig := r.Meta().IgnoreTagsConfig
tags := defaultTagsConfig.MergeTags(tftags.New(data.Tags))

tags := r.ExpandTags(ctx, data.Tags)
input := &resourceexplorer2.CreateIndexInput{
ClientToken: aws.String(sdkresource.UniqueId()),
}
Expand Down Expand Up @@ -141,7 +139,7 @@ func (r *resourceIndex) Create(ctx context.Context, request resource.CreateReque

// Set values for unknowns.
data.ARN = types.StringValue(arn)
data.TagsAll = flex.FlattenFrameworkStringValueMapLegacy(ctx, tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map())
data.TagsAll = r.FlattenTagsAll(ctx, tags)

response.Diagnostics.Append(response.State.Set(ctx, &data)...)
}
Expand All @@ -156,8 +154,6 @@ func (r *resourceIndex) Read(ctx context.Context, request resource.ReadRequest,
}

conn := r.Meta().ResourceExplorer2Client()
defaultTagsConfig := r.Meta().DefaultTagsConfig
ignoreTagsConfig := r.Meta().IgnoreTagsConfig

output, err := findIndex(ctx, conn)

Expand All @@ -175,16 +171,11 @@ func (r *resourceIndex) Read(ctx context.Context, request resource.ReadRequest,
}

data.ARN = flex.StringToFramework(ctx, output.Arn)
data.Type = types.StringValue(string(output.Type))

tags := KeyValueTags(output.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)
// AWS APIs often return empty lists of tags when none have been configured.
if tags := tags.RemoveDefaultConfig(defaultTagsConfig).Map(); len(tags) == 0 {
data.Tags = tftags.Null
} else {
data.Tags = flex.FlattenFrameworkStringValueMapLegacy(ctx, tags)
}
data.TagsAll = flex.FlattenFrameworkStringValueMapLegacy(ctx, tags.Map())
data.Type = flex.StringValueToFramework(ctx, output.Type)

apiTags := KeyValueTags(output.Tags)
data.Tags = r.FlattenTags(ctx, apiTags)
data.TagsAll = r.FlattenTagsAll(ctx, apiTags)

response.Diagnostics.Append(response.State.Set(ctx, &data)...)
}
Expand Down
7 changes: 7 additions & 0 deletions internal/service/resourceexplorer2/resourceexplorer2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ func TestAccResourceExplorer2_serial(t *testing.T) {
"tags": testAccIndex_tags,
"type": testAccIndex_type,
},
"View": {
"basic": testAccView_basic,
"defaultView": testAccView_defaultView,
"disappears": testAccView_disappears,
"filter": testAccView_filter,
"tags": testAccView_tags,
},
}

acctest.RunSerialTests2Levels(t, testCases, 0)
Expand Down
60 changes: 60 additions & 0 deletions internal/service/resourceexplorer2/sweep.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//go:build sweep
// +build sweep

package resourceexplorer2

import (
"context"
"fmt"
"log"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/resourceexplorer2"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/sweep"
)

func init() {
resource.AddTestSweepers("aws_resourceexplorer2_index", &resource.Sweeper{
Name: "aws_resourceexplorer2_index",
F: sweepIndexes,
})
}

func sweepIndexes(region string) error {
client, err := sweep.SharedRegionalSweepClient(region)
if err != nil {
return fmt.Errorf("error getting client: %s", err)
}
conn := client.(*conns.AWSClient).ResourceExplorer2Client()
input := &resourceexplorer2.ListIndexesInput{}
sweepResources := make([]sweep.Sweepable, 0)
ctx := context.Background()

pages := resourceexplorer2.NewListIndexesPaginator(conn, input)
for pages.HasMorePages() {
page, err := pages.NextPage(ctx)

if sweep.SkipSweepError(err) {
log.Printf("[WARN] Skipping Resource Explorer Index sweep for %s: %s", region, err)
return nil
}

if err != nil {
return fmt.Errorf("error listing Resource Explorer Indexes (%s): %w", region, err)
}

for _, v := range page.Indexes {
sweepResources = append(sweepResources, sweep.NewSweepFrameworkResource(newResourceIndex, aws.ToString(v.Arn), client))
}
}

err = sweep.SweepOrchestrator(sweepResources)

if err != nil {
return fmt.Errorf("error sweeping Resource Explorer Indexes (%s): %w", region, err)
}

return nil
}
Loading

0 comments on commit f435aa2

Please sign in to comment.