diff --git a/.ci/semgrep/tflog/tflog.go b/.ci/semgrep/tflog/tflog.go new file mode 100644 index 000000000000..a61c7e8ebcb0 --- /dev/null +++ b/.ci/semgrep/tflog/tflog.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func noAssignment() { + ctx := context.Background() + + // ruleid: setfield-without-assign + tflog.SetField(ctx, "field", "value") +} + +func assigned() { + ctx := context.Background() + + // ok: setfield-without-assign + ctx = tflog.SetField(ctx, "field", "value") +} + +func returnedContext() context.Context { + ctx := context.Background() + + // ok: setfield-without-assign + return tflog.SetField(ctx, "field", "value") +} + +func declareAndAssign_SameName() { + ctx := context.Background() + + for i := 0; i < 1; i++ { + // ok: setfield-without-assign + ctx := tflog.SetField(ctx, "field", "value") + } +} + +func declareAndAssign_Rename() { + outerCtx := context.Background() + + for i := 0; i < 1; i++ { + // ok: setfield-without-assign + innerCtx := tflog.SetField(outerCtx, "field", "value") + } +} diff --git a/.ci/semgrep/tflog/tflog.yml b/.ci/semgrep/tflog/tflog.yml index 431c83c360a0..a1b2da59cf2b 100644 --- a/.ci/semgrep/tflog/tflog.yml +++ b/.ci/semgrep/tflog/tflog.yml @@ -4,6 +4,6 @@ rules: message: The return value of "tflog.SetField" must be used patterns: - pattern: tflog.SetField(...) - - pattern-not-inside: $CTX = tflog.SetField($CTX, ...) + - pattern-not-inside: $CTX1 = tflog.SetField($CTX2, ...) - pattern-not-inside: return tflog.SetField($CTX, ...) severity: ERROR diff --git a/go.mod b/go.mod index 013d61a84b1b..3f1aa3c66f5a 100644 --- a/go.mod +++ b/go.mod @@ -310,6 +310,8 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/pquerna/otp v1.5.0 github.com/shopspring/decimal v1.4.0 + go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.63.0 + go.opentelemetry.io/otel v1.38.0 golang.org/x/crypto v0.43.0 golang.org/x/text v0.30.0 golang.org/x/tools v0.38.0 @@ -370,8 +372,6 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/zclconf/go-cty v1.17.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect golang.org/x/exp v0.0.0-20220921023135-46d9e7742f1e // indirect @@ -386,3 +386,5 @@ require ( ) replace github.com/hashicorp/terraform-plugin-log => github.com/gdavison/terraform-plugin-log v0.0.0-20230928191232-6c653d8ef8fb + +replace github.com/hashicorp/terraform-plugin-testing => github.com/gdavison/terraform-plugin-testing v0.0.0-20251008214752-cb22fd14f84d diff --git a/go.sum b/go.sum index 0659acfcbd97..1784be1d460e 100644 --- a/go.sum +++ b/go.sum @@ -596,6 +596,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/gdavison/terraform-plugin-log v0.0.0-20230928191232-6c653d8ef8fb h1:HM67IMNxlkqGxAM5ymxMg2ANCcbL4oEr5cy+tGZ6fNo= github.com/gdavison/terraform-plugin-log v0.0.0-20230928191232-6c653d8ef8fb/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/gdavison/terraform-plugin-testing v0.0.0-20251008214752-cb22fd14f84d h1:m3Al87LjSaNb/GWqj//HUPwYlkhxTpYO7Z5nlSF3dpY= +github.com/gdavison/terraform-plugin-testing v0.0.0-20251008214752-cb22fd14f84d/go.mod h1:LEK/JDcSM5eupLL8D0j/CIarlOTrl2wSmQE20mX/xSU= github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA= github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -685,8 +687,6 @@ github.com/hashicorp/terraform-plugin-mux v0.21.0 h1:QsEYnzSD2c3zT8zUrUGqaFGhV/Z github.com/hashicorp/terraform-plugin-mux v0.21.0/go.mod h1:Qpt8+6AD7NmL0DS7ASkN0EXpDQ2J/FnnIgeUr1tzr5A= github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 h1:mlAq/OrMlg04IuJT7NpefI1wwtdpWudnEmjuQs04t/4= github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1/go.mod h1:GQhpKVvvuwzD79e8/NZ+xzj+ZpWovdPAe8nfV/skwNU= -github.com/hashicorp/terraform-plugin-testing v1.14.0-beta.1 h1:caWmY2Fv/KgDAXU7IVjcBDfIdmr/n6VRYhCLxNmlaXs= -github.com/hashicorp/terraform-plugin-testing v1.14.0-beta.1/go.mod h1:jVm3pD9uQAT0X2RSEdcqjju2bCGv5f73DGZFU4v7EAU= github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= diff --git a/internal/acctest/knownvalue/account_id.go b/internal/acctest/knownvalue/account_id.go index 8266c6b09314..7c53fde8e9bd 100644 --- a/internal/acctest/knownvalue/account_id.go +++ b/internal/acctest/knownvalue/account_id.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package statecheck +package knownvalue import ( "context" @@ -34,7 +34,7 @@ func (v accountID) CheckValue(other any) error { // String returns the string representation of the value. func (v accountID) String() string { - return "Who Knows" + return "Account ID" } func AccountID() knownvalue.Check { diff --git a/internal/acctest/knownvalue/global_arn_exact.go b/internal/acctest/knownvalue/global_arn_exact.go index 2030dd5538b9..9abce86d0541 100644 --- a/internal/acctest/knownvalue/global_arn_exact.go +++ b/internal/acctest/knownvalue/global_arn_exact.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package statecheck +package knownvalue import ( "context" diff --git a/internal/acctest/knownvalue/global_arn_regexp.go b/internal/acctest/knownvalue/global_arn_regexp.go index 5b37a40affe6..2aa86638b8d3 100644 --- a/internal/acctest/knownvalue/global_arn_regexp.go +++ b/internal/acctest/knownvalue/global_arn_regexp.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package statecheck +package knownvalue import ( "context" diff --git a/internal/acctest/knownvalue/regional_arn_exact.go b/internal/acctest/knownvalue/regional_arn_exact.go index f5a9b7f9c1af..92e777508616 100644 --- a/internal/acctest/knownvalue/regional_arn_exact.go +++ b/internal/acctest/knownvalue/regional_arn_exact.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package statecheck +package knownvalue import ( "context" diff --git a/internal/acctest/knownvalue/regional_arn_regexp.go b/internal/acctest/knownvalue/regional_arn_regexp.go index a5b5211862d5..432f4f8d826e 100644 --- a/internal/acctest/knownvalue/regional_arn_regexp.go +++ b/internal/acctest/knownvalue/regional_arn_regexp.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package statecheck +package knownvalue import ( "context" diff --git a/internal/acctest/knownvalue/regional_arn_regexp_ignore_account.go b/internal/acctest/knownvalue/regional_arn_regexp_ignore_account.go index 836f2a8511cd..73069d8449c4 100644 --- a/internal/acctest/knownvalue/regional_arn_regexp_ignore_account.go +++ b/internal/acctest/knownvalue/regional_arn_regexp_ignore_account.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package statecheck +package knownvalue import ( "fmt" diff --git a/internal/acctest/knownvalue/string_ptr_exact.go b/internal/acctest/knownvalue/string_ptr_exact.go new file mode 100644 index 000000000000..ead0478afd42 --- /dev/null +++ b/internal/acctest/knownvalue/string_ptr_exact.go @@ -0,0 +1,46 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" +) + +var _ knownvalue.Check = stringPtrExact[string]{} + +type stringPtrExact[T ~string] struct { + value *T +} + +func (v stringPtrExact[T]) CheckValue(other any) error { + otherVal, ok := other.(string) + + if !ok { + return fmt.Errorf("expected string value for StringPtrExact check, got: %T", other) + } + + if otherVal != string(*v.value) { + return fmt.Errorf("expected value %s for StringPtrExact check, got: %s", *v.value, otherVal) + } + + return nil +} + +// String returns the string representation of the value. +func (v stringPtrExact[T]) String() string { + return string(*v.value) +} + +// StringExact returns a Check for asserting equality between the +// supplied string and a value passed to the CheckValue method. +func StringPtrExact[T ~string](value *T) stringPtrExact[T] { + if value == nil { + panic("value must not be nil") + } + return stringPtrExact[T]{ + value: value, + } +} diff --git a/internal/acctest/knownvalue/stringable_value.go b/internal/acctest/knownvalue/stringable_value.go index 53d828d0c13e..ed1ec293ae3f 100644 --- a/internal/acctest/knownvalue/stringable_value.go +++ b/internal/acctest/knownvalue/stringable_value.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package statecheck +package knownvalue import "github.com/hashicorp/terraform-plugin-testing/knownvalue" diff --git a/internal/acctest/statecheck/state_value.go b/internal/acctest/statecheck/state_value.go new file mode 100644 index 000000000000..adf42ca47bc6 --- /dev/null +++ b/internal/acctest/statecheck/state_value.go @@ -0,0 +1,106 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +type stateValue struct { + resourceAddress string + attributePath tfjsonpath.Path + value *string +} + +func StateValue() stateValue { + return stateValue{} +} + +// GetStateValue sets the resource address and attribute path to check and stores the state value. +// Calls to GetStateValue occur before any TestStep is run. +func (v *stateValue) GetStateValue(resourceAddress string, attributePath tfjsonpath.Path) statecheck.StateCheck { + v.resourceAddress = resourceAddress + v.attributePath = attributePath + + return newStateValueStateChecker(v) +} + +// Value checks the stored state value against the provided value. +// Calls to Value occur before any TestStep is run. +func (v *stateValue) Value() knownvalue.Check { + return newStateValueKnownValueChecker(v) +} + +type stateValueStateChecker struct { + base Base + stateValue *stateValue +} + +func newStateValueStateChecker(stateValue *stateValue) stateValueStateChecker { + return stateValueStateChecker{ + base: NewBase(stateValue.resourceAddress), + stateValue: stateValue, + } +} + +func (vc stateValueStateChecker) CheckState(ctx context.Context, request statecheck.CheckStateRequest, response *statecheck.CheckStateResponse) { + resource, ok := vc.base.ResourceFromState(request, response) + if !ok { + return + } + + value, err := tfjsonpath.Traverse(resource.AttributeValues, vc.stateValue.attributePath) + if err != nil { + response.Error = err + return + } + + stringVal, ok := value.(string) + if !ok { + response.Error = fmt.Errorf("expected string value for StateValue check, got: %T", value) + return + } + + vc.stateValue.value = &stringVal +} + +type stateValueKnownValueChecker struct { + stateValue *stateValue +} + +func newStateValueKnownValueChecker(stateValue *stateValue) stateValueKnownValueChecker { + return stateValueKnownValueChecker{ + stateValue: stateValue, + } +} + +func (vc stateValueKnownValueChecker) CheckValue(other any) error { + if vc.stateValue.value == nil { + return fmt.Errorf("state value has not been set") + } + + otherVal, ok := other.(string) + + if !ok { + return fmt.Errorf("expected string value for StateValue check, got: %T", other) + } + + if otherVal != *vc.stateValue.value { + return fmt.Errorf("expected value %s for StateValue check, got: %s", *vc.stateValue.value, otherVal) + } + + return nil +} + +func (vc stateValueKnownValueChecker) String() string { + if vc.stateValue.value == nil { + return "error: state value has not been set" + } + return fmt.Sprintf("%s (from state: %q %q)", *vc.stateValue.value, vc.stateValue.resourceAddress, vc.stateValue.attributePath.String()) +} diff --git a/internal/acctest/statecheck/state_value_test.go b/internal/acctest/statecheck/state_value_test.go new file mode 100644 index 000000000000..a709b83ca1c6 --- /dev/null +++ b/internal/acctest/statecheck/state_value_test.go @@ -0,0 +1,227 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "testing" + + "github.com/YakDriver/regexache" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestStateValue_ValuesSame(t *testing.T) { + t.Parallel() + + stateValue := StateValue() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "same" + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + stateValue.GetStateValue("test_resource.one", tfjsonpath.New("string_attribute")), + }, + }, + { + Config: `resource "test_resource" "one" { + string_attribute = "same" + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("test_resource.one", tfjsonpath.New("string_attribute"), stateValue.Value()), + }, + }, + }, + }) +} + +func TestStateValue_ValuesNotSame(t *testing.T) { + t.Parallel() + + stateValue := StateValue() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "same" + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + stateValue.GetStateValue("test_resource.one", tfjsonpath.New("string_attribute")), + }, + }, + { + Config: `resource "test_resource" "one" { + string_attribute = "not same" + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("test_resource.one", tfjsonpath.New("string_attribute"), stateValue.Value()), + }, + ExpectError: regexache.MustCompile(`expected value same for StateValue check, got: not same`), + }, + }, + }) +} + +func TestStateValue_NotInitialized(t *testing.T) { + t.Parallel() + + stateValue := StateValue() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "value" + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("test_resource.one", tfjsonpath.New("string_attribute"), stateValue.Value()), + }, + ExpectError: regexache.MustCompile(`state value has not been set`), + }, + }, + }) +} + +// Copied from https://github.com/hashicorp/terraform-plugin-testing/blob/main/statecheck/expect_known_value_test.go +func testProvider() *schema.Provider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "test_resource": { + CreateContext: func(_ context.Context, d *schema.ResourceData, _ any) diag.Diagnostics { + d.SetId("test") + + err := d.Set("string_computed_attribute", "computed") + if err != nil { + return diag.Errorf("error setting string_computed_attribute: %s", err) // nosemgrep:ci.semgrep.errors.no-diag.Errorf-leading-error,ci.semgrep.pluginsdk.avoid-diag_Errorf + } + + return nil + }, + UpdateContext: func(_ context.Context, _ *schema.ResourceData, _ any) diag.Diagnostics { + return nil + }, + DeleteContext: func(_ context.Context, _ *schema.ResourceData, _ any) diag.Diagnostics { + return nil + }, + ReadContext: func(_ context.Context, _ *schema.ResourceData, _ any) diag.Diagnostics { + return nil + }, + Schema: map[string]*schema.Schema{ + "bool_attribute": { + Optional: true, + Type: schema.TypeBool, + }, + "float_attribute": { + Optional: true, + Type: schema.TypeFloat, + }, + "int_attribute": { + Optional: true, + Type: schema.TypeInt, + }, + "list_attribute": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "list_nested_block": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "list_nested_block_attribute": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "map_attribute": { + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "set_attribute": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "set_nested_block": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "set_nested_block_attribute": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "set_nested_nested_block": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "set_nested_block": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "set_nested_block_attribute": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "string_attribute": { + Optional: true, + Type: schema.TypeString, + }, + "string_computed_attribute": { + Computed: true, + Type: schema.TypeString, + }, + }, + }, + }, + } +} diff --git a/internal/generate/servicepackage/main.go b/internal/generate/servicepackage/main.go index 993a827d920f..d01738ef12ea 100644 --- a/internal/generate/servicepackage/main.go +++ b/internal/generate/servicepackage/main.go @@ -102,6 +102,8 @@ func main() { value.TagsIdentifierAttribute = val.TagsIdentifierAttribute v.frameworkListResources[key] = value + } else { + g.Fatalf("Framework List Resource %q has no matching Framework Resource", key) } } @@ -117,6 +119,8 @@ func main() { value.TagsIdentifierAttribute = val.TagsIdentifierAttribute v.sdkListResources[key] = value + } else { + g.Fatalf("SDK List Resource %q has no matching SDK Resource", key) } } diff --git a/internal/generate/tests/annotations.go b/internal/generate/tests/annotations.go index 62e4cb89bbfb..1d6940fb3da3 100644 --- a/internal/generate/tests/annotations.go +++ b/internal/generate/tests/annotations.go @@ -88,6 +88,9 @@ func (c CommonArgs) HasImportIgnore() bool { } func (c CommonArgs) PlannableResourceAction() string { + if c.plannableImportAction == importActionUnset { + return importActionNoop.String() + } return c.plannableImportAction.String() } @@ -100,7 +103,8 @@ func (c CommonArgs) AdditionalTfVars() map[string]TFVar { type importAction int const ( - importActionNoop importAction = iota + importActionUnset importAction = iota + importActionNoop importActionUpdate importActionReplace ) @@ -203,7 +207,9 @@ func ParseTestingAnnotations(args common.Args, stuff *CommonArgs) error { for i, val := range stuff.ImportIgnore { stuff.ImportIgnore[i] = namesgen.ConstOrQuote(val) } - stuff.plannableImportAction = importActionUpdate + if stuff.plannableImportAction == importActionUnset { + stuff.plannableImportAction = importActionUpdate + } } if attr, ok := args.Keyword["importStateId"]; ok { diff --git a/internal/generate/tests/common_test.go.gtpl b/internal/generate/tests/common_test.go.gtpl index b4dbe4dfa382..f22834d3a40d 100644 --- a/internal/generate/tests/common_test.go.gtpl +++ b/internal/generate/tests/common_test.go.gtpl @@ -37,7 +37,12 @@ {{- end }} {{ define "baseTestname" -}} -{{ if .Serialize }}testAcc{{ else }}TestAcc{{ end }}{{ .ResourceProviderNameUpper }}{{ .Name }} +{{ if .Serialize }}testAcc{{ else }}TestAcc{{ end -}} +{{- if and (eq .ResourceProviderNameUpper "VPC") (eq .Name "VPC") -}} +VPC +{{- else -}} +{{ .ResourceProviderNameUpper }}{{ .Name }} +{{- end -}} {{- end }} {{ define "Test" -}} diff --git a/internal/service/ec2/ec2_instance_list_test.go b/internal/service/ec2/ec2_instance_list_test.go index 3abb34281166..d6dfdb7265df 100644 --- a/internal/service/ec2/ec2_instance_list_test.go +++ b/internal/service/ec2/ec2_instance_list_test.go @@ -4,7 +4,6 @@ package ec2_test import ( - "fmt" "testing" "github.com/hashicorp/terraform-plugin-testing/config" @@ -63,19 +62,19 @@ func TestAccEC2Instance_List_Basic(t *testing.T) { querycheck.ExpectIdentity("aws_instance.test", map[string]knownvalue.Check{ names.AttrAccountID: tfknownvalue.AccountID(), names.AttrRegion: knownvalue.StringExact(acctest.Region()), - names.AttrID: knownvalue.StringFunc(checker(&id1)), + names.AttrID: tfknownvalue.StringPtrExact(&id1), }), querycheck.ExpectIdentity("aws_instance.test", map[string]knownvalue.Check{ names.AttrAccountID: tfknownvalue.AccountID(), names.AttrRegion: knownvalue.StringExact(acctest.Region()), - names.AttrID: knownvalue.StringFunc(checker(&id2)), + names.AttrID: tfknownvalue.StringPtrExact(&id2), }), querycheck.ExpectIdentity("aws_instance.test", map[string]knownvalue.Check{ names.AttrAccountID: tfknownvalue.AccountID(), names.AttrRegion: knownvalue.StringExact(acctest.Region()), - names.AttrID: knownvalue.StringFunc(checker(&id3)), + names.AttrID: tfknownvalue.StringPtrExact(&id3), }), }, }, @@ -131,19 +130,19 @@ func TestAccEC2Instance_List_RegionOverride(t *testing.T) { querycheck.ExpectIdentity("aws_instance.test", map[string]knownvalue.Check{ names.AttrAccountID: tfknownvalue.AccountID(), names.AttrRegion: knownvalue.StringExact(acctest.AlternateRegion()), - names.AttrID: knownvalue.StringFunc(checker(&id1)), + names.AttrID: tfknownvalue.StringPtrExact(&id1), }), querycheck.ExpectIdentity("aws_instance.test", map[string]knownvalue.Check{ names.AttrAccountID: tfknownvalue.AccountID(), names.AttrRegion: knownvalue.StringExact(acctest.AlternateRegion()), - names.AttrID: knownvalue.StringFunc(checker(&id2)), + names.AttrID: tfknownvalue.StringPtrExact(&id2), }), querycheck.ExpectIdentity("aws_instance.test", map[string]knownvalue.Check{ names.AttrAccountID: tfknownvalue.AccountID(), names.AttrRegion: knownvalue.StringExact(acctest.AlternateRegion()), - names.AttrID: knownvalue.StringFunc(checker(&id3)), + names.AttrID: tfknownvalue.StringPtrExact(&id3), }), }, }, @@ -194,13 +193,13 @@ func TestAccEC2Instance_List_Filtered(t *testing.T) { querycheck.ExpectIdentity("aws_instance.test", map[string]knownvalue.Check{ names.AttrAccountID: tfknownvalue.AccountID(), names.AttrRegion: knownvalue.StringExact(acctest.Region()), - names.AttrID: knownvalue.StringFunc(checker(&id1)), + names.AttrID: tfknownvalue.StringPtrExact(&id1), }), querycheck.ExpectIdentity("aws_instance.test", map[string]knownvalue.Check{ names.AttrAccountID: tfknownvalue.AccountID(), names.AttrRegion: knownvalue.StringExact(acctest.Region()), - names.AttrID: knownvalue.StringFunc(checker(&id2)), + names.AttrID: tfknownvalue.StringPtrExact(&id2), }), }, }, @@ -258,13 +257,3 @@ func getter(s *string) resource.CheckResourceAttrWithFunc { return nil } } - -// TODO: Temporary until there is more testing support -func checker(s *string) func(string) error { - return func(v string) error { - if v != *s { - return fmt.Errorf("expected %q, got %q", *s, v) - } - return nil - } -} diff --git a/internal/service/ec2/service_package_gen.go b/internal/service/ec2/service_package_gen.go index adede89c4d55..b2a1742e72fe 100644 --- a/internal/service/ec2/service_package_gen.go +++ b/internal/service/ec2/service_package_gen.go @@ -1581,7 +1581,11 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*inttypes.ServicePa Tags: unique.Make(inttypes.ServicePackageResourceTags{ IdentifierAttribute: names.AttrID, }), - Region: unique.Make(inttypes.ResourceRegionDefault()), + Region: unique.Make(inttypes.ResourceRegionDefault()), + Identity: inttypes.RegionalSingleParameterIdentity(names.AttrID), + Import: inttypes.SDKv2Import{ + CustomImport: true, + }, }, { Factory: resourceVPCDHCPOptions, @@ -1824,6 +1828,16 @@ func (p *servicePackage) SDKListResources(ctx context.Context) iter.Seq[*inttype }), Identity: inttypes.RegionalSingleParameterIdentity(names.AttrID), }, + { + Factory: vpcResourceAsListResource, + TypeName: "aws_vpc", + Name: "VPC", + Region: unique.Make(inttypes.ResourceRegionDefault()), + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrID, + }), + Identity: inttypes.RegionalSingleParameterIdentity(names.AttrID), + }, }) } diff --git a/internal/service/ec2/sweep.go b/internal/service/ec2/sweep.go index 6779a586b136..d9292156b8fd 100644 --- a/internal/service/ec2/sweep.go +++ b/internal/service/ec2/sweep.go @@ -2472,9 +2472,16 @@ func sweepVPCs(region string) error { } conn := client.EC2Client(ctx) - input := ec2.DescribeVpcsInput{} var sweepResources []sweep.Sweepable + input := ec2.DescribeVpcsInput{ + Filters: []awstypes.Filter{ + { + Name: aws.String("is-default"), + Values: []string{"false"}, + }, + }, + } pages := ec2.NewDescribeVpcsPaginator(conn, &input) for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -2489,11 +2496,6 @@ func sweepVPCs(region string) error { } for _, v := range page.Vpcs { - // Skip default VPCs. - if aws.ToBool(v.IsDefault) { - continue - } - r := resourceVPC() d := r.Data(nil) d.SetId(aws.ToString(v.VpcId)) diff --git a/internal/service/ec2/testdata/VPC/basic/main_gen.tf b/internal/service/ec2/testdata/VPC/basic/main_gen.tf new file mode 100644 index 000000000000..655d2e1e6ede --- /dev/null +++ b/internal/service/ec2/testdata/VPC/basic/main_gen.tf @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" +} + diff --git a/internal/service/ec2/testdata/VPC/basic_v6.15.0/main_gen.tf b/internal/service/ec2/testdata/VPC/basic_v6.15.0/main_gen.tf new file mode 100644 index 000000000000..813ba6a19c43 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/basic_v6.15.0/main_gen.tf @@ -0,0 +1,17 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" +} + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "6.15.0" + } + } +} + +provider "aws" {} diff --git a/internal/service/ec2/testdata/VPC/list_basic/main.tf b/internal/service/ec2/testdata/VPC/list_basic/main.tf new file mode 100644 index 000000000000..0799f343f274 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_basic/main.tf @@ -0,0 +1,10 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +provider "aws" {} + +resource "aws_vpc" "test" { + count = 3 + + cidr_block = "10.1.0.0/16" +} diff --git a/internal/service/ec2/testdata/VPC/list_basic/main.tfquery.hcl b/internal/service/ec2/testdata/VPC/list_basic/main.tfquery.hcl new file mode 100644 index 000000000000..6b042e21a0d7 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_basic/main.tfquery.hcl @@ -0,0 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +list "aws_vpc" "test" { + provider = aws +} diff --git a/internal/service/ec2/testdata/VPC/list_exclude_default/main.tf b/internal/service/ec2/testdata/VPC/list_exclude_default/main.tf new file mode 100644 index 000000000000..b414b3bee713 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_exclude_default/main.tf @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +provider "aws" {} + +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" +} + +# tflint-ignore: terraform_unused_declarations +data "aws_vpc" "default" { + default = true +} diff --git a/internal/service/ec2/testdata/VPC/list_exclude_default/main.tfquery.hcl b/internal/service/ec2/testdata/VPC/list_exclude_default/main.tfquery.hcl new file mode 100644 index 000000000000..6b042e21a0d7 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_exclude_default/main.tfquery.hcl @@ -0,0 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +list "aws_vpc" "test" { + provider = aws +} diff --git a/internal/service/ec2/testdata/VPC/list_filtered/main.tf b/internal/service/ec2/testdata/VPC/list_filtered/main.tf new file mode 100644 index 000000000000..9b481e7ac7a3 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_filtered/main.tf @@ -0,0 +1,26 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +provider "aws" {} + +resource "aws_vpc" "expected" { + count = 2 + + cidr_block = "10.1.0.0/16" + + tags = { + expected = var.rName + } +} + +resource "aws_vpc" "not_expected" { + count = 2 + + cidr_block = "10.1.0.0/16" +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} diff --git a/internal/service/ec2/testdata/VPC/list_filtered/main.tfquery.hcl b/internal/service/ec2/testdata/VPC/list_filtered/main.tfquery.hcl new file mode 100644 index 000000000000..860171d2627c --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_filtered/main.tfquery.hcl @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +list "aws_vpc" "test" { + provider = aws + + config { + filter { + name = "tag:expected" + values = [var.rName] + } + } +} diff --git a/internal/service/ec2/testdata/VPC/list_filtered_is_default/main.tf b/internal/service/ec2/testdata/VPC/list_filtered_is_default/main.tf new file mode 100644 index 000000000000..4e2b0706b834 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_filtered_is_default/main.tf @@ -0,0 +1,4 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +provider "aws" {} diff --git a/internal/service/ec2/testdata/VPC/list_filtered_is_default/main.tfquery.hcl b/internal/service/ec2/testdata/VPC/list_filtered_is_default/main.tfquery.hcl new file mode 100644 index 000000000000..d91dd8d70eb6 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_filtered_is_default/main.tfquery.hcl @@ -0,0 +1,13 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +list "aws_vpc" "test" { + provider = aws + + config { + filter { + name = "is-default" + values = ["false"] + } + } +} diff --git a/internal/service/ec2/testdata/VPC/list_filtered_vpc_ids/main.tf b/internal/service/ec2/testdata/VPC/list_filtered_vpc_ids/main.tf new file mode 100644 index 000000000000..9b481e7ac7a3 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_filtered_vpc_ids/main.tf @@ -0,0 +1,26 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +provider "aws" {} + +resource "aws_vpc" "expected" { + count = 2 + + cidr_block = "10.1.0.0/16" + + tags = { + expected = var.rName + } +} + +resource "aws_vpc" "not_expected" { + count = 2 + + cidr_block = "10.1.0.0/16" +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} diff --git a/internal/service/ec2/testdata/VPC/list_filtered_vpc_ids/main.tfquery.hcl b/internal/service/ec2/testdata/VPC/list_filtered_vpc_ids/main.tfquery.hcl new file mode 100644 index 000000000000..045ce9ab63c2 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_filtered_vpc_ids/main.tfquery.hcl @@ -0,0 +1,21 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +list "aws_vpc" "test" { + provider = aws + + config { + vpc_ids = local.vpc_ids + filter { + name = "tag:expected" + values = [var.rName] + } + } +} + +locals { + vpc_ids = concat( + aws_vpc.expected[*].id, + aws_vpc.not_expected[*].id, + ) +} diff --git a/internal/service/ec2/testdata/VPC/list_region_override/main.tf b/internal/service/ec2/testdata/VPC/list_region_override/main.tf new file mode 100644 index 000000000000..856800b3188f --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_region_override/main.tf @@ -0,0 +1,18 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +provider "aws" {} + +resource "aws_vpc" "test" { + count = 3 + + region = var.region + + cidr_block = "10.1.0.0/16" +} + +variable "region" { + description = "Region to deploy resource in" + type = string + nullable = false +} diff --git a/internal/service/ec2/testdata/VPC/list_region_override/main.tfquery.hcl b/internal/service/ec2/testdata/VPC/list_region_override/main.tfquery.hcl new file mode 100644 index 000000000000..f2c1ddce2fda --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_region_override/main.tfquery.hcl @@ -0,0 +1,10 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +list "aws_vpc" "test" { + provider = aws + + config { + region = var.region + } +} diff --git a/internal/service/ec2/testdata/VPC/list_vpc_ids/main.tf b/internal/service/ec2/testdata/VPC/list_vpc_ids/main.tf new file mode 100644 index 000000000000..0799f343f274 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_vpc_ids/main.tf @@ -0,0 +1,10 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +provider "aws" {} + +resource "aws_vpc" "test" { + count = 3 + + cidr_block = "10.1.0.0/16" +} diff --git a/internal/service/ec2/testdata/VPC/list_vpc_ids/main.tfquery.hcl b/internal/service/ec2/testdata/VPC/list_vpc_ids/main.tfquery.hcl new file mode 100644 index 000000000000..8175671f3584 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/list_vpc_ids/main.tfquery.hcl @@ -0,0 +1,14 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +list "aws_vpc" "test" { + provider = aws + + config { + vpc_ids = local.vpc_ids + } +} + +locals { + vpc_ids = aws_vpc.test[*].id +} diff --git a/internal/service/ec2/testdata/VPC/region_override/main_gen.tf b/internal/service/ec2/testdata/VPC/region_override/main_gen.tf new file mode 100644 index 000000000000..81d85f59e009 --- /dev/null +++ b/internal/service/ec2/testdata/VPC/region_override/main_gen.tf @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_vpc" "test" { + region = var.region + + cidr_block = "10.1.0.0/16" +} + + +variable "region" { + description = "Region to deploy resource in" + type = string + nullable = false +} diff --git a/internal/service/ec2/testdata/tmpl/vpc_tags.gtpl b/internal/service/ec2/testdata/tmpl/vpc_tags.gtpl index fb29122a55c5..2834e74ed629 100644 --- a/internal/service/ec2/testdata/tmpl/vpc_tags.gtpl +++ b/internal/service/ec2/testdata/tmpl/vpc_tags.gtpl @@ -1,4 +1,5 @@ resource "aws_vpc" "test" { +{{- template "region" }} cidr_block = "10.1.0.0/16" {{- template "tags" . }} diff --git a/internal/service/ec2/vpc_.go b/internal/service/ec2/vpc_.go index cabb3106f64e..9b4eaa132516 100644 --- a/internal/service/ec2/vpc_.go +++ b/internal/service/ec2/vpc_.go @@ -15,17 +15,32 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ec2" awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" + fdiag "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/list" + listschema "github.com/hashicorp/terraform-plugin-framework/list/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/provider/sdkv2/importer" + "github.com/hashicorp/terraform-provider-aws/internal/retry" tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + inttypes "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" + "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" + "go.opentelemetry.io/otel/attribute" ) const ( @@ -48,8 +63,11 @@ var ( // @SDKResource("aws_vpc", name="VPC") // @Tags(identifierAttribute="id") +// @IdentityAttribute("id") +// @CustomImport // @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/ec2/types;awstypes;awstypes.Vpc") // @Testing(generator=false) +// @Testing(preIdentityVersion="v6.15.0") func resourceVPC() *schema.Resource { //lintignore:R011 return &schema.Resource{ @@ -181,6 +199,14 @@ func resourceVPC() *schema.Resource { } } +// @SDKListResource("aws_vpc") +func vpcResourceAsListResource() inttypes.ListResourceForSDK { + l := vpcListResource{} + l.SetResourceSchema(resourceVPC()) + + return &l +} + func resourceVPCCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).EC2Client(ctx) @@ -279,100 +305,10 @@ func resourceVPCRead(ctx context.Context, d *schema.ResourceData, meta any) diag return sdkdiag.AppendErrorf(diags, "reading EC2 VPC (%s): %s", d.Id(), err) } - ownerID := aws.ToString(vpc.OwnerId) - d.Set(names.AttrARN, vpcARN(ctx, c, ownerID, d.Id())) - d.Set(names.AttrCIDRBlock, vpc.CidrBlock) - d.Set("dhcp_options_id", vpc.DhcpOptionsId) - d.Set("instance_tenancy", vpc.InstanceTenancy) - d.Set(names.AttrOwnerID, ownerID) - - if v, err := tfresource.RetryWhenNewResourceNotFound(ctx, ec2PropagationTimeout, func(ctx context.Context) (bool, error) { - return findVPCAttribute(ctx, conn, d.Id(), awstypes.VpcAttributeNameEnableDnsHostnames) - }, d.IsNewResource()); err != nil { - return sdkdiag.AppendErrorf(diags, "reading EC2 VPC (%s) Attribute (%s): %s", d.Id(), awstypes.VpcAttributeNameEnableDnsHostnames, err) - } else { - d.Set("enable_dns_hostnames", v) - } - - if v, err := tfresource.RetryWhenNewResourceNotFound(ctx, ec2PropagationTimeout, func(ctx context.Context) (bool, error) { - return findVPCAttribute(ctx, conn, d.Id(), awstypes.VpcAttributeNameEnableDnsSupport) - }, d.IsNewResource()); err != nil { - return sdkdiag.AppendErrorf(diags, "reading EC2 VPC (%s) Attribute (%s): %s", d.Id(), awstypes.VpcAttributeNameEnableDnsSupport, err) - } else { - d.Set("enable_dns_support", v) + if err := resourceVPCFlatten(ctx, c, vpc, d); err != nil { + diags = sdkdiag.AppendFromErr(diags, err) } - if v, err := tfresource.RetryWhenNewResourceNotFound(ctx, ec2PropagationTimeout, func(ctx context.Context) (bool, error) { - return findVPCAttribute(ctx, conn, d.Id(), awstypes.VpcAttributeNameEnableNetworkAddressUsageMetrics) - }, d.IsNewResource()); err != nil { - return sdkdiag.AppendErrorf(diags, "reading EC2 VPC (%s) Attribute (%s): %s", d.Id(), awstypes.VpcAttributeNameEnableNetworkAddressUsageMetrics, err) - } else { - d.Set("enable_network_address_usage_metrics", v) - } - - if v, err := findVPCDefaultNetworkACL(ctx, conn, d.Id()); err != nil { - log.Printf("[WARN] Error reading EC2 VPC (%s) default NACL: %s", d.Id(), err) - } else { - d.Set("default_network_acl_id", v.NetworkAclId) - } - - if v, err := findVPCMainRouteTable(ctx, conn, d.Id()); err != nil { - log.Printf("[WARN] Error reading EC2 VPC (%s) main Route Table: %s", d.Id(), err) - d.Set("default_route_table_id", nil) - d.Set("main_route_table_id", nil) - } else { - d.Set("default_route_table_id", v.RouteTableId) - d.Set("main_route_table_id", v.RouteTableId) - } - - if v, err := findVPCDefaultSecurityGroup(ctx, conn, d.Id()); err != nil { - log.Printf("[WARN] Error reading EC2 VPC (%s) default Security Group: %s", d.Id(), err) - d.Set("default_security_group_id", nil) - } else { - d.Set("default_security_group_id", v.GroupId) - } - - if ipv6CIDRBlockAssociation := defaultIPv6CIDRBlockAssociation(vpc, d.Get("ipv6_association_id").(string)); ipv6CIDRBlockAssociation == nil { - d.Set("assign_generated_ipv6_cidr_block", nil) - d.Set("ipv6_association_id", nil) - d.Set("ipv6_cidr_block", nil) - d.Set("ipv6_cidr_block_network_border_group", nil) - d.Set("ipv6_ipam_pool_id", nil) - d.Set("ipv6_netmask_length", nil) - } else { - cidrBlock := aws.ToString(ipv6CIDRBlockAssociation.Ipv6CidrBlock) - ipv6PoolID := aws.ToString(ipv6CIDRBlockAssociation.Ipv6Pool) - isAmazonIPv6Pool := ipv6PoolID == amazonIPv6PoolID - d.Set("assign_generated_ipv6_cidr_block", isAmazonIPv6Pool) - d.Set("ipv6_association_id", ipv6CIDRBlockAssociation.AssociationId) - d.Set("ipv6_cidr_block", cidrBlock) - d.Set("ipv6_cidr_block_network_border_group", ipv6CIDRBlockAssociation.NetworkBorderGroup) - if isAmazonIPv6Pool { - d.Set("ipv6_ipam_pool_id", nil) - } else { - if ipv6PoolID == ipamManagedIPv6PoolID { - d.Set("ipv6_ipam_pool_id", d.Get("ipv6_ipam_pool_id")) - } else { - d.Set("ipv6_ipam_pool_id", ipv6PoolID) - } - } - d.Set("ipv6_netmask_length", nil) - if ipv6PoolID != "" && !isAmazonIPv6Pool { - parts := strings.Split(cidrBlock, "/") - if len(parts) == 2 { - if v, err := strconv.Atoi(parts[1]); err == nil { - d.Set("ipv6_netmask_length", v) - } else { - log.Printf("[WARN] Unable to parse CIDR (%s) netmask length: %s", cidrBlock, err) - } - } else { - log.Printf("[WARN] Invalid CIDR block format: %s", cidrBlock) - } - } - } - - setTagsOut(ctx, vpc.Tags) - return diags } @@ -495,7 +431,13 @@ func resourceVPCDelete(ctx context.Context, d *schema.ResourceData, meta any) di } func resourceVPCImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + identitySpec := importer.IdentitySpec(ctx) + if err := importer.RegionalSingleParameterized(ctx, d, identitySpec, meta.(importer.AWSClient)); err != nil { + return nil, err + } + d.Set("assign_generated_ipv6_cidr_block", false) + return []*schema.ResourceData{d}, nil } @@ -716,6 +658,268 @@ func modifyVPCTenancy(ctx context.Context, conn *ec2.Client, vpcID string, v str return nil } +func resourceVPCFlatten(ctx context.Context, client *conns.AWSClient, vpc *awstypes.Vpc, d *schema.ResourceData) error { + conn := client.EC2Client(ctx) + ownerID := aws.ToString(vpc.OwnerId) + d.Set(names.AttrARN, vpcARN(ctx, client, ownerID, d.Id())) + d.Set(names.AttrCIDRBlock, vpc.CidrBlock) + d.Set("dhcp_options_id", vpc.DhcpOptionsId) + d.Set("instance_tenancy", vpc.InstanceTenancy) + d.Set(names.AttrOwnerID, ownerID) + + if v, err := tfresource.RetryWhenNewResourceNotFound(ctx, ec2PropagationTimeout, func(ctx context.Context) (bool, error) { + return findVPCAttribute(ctx, conn, d.Id(), awstypes.VpcAttributeNameEnableDnsHostnames) + }, d.IsNewResource()); err != nil { + return fmt.Errorf("reading EC2 VPC (%s) Attribute (%s): %w", d.Id(), awstypes.VpcAttributeNameEnableDnsHostnames, err) + } else { + d.Set("enable_dns_hostnames", v) + } + + if v, err := tfresource.RetryWhenNewResourceNotFound(ctx, ec2PropagationTimeout, func(ctx context.Context) (bool, error) { + return findVPCAttribute(ctx, conn, d.Id(), awstypes.VpcAttributeNameEnableDnsSupport) + }, d.IsNewResource()); err != nil { + return fmt.Errorf("reading EC2 VPC (%s) Attribute (%s): %w", d.Id(), awstypes.VpcAttributeNameEnableDnsSupport, err) + } else { + d.Set("enable_dns_support", v) + } + + if v, err := tfresource.RetryWhenNewResourceNotFound(ctx, ec2PropagationTimeout, func(ctx context.Context) (bool, error) { + return findVPCAttribute(ctx, conn, d.Id(), awstypes.VpcAttributeNameEnableNetworkAddressUsageMetrics) + }, d.IsNewResource()); err != nil { + return fmt.Errorf("reading EC2 VPC (%s) Attribute (%s): %w", d.Id(), awstypes.VpcAttributeNameEnableNetworkAddressUsageMetrics, err) + } else { + d.Set("enable_network_address_usage_metrics", v) + } + + if v, err := findVPCDefaultNetworkACL(ctx, conn, d.Id()); err != nil { + return fmt.Errorf("reading EC2 VPC (%s) default NACL: %w", d.Id(), err) + } else { + d.Set("default_network_acl_id", v.NetworkAclId) + } + + if v, err := findVPCMainRouteTable(ctx, conn, d.Id()); err != nil { + return fmt.Errorf("reading EC2 VPC (%s) main Route Table: %w", d.Id(), err) + } else { + d.Set("default_route_table_id", v.RouteTableId) + d.Set("main_route_table_id", v.RouteTableId) + } + + if v, err := findVPCDefaultSecurityGroup(ctx, conn, d.Id()); err != nil { + return fmt.Errorf("reading EC2 VPC (%s) default Security Group: %w", d.Id(), err) + } else { + d.Set("default_security_group_id", v.GroupId) + } + + if ipv6CIDRBlockAssociation := defaultIPv6CIDRBlockAssociation(vpc, d.Get("ipv6_association_id").(string)); ipv6CIDRBlockAssociation == nil { + d.Set("assign_generated_ipv6_cidr_block", nil) + d.Set("ipv6_association_id", nil) + d.Set("ipv6_cidr_block", nil) + d.Set("ipv6_cidr_block_network_border_group", nil) + d.Set("ipv6_ipam_pool_id", nil) + d.Set("ipv6_netmask_length", nil) + } else { + cidrBlock := aws.ToString(ipv6CIDRBlockAssociation.Ipv6CidrBlock) + ipv6PoolID := aws.ToString(ipv6CIDRBlockAssociation.Ipv6Pool) + isAmazonIPv6Pool := ipv6PoolID == amazonIPv6PoolID + d.Set("assign_generated_ipv6_cidr_block", isAmazonIPv6Pool) + d.Set("ipv6_association_id", ipv6CIDRBlockAssociation.AssociationId) + d.Set("ipv6_cidr_block", cidrBlock) + d.Set("ipv6_cidr_block_network_border_group", ipv6CIDRBlockAssociation.NetworkBorderGroup) + if isAmazonIPv6Pool { + d.Set("ipv6_ipam_pool_id", nil) + } else { + if ipv6PoolID == ipamManagedIPv6PoolID { + d.Set("ipv6_ipam_pool_id", d.Get("ipv6_ipam_pool_id")) + } else { + d.Set("ipv6_ipam_pool_id", ipv6PoolID) + } + } + d.Set("ipv6_netmask_length", nil) + if ipv6PoolID != "" && !isAmazonIPv6Pool { + parts := strings.Split(cidrBlock, "/") + if len(parts) == 2 { + if v, err := strconv.Atoi(parts[1]); err == nil { + d.Set("ipv6_netmask_length", v) + } else { + log.Printf("[WARN] Unable to parse CIDR (%s) netmask length: %s", cidrBlock, err) + } + } else { + log.Printf("[WARN] Invalid CIDR block format: %s", cidrBlock) + } + } + } + + setTagsOut(ctx, vpc.Tags) + + return nil +} + func vpcARN(ctx context.Context, c *conns.AWSClient, accountID, vpcID string) string { return c.RegionalARNWithAccount(ctx, names.EC2, accountID, "vpc/"+vpcID) } + +var _ list.ListResourceWithRawV5Schemas = &vpcListResource{} + +type vpcListResource struct { + framework.ResourceWithConfigure + framework.ListResourceWithSDKv2Resource + framework.ListResourceWithSDKv2Tags +} + +type vpcListResourceModel struct { + framework.WithRegionModel + VPCIDs fwtypes.ListValueOf[types.String] `tfsdk:"vpc_ids"` + Filters customListFilters `tfsdk:"filter"` +} + +func (l *vpcListResource) ListResourceConfigSchema(ctx context.Context, request list.ListResourceSchemaRequest, response *list.ListResourceSchemaResponse) { + response.Schema = listschema.Schema{ + Attributes: map[string]listschema.Attribute{ + "vpc_ids": listschema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Optional: true, + }, + }, + Blocks: map[string]listschema.Block{ + names.AttrFilter: listschema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[customListFilterModel](ctx), + NestedObject: listschema.NestedBlockObject{ + Attributes: map[string]listschema.Attribute{ + names.AttrName: listschema.StringAttribute{ + Required: true, + Validators: []validator.String{ + notIsDefaultValidator{}, + }, + }, + names.AttrValues: listschema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + } +} + +var _ validator.String = notIsDefaultValidator{} + +type notIsDefaultValidator struct{} + +func (v notIsDefaultValidator) Description(ctx context.Context) string { + return v.MarkdownDescription(ctx) +} + +func (v notIsDefaultValidator) MarkdownDescription(_ context.Context) string { + return "" +} + +func (v notIsDefaultValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue + + if value.ValueString() == "is-default" { + response.Diagnostics.Append(fdiag.NewAttributeErrorDiagnostic( + request.Path, + "Invalid Attribute Value", + `The filter "is-default" is not supported. To list default VPCs, use the resource type "aws_default_vpc".`, + )) + } +} + +func (l *vpcListResource) List(ctx context.Context, request list.ListRequest, stream *list.ListResultsStream) { + awsClient := l.Meta() + conn := awsClient.EC2Client(ctx) + + attributes := []attribute.KeyValue{ + otelaws.RegionAttr(awsClient.Region(ctx)), + } + for _, attribute := range attributes { + ctx = tflog.SetField(ctx, string(attribute.Key), attribute.Value.AsInterface()) + } + + var query vpcListResourceModel + if request.Config.Raw.IsKnown() && !request.Config.Raw.IsNull() { + if diags := request.Config.Get(ctx, &query); diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + } + + var input ec2.DescribeVpcsInput + if diags := fwflex.Expand(ctx, query, &input); diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + input.Filters = append(input.Filters, awstypes.Filter{ + Name: aws.String("is-default"), + Values: []string{"false"}, + }) + + tflog.Info(ctx, "Listing resources") + + stream.Results = func(yield func(list.ListResult) bool) { + pages := ec2.NewDescribeVpcsPaginator(conn, &input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if err != nil { + result := fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + for _, vpc := range page.Vpcs { + ctx := tflog.SetField(ctx, "tf_aws.resource_attribute.id", aws.ToString(vpc.VpcId)) + + result := request.NewListResult(ctx) + + tags := keyValueTags(ctx, vpc.Tags) + + rd := l.ResourceData() + rd.SetId(aws.ToString(vpc.VpcId)) + + tflog.Info(ctx, "Reading resource") + err := resourceVPCFlatten(ctx, awsClient, &vpc, rd) + if retry.NotFound(err) { + tflog.Warn(ctx, "Resource disappeared during listing, skipping") + continue + } + if err != nil { + result = fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + // set tags + err = l.SetTags(ctx, awsClient, rd) + if err != nil { + result = fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + if v, ok := tags["Name"]; ok { + result.DisplayName = v.ValueString() + } else { + result.DisplayName = aws.ToString(vpc.VpcId) + } + + l.SetResult(ctx, awsClient, request.IncludeResource, &result, rd) + if result.Diagnostics.HasError() { + yield(result) + return + } + + if !yield(result) { + return + } + } + } + } +} diff --git a/internal/service/ec2/vpc_data_source_tags_gen_test.go b/internal/service/ec2/vpc_data_source_tags_gen_test.go index 6ddcc71b002f..42957019ec34 100644 --- a/internal/service/ec2/vpc_data_source_tags_gen_test.go +++ b/internal/service/ec2/vpc_data_source_tags_gen_test.go @@ -19,7 +19,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccVPCVPCDataSource_tags(t *testing.T) { +func TestAccVPCDataSource_tags(t *testing.T) { ctx := acctest.Context(t) dataSourceName := "data.aws_vpc.test" @@ -46,7 +46,7 @@ func TestAccVPCVPCDataSource_tags(t *testing.T) { }) } -func TestAccVPCVPCDataSource_tags_NullMap(t *testing.T) { +func TestAccVPCDataSource_tags_NullMap(t *testing.T) { ctx := acctest.Context(t) dataSourceName := "data.aws_vpc.test" @@ -69,7 +69,7 @@ func TestAccVPCVPCDataSource_tags_NullMap(t *testing.T) { }) } -func TestAccVPCVPCDataSource_tags_EmptyMap(t *testing.T) { +func TestAccVPCDataSource_tags_EmptyMap(t *testing.T) { ctx := acctest.Context(t) dataSourceName := "data.aws_vpc.test" @@ -92,7 +92,7 @@ func TestAccVPCVPCDataSource_tags_EmptyMap(t *testing.T) { }) } -func TestAccVPCVPCDataSource_tags_DefaultTags_nonOverlapping(t *testing.T) { +func TestAccVPCDataSource_tags_DefaultTags_nonOverlapping(t *testing.T) { ctx := acctest.Context(t) dataSourceName := "data.aws_vpc.test" @@ -123,7 +123,7 @@ func TestAccVPCVPCDataSource_tags_DefaultTags_nonOverlapping(t *testing.T) { }) } -func TestAccVPCVPCDataSource_tags_IgnoreTags_Overlap_DefaultTag(t *testing.T) { +func TestAccVPCDataSource_tags_IgnoreTags_Overlap_DefaultTag(t *testing.T) { ctx := acctest.Context(t) dataSourceName := "data.aws_vpc.test" @@ -160,7 +160,7 @@ func TestAccVPCVPCDataSource_tags_IgnoreTags_Overlap_DefaultTag(t *testing.T) { }) } -func TestAccVPCVPCDataSource_tags_IgnoreTags_Overlap_ResourceTag(t *testing.T) { +func TestAccVPCDataSource_tags_IgnoreTags_Overlap_ResourceTag(t *testing.T) { ctx := acctest.Context(t) dataSourceName := "data.aws_vpc.test" diff --git a/internal/service/ec2/vpc_identity_gen_test.go b/internal/service/ec2/vpc_identity_gen_test.go new file mode 100644 index 000000000000..569ecd517c00 --- /dev/null +++ b/internal/service/ec2/vpc_identity_gen_test.go @@ -0,0 +1,284 @@ +// Code generated by internal/generate/identitytests/main.go; DO NOT EDIT. + +package ec2_test + +import ( + "testing" + + awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfknownvalue "github.com/hashicorp/terraform-provider-aws/internal/acctest/knownvalue" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccVPC_Identity_Basic(t *testing.T) { + ctx := acctest.Context(t) + + var v awstypes.Vpc + resourceName := "aws_vpc.test" + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: testAccCheckVPCDestroy(ctx), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/basic/"), + ConfigVariables: config.Variables{}, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCExists(ctx, resourceName, &v), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrID)), + }, + }, + + // Step 2: Import command + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/basic/"), + ConfigVariables: config.Variables{}, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import block with Import ID + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/basic/"), + ConfigVariables: config.Variables{}, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + + // Step 4: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/basic/"), + ConfigVariables: config.Variables{}, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + }, + }) +} + +func TestAccVPC_Identity_RegionOverride(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_vpc.test" + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: acctest.CheckDestroyNoop, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/region_override/"), + ConfigVariables: config.Variables{ + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.AlternateRegion()), + names.AttrID: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrID)), + }, + }, + + // Step 2: Import command + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/region_override/"), + ConfigVariables: config.Variables{ + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import block with Import ID + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/region_override/"), + ConfigVariables: config.Variables{ + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 4: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/region_override/"), + ConfigVariables: config.Variables{ + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + }, + }) +} + +// Resource Identity was added after v6.15.0 +func TestAccVPC_Identity_ExistingResource(t *testing.T) { + ctx := acctest.Context(t) + + var v awstypes.Vpc + resourceName := "aws_vpc.test" + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: testAccCheckVPCDestroy(ctx), + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/basic_v6.15.0/"), + ConfigVariables: config.Variables{}, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCExists(ctx, resourceName, &v), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/basic/"), + ConfigVariables: config.Variables{}, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrID)), + }, + }, + }, + }) +} + +// Resource Identity was added after v6.15.0 +func TestAccVPC_Identity_ExistingResource_NoRefresh_NoChange(t *testing.T) { + ctx := acctest.Context(t) + + var v awstypes.Vpc + resourceName := "aws_vpc.test" + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: testAccCheckVPCDestroy(ctx), + AdditionalCLIOptions: &resource.AdditionalCLIOptions{ + Plan: resource.PlanOptions{ + NoRefresh: true, + }, + }, + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/VPC/basic_v6.15.0/"), + ConfigVariables: config.Variables{}, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckVPCExists(ctx, resourceName, &v), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/basic/"), + ConfigVariables: config.Variables{}, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + }, + }) +} diff --git a/internal/service/ec2/vpc_list_test.go b/internal/service/ec2/vpc_list_test.go new file mode 100644 index 000000000000..f8d878a55577 --- /dev/null +++ b/internal/service/ec2/vpc_list_test.go @@ -0,0 +1,470 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ec2_test + +import ( + "testing" + + "github.com/YakDriver/regexache" + "github.com/hashicorp/terraform-plugin-testing/config" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfknownvalue "github.com/hashicorp/terraform-provider-aws/internal/acctest/knownvalue" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccVPC_List_Basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName1 := "aws_vpc.test[0]" + resourceName2 := "aws_vpc.test[1]" + resourceName3 := "aws_vpc.test[2]" + + id1 := tfstatecheck.StateValue() + id2 := tfstatecheck.StateValue() + id3 := tfstatecheck.StateValue() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: testAccCheckVPCDestroy(ctx), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_basic"), + ConfigStateChecks: []statecheck.StateCheck{ + id1.GetStateValue(resourceName1, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceName1, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + id2.GetStateValue(resourceName2, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceName2, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + id3.GetStateValue(resourceName3, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceName3, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + }, + }, + + // Step 2: Query + { + Query: true, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_basic"), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: id1.Value(), + }), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: id2.Value(), + }), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: id3.Value(), + }), + }, + }, + }, + }) +} + +func TestAccVPC_List_RegionOverride(t *testing.T) { + ctx := acctest.Context(t) + + resourceName1 := "aws_vpc.test[0]" + resourceName2 := "aws_vpc.test[1]" + resourceName3 := "aws_vpc.test[2]" + + id1 := tfstatecheck.StateValue() + id2 := tfstatecheck.StateValue() + id3 := tfstatecheck.StateValue() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: testAccCheckVPCDestroy(ctx), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_region_override/"), + ConfigVariables: config.Variables{ + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + id1.GetStateValue(resourceName1, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNAlternateRegionFormat(resourceName1, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + id2.GetStateValue(resourceName2, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNAlternateRegionFormat(resourceName2, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + id3.GetStateValue(resourceName3, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNAlternateRegionFormat(resourceName3, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + }, + }, + + // Step 2: Query + { + Query: true, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_region_override/"), + ConfigVariables: config.Variables{ + "region": config.StringVariable(acctest.AlternateRegion()), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.AlternateRegion()), + names.AttrID: id1.Value(), + }), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.AlternateRegion()), + names.AttrID: id2.Value(), + }), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.AlternateRegion()), + names.AttrID: id3.Value(), + }), + }, + }, + }, + }) +} + +func TestAccVPC_List_Filtered(t *testing.T) { + ctx := acctest.Context(t) + + resourceNameExpected1 := "aws_vpc.expected[0]" + resourceNameExpected2 := "aws_vpc.expected[1]" + resourceNameNotExpected1 := "aws_vpc.not_expected[0]" + resourceNameNotExpected2 := "aws_vpc.not_expected[1]" + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + expected1 := tfstatecheck.StateValue() + expected2 := tfstatecheck.StateValue() + notExpected1 := tfstatecheck.StateValue() + notExpected2 := tfstatecheck.StateValue() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: testAccCheckVPCDestroy(ctx), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_filtered/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigStateChecks: []statecheck.StateCheck{ + expected1.GetStateValue(resourceNameExpected1, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceNameExpected1, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + expected2.GetStateValue(resourceNameExpected2, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceNameExpected2, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + notExpected1.GetStateValue(resourceNameNotExpected1, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceNameNotExpected1, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + notExpected2.GetStateValue(resourceNameNotExpected2, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceNameNotExpected2, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + }, + }, + + // Step 2: Query + { + Query: true, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_filtered/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: expected1.Value(), + }), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: expected2.Value(), + }), + + querycheck.ExpectNoIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: notExpected1.Value(), + }), + + querycheck.ExpectNoIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: notExpected2.Value(), + }), + }, + }, + }, + }) +} + +func TestAccVPC_List_DefaultVPC_Exclude(t *testing.T) { + ctx := acctest.Context(t) + + id := tfstatecheck.StateValue() + defaultVPCID := tfstatecheck.StateValue() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDefaultVPCExists(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_exclude_default"), + ConfigStateChecks: []statecheck.StateCheck{ + id.GetStateValue("aws_vpc.test", tfjsonpath.New(names.AttrID)), + defaultVPCID.GetStateValue("data.aws_vpc.default", tfjsonpath.New(names.AttrID)), + }, + }, + + // Step 2: Query + { + Query: true, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_exclude_default"), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: id.Value(), + }), + + querycheck.ExpectNoIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: defaultVPCID.Value(), + }), + }, + }, + }, + }) +} + +func TestAccVPC_List_VPCIDs(t *testing.T) { + ctx := acctest.Context(t) + + resourceName1 := "aws_vpc.test[0]" + resourceName2 := "aws_vpc.test[1]" + resourceName3 := "aws_vpc.test[2]" + + id1 := tfstatecheck.StateValue() + id2 := tfstatecheck.StateValue() + id3 := tfstatecheck.StateValue() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: testAccCheckVPCDestroy(ctx), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_vpc_ids"), + ConfigStateChecks: []statecheck.StateCheck{ + id1.GetStateValue(resourceName1, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceName1, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + id2.GetStateValue(resourceName2, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceName2, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + id3.GetStateValue(resourceName3, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceName3, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + }, + }, + + // Step 2: Query + { + Query: true, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_vpc_ids"), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("aws_vpc.test", 3), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: id1.Value(), + }), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: id2.Value(), + }), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: id3.Value(), + }), + }, + }, + }, + }) +} + +func TestAccVPC_List_FilteredVPCIDs(t *testing.T) { + ctx := acctest.Context(t) + + resourceNameExpected1 := "aws_vpc.expected[0]" + resourceNameExpected2 := "aws_vpc.expected[1]" + resourceNameNotExpected1 := "aws_vpc.not_expected[0]" + resourceNameNotExpected2 := "aws_vpc.not_expected[1]" + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + expected1 := tfstatecheck.StateValue() + expected2 := tfstatecheck.StateValue() + notExpected1 := tfstatecheck.StateValue() + notExpected2 := tfstatecheck.StateValue() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: testAccCheckVPCDestroy(ctx), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_filtered_vpc_ids/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigStateChecks: []statecheck.StateCheck{ + expected1.GetStateValue(resourceNameExpected1, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceNameExpected1, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + expected2.GetStateValue(resourceNameExpected2, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceNameExpected2, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + notExpected1.GetStateValue(resourceNameNotExpected1, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceNameNotExpected1, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + + notExpected2.GetStateValue(resourceNameNotExpected2, tfjsonpath.New(names.AttrID)), + tfstatecheck.ExpectRegionalARNFormat(resourceNameNotExpected2, tfjsonpath.New(names.AttrARN), "ec2", "vpc/{id}"), + }, + }, + + // Step 2: Query + { + Query: true, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_filtered_vpc_ids/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("aws_vpc.test", 2), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: expected1.Value(), + }), + + querycheck.ExpectIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: expected2.Value(), + }), + + querycheck.ExpectNoIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: notExpected1.Value(), + }), + + querycheck.ExpectNoIdentity("aws_vpc.test", map[string]knownvalue.Check{ + names.AttrAccountID: tfknownvalue.AccountID(), + names.AttrRegion: knownvalue.StringExact(acctest.Region()), + names.AttrID: notExpected2.Value(), + }), + }, + }, + }, + }) +} + +func TestAccVPC_List_Filtered_IsDefault(t *testing.T) { + t.Skip("Skipping because ExpectError is not currently supported for Query mode") + + ctx := acctest.Context(t) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + CheckDestroy: testAccCheckVPCDestroy(ctx), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_filtered_is_default"), + }, + + // Step 2: Query + { + Query: true, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/VPC/list_filtered_is_default/"), + ExpectError: regexache.MustCompile(`The filter "is-default" is not supported. To list default VPCs, use the resource type "aws_default_vpc".`), + }, + }, + }) +} diff --git a/internal/service/ec2/vpc_tags_gen_test.go b/internal/service/ec2/vpc_tags_gen_test.go index 9ead77c352b4..fbe9187a9824 100644 --- a/internal/service/ec2/vpc_tags_gen_test.go +++ b/internal/service/ec2/vpc_tags_gen_test.go @@ -16,7 +16,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccVPCVPC_tags(t *testing.T) { +func TestAccVPC_tags(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -190,7 +190,7 @@ func TestAccVPCVPC_tags(t *testing.T) { }) } -func TestAccVPCVPC_tags_null(t *testing.T) { +func TestAccVPC_tags_null(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -254,7 +254,7 @@ func TestAccVPCVPC_tags_null(t *testing.T) { }) } -func TestAccVPCVPC_tags_EmptyMap(t *testing.T) { +func TestAccVPC_tags_EmptyMap(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -314,7 +314,7 @@ func TestAccVPCVPC_tags_EmptyMap(t *testing.T) { }) } -func TestAccVPCVPC_tags_AddOnUpdate(t *testing.T) { +func TestAccVPC_tags_AddOnUpdate(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -392,7 +392,7 @@ func TestAccVPCVPC_tags_AddOnUpdate(t *testing.T) { }) } -func TestAccVPCVPC_tags_EmptyTag_OnCreate(t *testing.T) { +func TestAccVPC_tags_EmptyTag_OnCreate(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -477,7 +477,7 @@ func TestAccVPCVPC_tags_EmptyTag_OnCreate(t *testing.T) { }) } -func TestAccVPCVPC_tags_EmptyTag_OnUpdate_Add(t *testing.T) { +func TestAccVPC_tags_EmptyTag_OnUpdate_Add(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -609,7 +609,7 @@ func TestAccVPCVPC_tags_EmptyTag_OnUpdate_Add(t *testing.T) { }) } -func TestAccVPCVPC_tags_EmptyTag_OnUpdate_Replace(t *testing.T) { +func TestAccVPC_tags_EmptyTag_OnUpdate_Replace(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -695,7 +695,7 @@ func TestAccVPCVPC_tags_EmptyTag_OnUpdate_Replace(t *testing.T) { }) } -func TestAccVPCVPC_tags_DefaultTags_providerOnly(t *testing.T) { +func TestAccVPC_tags_DefaultTags_providerOnly(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -868,7 +868,7 @@ func TestAccVPCVPC_tags_DefaultTags_providerOnly(t *testing.T) { }) } -func TestAccVPCVPC_tags_DefaultTags_nonOverlapping(t *testing.T) { +func TestAccVPC_tags_DefaultTags_nonOverlapping(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1022,7 +1022,7 @@ func TestAccVPCVPC_tags_DefaultTags_nonOverlapping(t *testing.T) { }) } -func TestAccVPCVPC_tags_DefaultTags_overlapping(t *testing.T) { +func TestAccVPC_tags_DefaultTags_overlapping(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1192,7 +1192,7 @@ func TestAccVPCVPC_tags_DefaultTags_overlapping(t *testing.T) { }) } -func TestAccVPCVPC_tags_DefaultTags_updateToProviderOnly(t *testing.T) { +func TestAccVPC_tags_DefaultTags_updateToProviderOnly(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1279,7 +1279,7 @@ func TestAccVPCVPC_tags_DefaultTags_updateToProviderOnly(t *testing.T) { }) } -func TestAccVPCVPC_tags_DefaultTags_updateToResourceOnly(t *testing.T) { +func TestAccVPC_tags_DefaultTags_updateToResourceOnly(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1365,7 +1365,7 @@ func TestAccVPCVPC_tags_DefaultTags_updateToResourceOnly(t *testing.T) { }) } -func TestAccVPCVPC_tags_DefaultTags_emptyResourceTag(t *testing.T) { +func TestAccVPC_tags_DefaultTags_emptyResourceTag(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1428,7 +1428,7 @@ func TestAccVPCVPC_tags_DefaultTags_emptyResourceTag(t *testing.T) { }) } -func TestAccVPCVPC_tags_DefaultTags_emptyProviderOnlyTag(t *testing.T) { +func TestAccVPC_tags_DefaultTags_emptyProviderOnlyTag(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1483,7 +1483,7 @@ func TestAccVPCVPC_tags_DefaultTags_emptyProviderOnlyTag(t *testing.T) { }) } -func TestAccVPCVPC_tags_DefaultTags_nullOverlappingResourceTag(t *testing.T) { +func TestAccVPC_tags_DefaultTags_nullOverlappingResourceTag(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1543,7 +1543,7 @@ func TestAccVPCVPC_tags_DefaultTags_nullOverlappingResourceTag(t *testing.T) { }) } -func TestAccVPCVPC_tags_DefaultTags_nullNonOverlappingResourceTag(t *testing.T) { +func TestAccVPC_tags_DefaultTags_nullNonOverlappingResourceTag(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1603,7 +1603,7 @@ func TestAccVPCVPC_tags_DefaultTags_nullNonOverlappingResourceTag(t *testing.T) }) } -func TestAccVPCVPC_tags_ComputedTag_OnCreate(t *testing.T) { +func TestAccVPC_tags_ComputedTag_OnCreate(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1656,7 +1656,7 @@ func TestAccVPCVPC_tags_ComputedTag_OnCreate(t *testing.T) { }) } -func TestAccVPCVPC_tags_ComputedTag_OnUpdate_Add(t *testing.T) { +func TestAccVPC_tags_ComputedTag_OnUpdate_Add(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1750,7 +1750,7 @@ func TestAccVPCVPC_tags_ComputedTag_OnUpdate_Add(t *testing.T) { }) } -func TestAccVPCVPC_tags_ComputedTag_OnUpdate_Replace(t *testing.T) { +func TestAccVPC_tags_ComputedTag_OnUpdate_Replace(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1834,7 +1834,7 @@ func TestAccVPCVPC_tags_ComputedTag_OnUpdate_Replace(t *testing.T) { }) } -func TestAccVPCVPC_tags_IgnoreTags_Overlap_DefaultTag(t *testing.T) { +func TestAccVPC_tags_IgnoreTags_Overlap_DefaultTag(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc @@ -1993,7 +1993,7 @@ func TestAccVPCVPC_tags_IgnoreTags_Overlap_DefaultTag(t *testing.T) { }) } -func TestAccVPCVPC_tags_IgnoreTags_Overlap_ResourceTag(t *testing.T) { +func TestAccVPC_tags_IgnoreTags_Overlap_ResourceTag(t *testing.T) { ctx := acctest.Context(t) var v awstypes.Vpc diff --git a/internal/service/iam/role.go b/internal/service/iam/role.go index dce4dbd39e38..4f625fd3f235 100644 --- a/internal/service/iam/role.go +++ b/internal/service/iam/role.go @@ -213,7 +213,7 @@ func resourceRole() *schema.Resource { } // @SDKListResource("aws_iam_role") -func instanceResourceAsListResource() inttypes.ListResourceForSDK { +func roleResourceAsListResource() inttypes.ListResourceForSDK { l := roleListResource{} l.SetResourceSchema(resourceRole()) diff --git a/internal/service/iam/service_package_gen.go b/internal/service/iam/service_package_gen.go index 85301f905015..144e10450378 100644 --- a/internal/service/iam/service_package_gen.go +++ b/internal/service/iam/service_package_gen.go @@ -434,7 +434,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*inttypes.ServicePa func (p *servicePackage) SDKListResources(ctx context.Context) iter.Seq[*inttypes.ServicePackageSDKListResource] { return slices.Values([]*inttypes.ServicePackageSDKListResource{ { - Factory: instanceResourceAsListResource, + Factory: roleResourceAsListResource, TypeName: "aws_iam_role", Name: "Role", Region: unique.Make(inttypes.ResourceRegionDisabled()), diff --git a/website/docs/list-resources/vpc.html.markdown b/website/docs/list-resources/vpc.html.markdown new file mode 100644 index 000000000000..219f189878b0 --- /dev/null +++ b/website/docs/list-resources/vpc.html.markdown @@ -0,0 +1,63 @@ +--- +subcategory: "VPC (Virtual Private Cloud)" +layout: "aws" +page_title: "AWS: aws_vpc" +description: |- + Lists VPC resources. +--- + +# List Resource: aws_vpc + +Lists VPC resources. + +Note: The default VPC is not included. + +## Example Usage + +### Basic Usage + +```terraform +list "aws_vpc" "example" { + provider = aws +} +``` + +### Filter Usage + +This example will return VPCs with the tag `Project` with the value `example`. + +```terraform +list "aws_vpc" "example" { + provider = aws + + config { + filter { + name = "tag:Project" + values = ["example"] + } + } +} +``` + +## Argument Reference + +This list resource supports the following arguments: + +* `filter` - (Optional) One or more filters to apply to the search. + If multiple `filter` blocks are provided, they all must be true. + For a full reference of filter names, see [describe-vpcs in the AWS CLI reference][describe-vpcs]. + See [`filter` Block](#filter-block) below. +* `region` - (Optional) [Region](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints) to query. + Defaults to the Region set in the [provider configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference). +* `vpc_ids` - (Optional) List of VPC IDs to query. + +### `filter` Block + +The `filter` block supports the following arguments: + +* `name` - (Required) Name of the filter. + For a full reference of filter names, see [describe-vpcs in the AWS CLI reference][describe-vpcs]. + `is-default` is not supported. +* `values` - (Required) One or more values to match. + +[describe-vpcs]: http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-vpcs.html