diff --git a/.changelog/35199.txt b/.changelog/35199.txt new file mode 100644 index 000000000000..9b075259b938 --- /dev/null +++ b/.changelog/35199.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_rds_integration +``` diff --git a/.teamcity/scripts/provider_tests/acceptance_tests.sh b/.teamcity/scripts/provider_tests/acceptance_tests.sh index c7c400ea2f61..48a2becdeb49 100644 --- a/.teamcity/scripts/provider_tests/acceptance_tests.sh +++ b/.teamcity/scripts/provider_tests/acceptance_tests.sh @@ -48,6 +48,7 @@ TF_ACC=1 go test \ ./internal/json/... \ ./internal/logging/... \ ./internal/maps/... \ + ./internal/namevaluesfilters/... \ ./internal/provider/... \ ./internal/retry/... \ ./internal/sdkv2/... \ diff --git a/.teamcity/scripts/provider_tests/unit_tests.sh b/.teamcity/scripts/provider_tests/unit_tests.sh index 3065a44e82a0..0c942b55a552 100644 --- a/.teamcity/scripts/provider_tests/unit_tests.sh +++ b/.teamcity/scripts/provider_tests/unit_tests.sh @@ -20,6 +20,7 @@ go test \ ./internal/json/... \ ./internal/logging/... \ ./internal/maps/... \ + ./internal/namevaluesfilters/... \ ./internal/provider/... \ ./internal/retry/... \ ./internal/sdkv2/... \ diff --git a/internal/generate/common/generator.go b/internal/generate/common/generator.go index 7dbbd25dd94c..5a53fbbab1d7 100644 --- a/internal/generate/common/generator.go +++ b/internal/generate/common/generator.go @@ -7,6 +7,7 @@ import ( "bytes" "fmt" "go/format" + "maps" "os" "path" "strings" @@ -58,7 +59,7 @@ type Destination interface { CreateDirectories() error Write() error WriteBytes(body []byte) error - WriteTemplate(templateName, templateBody string, templateData any) error + WriteTemplate(templateName, templateBody string, templateData any, funcMaps ...template.FuncMap) error WriteTemplateSet(templates *template.Template, templateData any) error } @@ -129,8 +130,8 @@ func (d *baseDestination) WriteBytes(body []byte) error { return err } -func (d *baseDestination) WriteTemplate(templateName, templateBody string, templateData any) error { - body, err := parseTemplate(templateName, templateBody, templateData) +func (d *baseDestination) WriteTemplate(templateName, templateBody string, templateData any, funcMaps ...template.FuncMap) error { + body, err := parseTemplate(templateName, templateBody, templateData, funcMaps...) if err != nil { return err @@ -144,7 +145,7 @@ func (d *baseDestination) WriteTemplate(templateName, templateBody string, templ return d.WriteBytes(body) } -func parseTemplate(templateName, templateBody string, templateData any) ([]byte, error) { +func parseTemplate(templateName, templateBody string, templateData any, funcMaps ...template.FuncMap) ([]byte, error) { funcMap := template.FuncMap{ // FirstUpper returns a string with the first character as upper case. "FirstUpper": func(s string) string { @@ -157,6 +158,9 @@ func parseTemplate(templateName, templateBody string, templateData any) ([]byte, // Title returns a string with the first character of each word as upper case. "Title": cases.Title(language.Und, cases.NoLower).String, } + for _, v := range funcMaps { + maps.Copy(funcMap, v) // Extras overwrite defaults. + } tmpl, err := template.New(templateName).Funcs(funcMap).Parse(templateBody) if err != nil { diff --git a/internal/generate/namevaluesfilters/README.md b/internal/generate/namevaluesfilters/README.md deleted file mode 100644 index 757e52948098..000000000000 --- a/internal/generate/namevaluesfilters/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# namevaluesfilters - -The `namevaluesfilters` package is designed to provide a consistent interface for handling AWS resource filtering. - -This package implements a single `NameValuesFilters` type, which covers all filter handling logic, such as merging filters, via functions on the single type. The underlying implementation is compatible with Go operations such as `len()`. - -Full documentation for this package can be found on [GoDoc](https://godoc.org/github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters). - -Many AWS Go SDK services that support resource filtering have their service-specific Go type conversion functions to and from `NameValuesFilters` code generated. Converting from `NameValuesFilters` to AWS Go SDK types is done via `{SERVICE}Filters()` functions on the type. For more information about this code generation, see the [`generators/servicefilters` README](generators/servicefilters/README.md). - -Any filtering functions that cannot be generated should be hand implemented in a service-specific source file and follow the format of similar generated code wherever possible. The first line of the source file should be `// +build !generate`. This prevents the file's inclusion during the code generation phase. - -## Code Structure - -```text -internal/generate/namevaluesfilters -├── generators -│ └── servicefilters (generates service_filters_gen.go) -├── name_values_filters_test.go (unit tests for core logic) -├── name_values_filters.go (core logic) -├── service_generation_customizations.go (shared AWS Go SDK service customizations for generators) -├── service_filters_gen.go (generated AWS Go SDK service conversion functions) -└── _filters.go (any service-specific functions that cannot be generated) -``` diff --git a/internal/generate/namevaluesfilters/generators/servicefilters/README.md b/internal/generate/namevaluesfilters/generators/servicefilters/README.md deleted file mode 100644 index e86cf2585a08..000000000000 --- a/internal/generate/namevaluesfilters/generators/servicefilters/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# servicefilters - -This package contains a code generator to consistently handle the various AWS Go SDK service implementations for converting service filter types to/from `NameValuesFilters`. Not all AWS Go SDK services that support filters are generated in this manner. - -To run this code generator, execute `go generate ./...` from the root of the repository. The general workflow for the generator is: - -- Generate Go file contents via template from local variables and functions -- Go format file contents -- Write file contents to `service_filters_gen.go` file - -## Example Output - -```go -// DocDBFilters returns docdb service filters. -func (filters NameValuesFilters) DocDBFilters() []*docdb.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*docdb.Filter, 0, len(m)) - - for k, v := range m { - filter := &docdb.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} -``` - -## Implementing a New Generated Service - -- In `main.go`: Add service name, e.g. `docdb`, to one of the implementation handlers - - Use `sliceServiceNames` if the AWS Go SDK service implements a specific Go type such as `Filter` -- Run `go generate ./...` (or `make gen`) from the root of the repository to regenerate the code -- Run `go test ./...` (or `make test`) from the root of the repository to ensure the generated code compiles diff --git a/internal/generate/namevaluesfilters/generators/servicefilters/main.go b/internal/generate/namevaluesfilters/generators/servicefilters/main.go deleted file mode 100644 index 1c734fc7e029..000000000000 --- a/internal/generate/namevaluesfilters/generators/servicefilters/main.go +++ /dev/null @@ -1,128 +0,0 @@ -//go:build generate -// +build generate - -package main - -import ( - "bytes" - "go/format" - "log" - "os" - "sort" - "strings" - "text/template" - - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" -) - -const filename = `service_filters_gen.go` - -// Representing types such as []*fsx.Filter, []*rds.Filter, ... -var sliceServiceNames = []string{ - "autoscaling", - "databasemigrationservice", - "docdb", - "elasticinference", - "elasticsearchservice", - "fsx", - "imagebuilder", - "licensemanager", - "neptune", - "rds", - "resourcegroupstaggingapi", - "route53resolver", -} - -type TemplateData struct { - SliceServiceNames []string -} - -func main() { - // Always sort to reduce any potential generation churn - sort.Strings(sliceServiceNames) - - templateData := TemplateData{ - SliceServiceNames: sliceServiceNames, - } - templateFuncMap := template.FuncMap{ - "FilterPackage": namevaluesfilters.ServiceFilterPackage, - "FilterType": namevaluesfilters.ServiceFilterType, - "FilterTypeNameField": namevaluesfilters.ServiceFilterTypeNameField, - "FilterTypeValuesField": namevaluesfilters.ServiceFilterTypeValuesField, - "Title": strings.Title, - } - - tmpl, err := template.New("servicefilters").Funcs(templateFuncMap).Parse(templateBody) - - if err != nil { - log.Fatalf("error parsing template: %s", err) - } - - var buffer bytes.Buffer - err = tmpl.Execute(&buffer, templateData) - - if err != nil { - log.Fatalf("error executing template: %s", err) - } - - generatedFileContents, err := format.Source(buffer.Bytes()) - - if err != nil { - log.Fatalf("error formatting generated file: %s", err) - } - - f, err := os.Create(filename) - - if err != nil { - log.Fatalf("error creating file (%s): %s", filename, err) - } - - defer f.Close() - - _, err = f.Write(generatedFileContents) - - if err != nil { - log.Fatalf("error writing to file (%s): %s", filename, err) - } -} - -var templateBody = ` -// Code generated by generators/servicefilters/main.go; DO NOT EDIT. - -package namevaluesfilters - -import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports - "github.com/aws/aws-sdk-go/aws" -{{- range .SliceServiceNames }} -{{- if eq . (. | FilterPackage) }} - "github.com/aws/aws-sdk-go/service/{{ . }}" -{{- end }} -{{- end }} -) - -// []*SERVICE.Filter handling -{{- range .SliceServiceNames }} - -// {{ . | Title }}Filters returns {{ . }} service filters. -func (filters NameValuesFilters) {{ . | Title }}Filters() []*{{ . | FilterPackage }}.{{ . | FilterType }} { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*{{ . | FilterPackage }}.{{ . | FilterType }}, 0, len(m)) - - for k, v := range m { - filter := &{{ . | FilterPackage }}.{{ . | FilterType }}{ - {{ . | FilterTypeNameField }}: aws.String(k), - {{ . | FilterTypeValuesField }}: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} -{{- end }} -` diff --git a/internal/generate/namevaluesfilters/service_filters_gen.go b/internal/generate/namevaluesfilters/service_filters_gen.go deleted file mode 100644 index 737bce86a01a..000000000000 --- a/internal/generate/namevaluesfilters/service_filters_gen.go +++ /dev/null @@ -1,285 +0,0 @@ -// Code generated by generators/servicefilters/main.go; DO NOT EDIT. - -package namevaluesfilters - -import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/autoscaling" - "github.com/aws/aws-sdk-go/service/databasemigrationservice" - "github.com/aws/aws-sdk-go/service/docdb" - "github.com/aws/aws-sdk-go/service/elasticinference" - "github.com/aws/aws-sdk-go/service/elasticsearchservice" - "github.com/aws/aws-sdk-go/service/fsx" - "github.com/aws/aws-sdk-go/service/imagebuilder" - "github.com/aws/aws-sdk-go/service/licensemanager" - "github.com/aws/aws-sdk-go/service/neptune" - "github.com/aws/aws-sdk-go/service/rds" - "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" - "github.com/aws/aws-sdk-go/service/route53resolver" -) - -// []*SERVICE.Filter handling - -// AutoScalingFilters returns autoscaling service filters. -func (filters NameValuesFilters) AutoScalingFilters() []*autoscaling.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*autoscaling.Filter, 0, len(m)) - - for k, v := range m { - filter := &autoscaling.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// DatabasemigrationserviceFilters returns databasemigrationservice service filters. -func (filters NameValuesFilters) DatabasemigrationserviceFilters() []*databasemigrationservice.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*databasemigrationservice.Filter, 0, len(m)) - - for k, v := range m { - filter := &databasemigrationservice.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// DocDBFilters returns docdb service filters. -func (filters NameValuesFilters) DocDBFilters() []*docdb.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*docdb.Filter, 0, len(m)) - - for k, v := range m { - filter := &docdb.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// ElasticinferenceFilters returns elasticinference service filters. -func (filters NameValuesFilters) ElasticinferenceFilters() []*elasticinference.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*elasticinference.Filter, 0, len(m)) - - for k, v := range m { - filter := &elasticinference.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// ElasticsearchserviceFilters returns elasticsearchservice service filters. -func (filters NameValuesFilters) ElasticsearchserviceFilters() []*elasticsearchservice.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*elasticsearchservice.Filter, 0, len(m)) - - for k, v := range m { - filter := &elasticsearchservice.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// FSxFilters returns fsx service filters. -func (filters NameValuesFilters) FSxFilters() []*fsx.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*fsx.Filter, 0, len(m)) - - for k, v := range m { - filter := &fsx.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// ImagebuilderFilters returns imagebuilder service filters. -func (filters NameValuesFilters) ImagebuilderFilters() []*imagebuilder.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*imagebuilder.Filter, 0, len(m)) - - for k, v := range m { - filter := &imagebuilder.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// LicensemanagerFilters returns licensemanager service filters. -func (filters NameValuesFilters) LicensemanagerFilters() []*licensemanager.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*licensemanager.Filter, 0, len(m)) - - for k, v := range m { - filter := &licensemanager.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// NeptuneFilters returns neptune service filters. -func (filters NameValuesFilters) NeptuneFilters() []*neptune.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*neptune.Filter, 0, len(m)) - - for k, v := range m { - filter := &neptune.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// RDSFilters returns rds service filters. -func (filters NameValuesFilters) RDSFilters() []*rds.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*rds.Filter, 0, len(m)) - - for k, v := range m { - filter := &rds.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// ResourcegroupstaggingapiFilters returns resourcegroupstaggingapi service filters. -func (filters NameValuesFilters) ResourcegroupstaggingapiFilters() []*resourcegroupstaggingapi.TagFilter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*resourcegroupstaggingapi.TagFilter, 0, len(m)) - - for k, v := range m { - filter := &resourcegroupstaggingapi.TagFilter{ - Key: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} - -// Route53resolverFilters returns route53resolver service filters. -func (filters NameValuesFilters) Route53resolverFilters() []*route53resolver.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*route53resolver.Filter, 0, len(m)) - - for k, v := range m { - filter := &route53resolver.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} diff --git a/internal/generate/namevaluesfilters/v1/file.tmpl b/internal/generate/namevaluesfilters/v1/file.tmpl new file mode 100644 index 000000000000..7d1c17124d5c --- /dev/null +++ b/internal/generate/namevaluesfilters/v1/file.tmpl @@ -0,0 +1,38 @@ +// Code generated by generators/servicefilters/main.go; DO NOT EDIT. + +package v1 + +import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports + "github.com/aws/aws-sdk-go/aws" +{{- range .SliceServiceNames }} +{{- if eq . (. | FilterPackage) }} + "github.com/aws/aws-sdk-go/service/{{ . }}" +{{- end }} +{{- end }} +) + +// []*SERVICE.Filter handling +{{- range .SliceServiceNames }} + +// {{ . | ProviderNameUpper }}Filters returns {{ . }} service filters. +func (filters NameValuesFilters) {{ . | ProviderNameUpper }}Filters() []*{{ . | FilterPackage }}.{{ . | FilterType }} { + m := filters.Map() + + if len(m) == 0 { + return nil + } + + result := make([]*{{ . | FilterPackage }}.{{ . | FilterType }}, 0, len(m)) + + for k, v := range m { + filter := &{{ . | FilterPackage }}.{{ . | FilterType }}{ + {{ . | FilterTypeNameField }}: aws.String(k), + {{ . | FilterTypeValuesField }}: aws.StringSlice(v), + } + + result = append(result, filter) + } + + return result +} +{{- end }} diff --git a/internal/generate/namevaluesfilters/v1/main.go b/internal/generate/namevaluesfilters/v1/main.go new file mode 100644 index 000000000000..88f2a12033bd --- /dev/null +++ b/internal/generate/namevaluesfilters/v1/main.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build generate +// +build generate + +package main + +import ( + _ "embed" + "sort" + "text/template" + + "github.com/hashicorp/terraform-provider-aws/internal/generate/common" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" + "github.com/hashicorp/terraform-provider-aws/names" +) + +type TemplateData struct { + SliceServiceNames []string +} + +func main() { + const ( + filename = `service_filters_gen.go` + ) + g := common.NewGenerator() + + g.Infof("Generating internal/namevaluesfilters/v1/%s", filename) + + // Representing types such as []*fsx.Filter, []*rds.Filter, ... + sliceServiceNames := []string{ + "imagebuilder", + "rds", + "route53resolver", + } + // Always sort to reduce any potential generation churn + sort.Strings(sliceServiceNames) + + td := TemplateData{ + SliceServiceNames: sliceServiceNames, + } + templateFuncMap := template.FuncMap{ + "FilterPackage": namevaluesfiltersv1.ServiceFilterPackage, + "FilterType": namevaluesfiltersv1.ServiceFilterType, + "FilterTypeNameField": namevaluesfiltersv1.ServiceFilterTypeNameField, + "FilterTypeValuesField": namevaluesfiltersv1.ServiceFilterTypeValuesField, + "ProviderNameUpper": names.ProviderNameUpper, + } + + d := g.NewGoFileDestination(filename) + + if err := d.WriteTemplate("namevaluesfilters", tmpl, td, templateFuncMap); err != nil { + g.Fatalf("generating file (%s): %s", filename, err) + } + + if err := d.Write(); err != nil { + g.Fatalf("generating file (%s): %s", filename, err) + } +} + +//go:embed file.tmpl +var tmpl string diff --git a/internal/generate/namevaluesfilters/v2/file.tmpl b/internal/generate/namevaluesfilters/v2/file.tmpl new file mode 100644 index 000000000000..42543ef94e09 --- /dev/null +++ b/internal/generate/namevaluesfilters/v2/file.tmpl @@ -0,0 +1,36 @@ +// Code generated by generators/servicefilters/main.go; DO NOT EDIT. + +package v2 + +import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports + "github.com/aws/aws-sdk-go-v2/aws" +{{- range .SliceServiceNames }} + {{ . | FilterPackage}} +{{- end }} +) + +// []*SERVICE.Filter handling +{{- range .SliceServiceNames }} + +// {{ . | ProviderNameUpper }}Filters returns {{ . }} service filters. +func (filters NameValuesFilters) {{ . | ProviderNameUpper }}Filters() []{{ . | FilterPackagePrefix }}.{{ . | FilterType }} { + m := filters.Map() + + if len(m) == 0 { + return nil + } + + result := make([]{{ . | FilterPackagePrefix }}.{{ . | FilterType }}, 0, len(m)) + + for k, v := range m { + filter := {{ . | FilterPackagePrefix }}.{{ . | FilterType }}{ + {{ . | FilterTypeNameField }}: {{ . | FilterTypeNameFunc }}(k), + {{ . | FilterTypeValuesField }}: v, + } + + result = append(result, filter) + } + + return result +} +{{- end }} diff --git a/internal/generate/namevaluesfilters/v2/main.go b/internal/generate/namevaluesfilters/v2/main.go new file mode 100644 index 000000000000..bb0eadba1310 --- /dev/null +++ b/internal/generate/namevaluesfilters/v2/main.go @@ -0,0 +1,64 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build generate +// +build generate + +package main + +import ( + _ "embed" + "sort" + "text/template" + + "github.com/hashicorp/terraform-provider-aws/internal/generate/common" + namevaluesfiltersv2 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v2" + "github.com/hashicorp/terraform-provider-aws/names" +) + +type TemplateData struct { + SliceServiceNames []string +} + +func main() { + const ( + filename = `service_filters_gen.go` + ) + g := common.NewGenerator() + + g.Infof("Generating internal/namevaluesfilters/v2/%s", filename) + + // Representing types such as []*ec2.Filter, []*rds.Filter, ... + sliceServiceNames := []string{ + "rds", + "secretsmanager", + } + // Always sort to reduce any potential generation churn + sort.Strings(sliceServiceNames) + + td := TemplateData{ + SliceServiceNames: sliceServiceNames, + } + templateFuncMap := template.FuncMap{ + "FilterPackage": namevaluesfiltersv2.ServiceFilterPackage, + "FilterPackagePrefix": namevaluesfiltersv2.ServiceFilterPackagePrefix, + "FilterType": namevaluesfiltersv2.ServiceFilterType, + "FilterTypeNameField": namevaluesfiltersv2.ServiceFilterTypeNameField, + "FilterTypeNameFunc": namevaluesfiltersv2.ServiceFilterTypeNameFunc, + "FilterTypeValuesField": namevaluesfiltersv2.ServiceFilterTypeValuesField, + "ProviderNameUpper": names.ProviderNameUpper, + } + + d := g.NewGoFileDestination(filename) + + if err := d.WriteTemplate("namevaluesfiltersv2", tmpl, td, templateFuncMap); err != nil { + g.Fatalf("generating file (%s): %s", filename, err) + } + + if err := d.Write(); err != nil { + g.Fatalf("generating file (%s): %s", filename, err) + } +} + +//go:embed file.tmpl +var tmpl string diff --git a/internal/generate/namevaluesfiltersv2/README.md b/internal/generate/namevaluesfiltersv2/README.md deleted file mode 100644 index 73e80758c4ac..000000000000 --- a/internal/generate/namevaluesfiltersv2/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# namevaluesfiltersv2 - -The `namevaluesfiltersv2` package is designed to provide a consistent interface for handling AWS resource filtering for AWS SDK for Go v2. - -This package implements a single `NameValuesFilters` type, which covers all filter handling logic, such as merging filters, via functions on the single type. The underlying implementation is compatible with Go operations such as `len()`. - -Full documentation for this package can be found on [GoDoc](https://godoc.org/github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfiltersv2). - -Many AWS Go SDK v2 services that support resource filtering have their service-specific Go type conversion functions to and from `NameValuesFilters` code generated. Converting from `NameValuesFilters` to AWS Go SDK v2 types is done via `{SERVICE}Filters()` functions on the type. For more information about this code generation, see the [`generators/servicefilters` README](generators/servicefilters/README.md). - -Any filtering functions that cannot be generated should be hand implemented in a service-specific source file and follow the format of similar generated code wherever possible. The first line of the source file should be `// +build !generate`. This prevents the file's inclusion during the code generation phase. - -## Code Structure - -```text -internal/generate/namevaluesfiltersv2 -├── generators -│ └── servicefilters (generates service_filters_gen.go) -├── name_values_filters_test.go (unit tests for core logic) -├── name_values_filters.go (core logic) -├── service_generation_customizations.go (shared AWS Go SDK service customizations for generators) -├── service_filters_gen.go (generated AWS Go SDK service conversion functions) -└── _filters.go (any service-specific functions that cannot be generated) -``` diff --git a/internal/generate/namevaluesfiltersv2/generators/servicefilters/README.md b/internal/generate/namevaluesfiltersv2/generators/servicefilters/README.md deleted file mode 100644 index e86cf2585a08..000000000000 --- a/internal/generate/namevaluesfiltersv2/generators/servicefilters/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# servicefilters - -This package contains a code generator to consistently handle the various AWS Go SDK service implementations for converting service filter types to/from `NameValuesFilters`. Not all AWS Go SDK services that support filters are generated in this manner. - -To run this code generator, execute `go generate ./...` from the root of the repository. The general workflow for the generator is: - -- Generate Go file contents via template from local variables and functions -- Go format file contents -- Write file contents to `service_filters_gen.go` file - -## Example Output - -```go -// DocDBFilters returns docdb service filters. -func (filters NameValuesFilters) DocDBFilters() []*docdb.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]*docdb.Filter, 0, len(m)) - - for k, v := range m { - filter := &docdb.Filter{ - Name: aws.String(k), - Values: aws.StringSlice(v), - } - - result = append(result, filter) - } - - return result -} -``` - -## Implementing a New Generated Service - -- In `main.go`: Add service name, e.g. `docdb`, to one of the implementation handlers - - Use `sliceServiceNames` if the AWS Go SDK service implements a specific Go type such as `Filter` -- Run `go generate ./...` (or `make gen`) from the root of the repository to regenerate the code -- Run `go test ./...` (or `make test`) from the root of the repository to ensure the generated code compiles diff --git a/internal/generate/namevaluesfiltersv2/generators/servicefilters/main.go b/internal/generate/namevaluesfiltersv2/generators/servicefilters/main.go deleted file mode 100644 index 88368f5110da..000000000000 --- a/internal/generate/namevaluesfiltersv2/generators/servicefilters/main.go +++ /dev/null @@ -1,117 +0,0 @@ -//go:build generate -// +build generate - -package main - -import ( - "bytes" - "go/format" - "log" - "os" - "sort" - "strings" - "text/template" - - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfiltersv2" -) - -const filename = `service_filters_v2_gen.go` - -// Representing types such as []*ec2.Filter, []*rds.Filter, ... -var sliceServiceNames = []string{ - "secretsmanager", -} - -type TemplateData struct { - SliceServiceNames []string -} - -func main() { - // Always sort to reduce any potential generation churn - sort.Strings(sliceServiceNames) - - templateData := TemplateData{ - SliceServiceNames: sliceServiceNames, - } - templateFuncMap := template.FuncMap{ - "FilterPackage": namevaluesfiltersv2.ServiceFilterPackage, - "FilterPackagePrefix": namevaluesfiltersv2.ServiceFilterPackagePrefix, - "FilterType": namevaluesfiltersv2.ServiceFilterType, - "FilterTypeNameField": namevaluesfiltersv2.ServiceFilterTypeNameField, - "FilterTypeValuesField": namevaluesfiltersv2.ServiceFilterTypeValuesField, - "Title": strings.Title, - } - - tmpl, err := template.New("servicefilters").Funcs(templateFuncMap).Parse(templateBody) - - if err != nil { - log.Fatalf("error parsing template: %s", err) - } - - var buffer bytes.Buffer - err = tmpl.Execute(&buffer, templateData) - - if err != nil { - log.Fatalf("error executing template: %s", err) - } - - generatedFileContents, err := format.Source(buffer.Bytes()) - - if err != nil { - log.Fatalf("error formatting generated file: %s", err) - } - - f, err := os.Create(filename) - - if err != nil { - log.Fatalf("error creating file (%s): %s", filename, err) - } - - defer f.Close() - - _, err = f.Write(generatedFileContents) - - if err != nil { - log.Fatalf("error writing to file (%s): %s", filename, err) - } -} - -var templateBody = ` -// Code generated by generators/servicefilters/main.go; DO NOT EDIT. - -package namevaluesfiltersv2 - -import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports -{{- range .SliceServiceNames }} -{{- if eq . (. | FilterPackage) }} - {{ . }} -{{- end }} -{{- end }} -) - -// []*SERVICE.Filter handling -{{- range .SliceServiceNames }} - -// {{ . | Title }}Filters returns {{ . }} service filters. -func (filters NameValuesFilters) {{ . | Title }}Filters() []*{{ . | FilterPackagePrefix }}.{{ . | FilterType }} { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]{{ . | FilterPackagePrefix }}.{{ . | FilterType }}, 0, len(m)) - - for k, v := range m { - filter := {{ . | FilterPackagePrefix }}.{{ . | FilterType }}{ - {{ . | FilterTypeNameField }}: { . | FilterPackagePrefix }}.FilterNameStringType(k), - {{ . | FilterTypeValuesField }}: v, - } - - result = append(result, filter) - } - - return result -} -{{- end }} -` diff --git a/internal/generate/namevaluesfiltersv2/name_values_filters.go b/internal/generate/namevaluesfiltersv2/name_values_filters.go deleted file mode 100644 index 2bba6c56ced8..000000000000 --- a/internal/generate/namevaluesfiltersv2/name_values_filters.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package namevaluesfiltersv2 - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -// NameValuesFilters is a standard implementation for AWS resource filters. -// The AWS Go SDK is split into multiple service packages, each service with -// its own Go struct type representing a resource filter. To standardize logic -// across all these Go types, we convert them into this Go type. -type NameValuesFilters map[string][]string - -// Add adds missing and updates existing filters from common Terraform Provider SDK types. -// Supports map[string]string, map[string][]string, *schema.Set. -func (filters NameValuesFilters) Add(i interface{}) NameValuesFilters { - switch value := i.(type) { - case map[string]string: - for name, v := range value { - if values, ok := filters[name]; ok { - filters[name] = append(values, v) - } else { - values = []string{v} - filters[name] = values - } - } - - case map[string][]string: - // We can't use fallthrough here, so recurse. - return filters.Add(NameValuesFilters(value)) - - case NameValuesFilters: - for name, vs := range value { - if values, ok := filters[name]; ok { - filters[name] = append(values, vs...) - } else { - values = make([]string, len(vs)) - copy(values, vs) - filters[name] = values - } - } - - case *schema.Set: - // The set of filters described by Schema(). - for _, filter := range value.List() { - m := filter.(map[string]interface{}) - name := m["name"].(string) - - for _, v := range m["values"].(*schema.Set).List() { - if values, ok := filters[name]; ok { - filters[name] = append(values, v.(string)) - } else { - values = []string{v.(string)} - filters[name] = values - } - } - } - } - - return filters -} - -// Map returns filter names mapped to their values. -// Duplicate values are eliminated and empty values removed. -func (filters NameValuesFilters) Map() map[string][]string { - result := make(map[string][]string) - - for k, v := range filters { - targetValues := make([]string, 0) - - SOURCE_VALUES: - for _, sourceValue := range v { - if sourceValue == "" { - continue - } - - for _, targetValue := range targetValues { - if sourceValue == targetValue { - continue SOURCE_VALUES - } - } - - targetValues = append(targetValues, sourceValue) - } - - if len(targetValues) == 0 { - continue - } - - result[k] = targetValues - } - - return result -} - -// New creates NameValuesFilters from common Terraform Provider SDK types. -// Supports map[string]string, map[string][]string, *schema.Set. -func New(i interface{}) NameValuesFilters { - return make(NameValuesFilters).Add(i) -} - -// Schema returns a *schema.Schema that represents a set of custom filtering criteria -// that a user can specify as input to a data source. -// It is conventional for an attribute of this type to be included as a top-level attribute called "filter". -func Schema() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - }, - - "values": { - Type: schema.TypeSet, - Required: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - } -} diff --git a/internal/generate/namevaluesfiltersv2/name_values_filters_test.go b/internal/generate/namevaluesfiltersv2/name_values_filters_test.go deleted file mode 100644 index 5e98ab0ba728..000000000000 --- a/internal/generate/namevaluesfiltersv2/name_values_filters_test.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package namevaluesfiltersv2_test - -import ( - "reflect" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" -) - -func TestNameValuesFiltersMap(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - filters namevaluesfilters.NameValuesFilters - want map[string][]string - }{ - { - name: "empty", - filters: namevaluesfilters.New(map[string][]string{}), - want: map[string][]string{}, - }, - { - name: "empty_strings", - filters: namevaluesfilters.New(map[string][]string{ - "name1": {""}, - "name2": {"", ""}, - }), - want: map[string][]string{}, - }, - { - name: "duplicates", - filters: namevaluesfilters.New(map[string][]string{ - "name1": {"value1"}, - "name2": {"value2a", "value2b", "", "value2a", "value2c", "value2c"}, - }), - want: map[string][]string{ - "name1": {"value1"}, - "name2": {"value2a", "value2b", "value2c"}, - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - got := testCase.filters.Map() - - testNameValuesFiltersVerifyMap(t, got, testCase.want) - }) - } -} - -func TestNameValuesFiltersAdd(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - filters namevaluesfilters.NameValuesFilters - add interface{} - want map[string][]string - }{ - { - name: "empty", - filters: namevaluesfilters.New(map[string][]string{}), - add: nil, - want: map[string][]string{}, - }, - { - name: "add_all", - filters: namevaluesfilters.New(map[string]string{ - "name1": "value1", - "name2": "value2", - "name3": "value3", - }), - add: namevaluesfilters.New(map[string][]string{ - "name4": {"value4a", "value4b"}, - "name5": {"value5"}, - "name6": {"value6a", "value6b", "value6c"}, - }), - want: map[string][]string{ - "name1": {"value1"}, - "name2": {"value2"}, - "name3": {"value3"}, - "name4": {"value4a", "value4b"}, - "name5": {"value5"}, - "name6": {"value6a", "value6b", "value6c"}, - }, - }, - { - name: "mixed", - filters: namevaluesfilters.New(map[string][]string{ - "name1": {"value1a"}, - "name2": {"value2a", "value2b"}, - }), - add: map[string]string{ - "name1": "value1b", - "name3": "value3", - }, - want: map[string][]string{ - "name1": {"value1a", "value1b"}, - "name2": {"value2a", "value2b"}, - "name3": {"value3"}, - }, - }, - { - name: "from_set", - filters: namevaluesfilters.New(schema.NewSet(testNameValuesFiltersHashSet, []interface{}{ - map[string]interface{}{ - "name": "name1", - "values": schema.NewSet(schema.HashString, []interface{}{ - "value1", - }), - }, - map[string]interface{}{ - "name": "name2", - "values": schema.NewSet(schema.HashString, []interface{}{ - "value2a", - "value2b", - }), - }, - map[string]interface{}{ - "name": "name3", - "values": schema.NewSet(schema.HashString, []interface{}{ - "value3", - }), - }, - })), - add: map[string][]string{ - "name1": {"value1"}, - "name2": {"value2c"}, - }, - want: map[string][]string{ - "name1": {"value1"}, - "name2": {"value2a", "value2b", "value2c"}, - "name3": {"value3"}, - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - got := testCase.filters.Add(testCase.add) - - testNameValuesFiltersVerifyMap(t, got.Map(), testCase.want) - }) - } -} - -func testNameValuesFiltersVerifyMap(t *testing.T, got map[string][]string, want map[string][]string) { - for k, wantV := range want { - gotV, ok := got[k] - - if !ok { - t.Errorf("want missing name: %s", k) - continue - } - - if !reflect.DeepEqual(gotV, wantV) { - t.Errorf("got name (%s) values %s; want values %s", k, gotV, wantV) - } - } - - for k := range got { - if _, ok := want[k]; !ok { - t.Errorf("got extra name: %s", k) - } - } -} - -func testNameValuesFiltersHashSet(v interface{}) int { - m := v.(map[string]interface{}) - return create.StringHashcode(m["name"].(string)) -} diff --git a/internal/generate/namevaluesfiltersv2/service_filters_gen.go b/internal/generate/namevaluesfiltersv2/service_filters_gen.go deleted file mode 100644 index e25053e62443..000000000000 --- a/internal/generate/namevaluesfiltersv2/service_filters_gen.go +++ /dev/null @@ -1,31 +0,0 @@ -// Code generated by generators/servicefilters/main.go; DO NOT EDIT. - -package namevaluesfiltersv2 - -import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports - secretsmanagertypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" -) - -// []*SERVICE.Filter handling - -// SecretsmanagerFilters returns secretsmanager service filters. -func (filters NameValuesFilters) SecretsmanagerFilters() []secretsmanagertypes.Filter { - m := filters.Map() - - if len(m) == 0 { - return nil - } - - result := make([]secretsmanagertypes.Filter, 0, len(m)) - - for k, v := range m { - filter := secretsmanagertypes.Filter{ - Key: secretsmanagertypes.FilterNameStringType(k), - Values: v, - } - - result = append(result, filter) - } - - return result -} diff --git a/internal/generate/namevaluesfilters/name_values_filters.go b/internal/namevaluesfilters/name_values_filters.go similarity index 100% rename from internal/generate/namevaluesfilters/name_values_filters.go rename to internal/namevaluesfilters/name_values_filters.go diff --git a/internal/generate/namevaluesfilters/name_values_filters_test.go b/internal/namevaluesfilters/name_values_filters_test.go similarity index 98% rename from internal/generate/namevaluesfilters/name_values_filters_test.go rename to internal/namevaluesfilters/name_values_filters_test.go index 07a377e852b0..e4de0f0bf2eb 100644 --- a/internal/generate/namevaluesfilters/name_values_filters_test.go +++ b/internal/namevaluesfilters/name_values_filters_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" ) func TestNameValuesFiltersMap(t *testing.T) { diff --git a/internal/namevaluesfilters/v1/README.md b/internal/namevaluesfilters/v1/README.md new file mode 100644 index 000000000000..3793f5a824f1 --- /dev/null +++ b/internal/namevaluesfilters/v1/README.md @@ -0,0 +1,5 @@ +# namevaluesfilters + +The `namevaluesfilters/v1` package is designed to provide a consistent interface for handling AWS resource filtering with AWS SDK for Go v1. + +This package implements a single `NameValuesFilters` type, which covers all filter handling logic, such as merging filters, via functions on the single type. The underlying implementation is compatible with Go operations such as `len()`. diff --git a/internal/namevaluesfilters/v1/generate.go b/internal/namevaluesfilters/v1/generate.go new file mode 100644 index 000000000000..e56395048194 --- /dev/null +++ b/internal/namevaluesfilters/v1/generate.go @@ -0,0 +1,7 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:generate go run ../../generate/namevaluesfilters/v1/main.go +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package v1 diff --git a/internal/namevaluesfilters/v1/name_values_filters.go b/internal/namevaluesfilters/v1/name_values_filters.go new file mode 100644 index 000000000000..c8b81fabb46d --- /dev/null +++ b/internal/namevaluesfilters/v1/name_values_filters.go @@ -0,0 +1,16 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v1 + +import ( + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" +) + +type NameValuesFilters struct { + namevaluesfilters.NameValuesFilters +} + +func New(i interface{}) NameValuesFilters { + return NameValuesFilters{NameValuesFilters: namevaluesfilters.New(i)} +} diff --git a/internal/namevaluesfilters/v1/service_filters_gen.go b/internal/namevaluesfilters/v1/service_filters_gen.go new file mode 100644 index 000000000000..bab79ede734a --- /dev/null +++ b/internal/namevaluesfilters/v1/service_filters_gen.go @@ -0,0 +1,78 @@ +// Code generated by generators/servicefilters/main.go; DO NOT EDIT. + +package v1 + +import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/imagebuilder" + "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go/service/route53resolver" +) + +// []*SERVICE.Filter handling + +// ImageBuilderFilters returns imagebuilder service filters. +func (filters NameValuesFilters) ImageBuilderFilters() []*imagebuilder.Filter { + m := filters.Map() + + if len(m) == 0 { + return nil + } + + result := make([]*imagebuilder.Filter, 0, len(m)) + + for k, v := range m { + filter := &imagebuilder.Filter{ + Name: aws.String(k), + Values: aws.StringSlice(v), + } + + result = append(result, filter) + } + + return result +} + +// RDSFilters returns rds service filters. +func (filters NameValuesFilters) RDSFilters() []*rds.Filter { + m := filters.Map() + + if len(m) == 0 { + return nil + } + + result := make([]*rds.Filter, 0, len(m)) + + for k, v := range m { + filter := &rds.Filter{ + Name: aws.String(k), + Values: aws.StringSlice(v), + } + + result = append(result, filter) + } + + return result +} + +// Route53ResolverFilters returns route53resolver service filters. +func (filters NameValuesFilters) Route53ResolverFilters() []*route53resolver.Filter { + m := filters.Map() + + if len(m) == 0 { + return nil + } + + result := make([]*route53resolver.Filter, 0, len(m)) + + for k, v := range m { + filter := &route53resolver.Filter{ + Name: aws.String(k), + Values: aws.StringSlice(v), + } + + result = append(result, filter) + } + + return result +} diff --git a/internal/generate/namevaluesfilters/service_generation_customizations.go b/internal/namevaluesfilters/v1/service_generation_customizations.go similarity index 97% rename from internal/generate/namevaluesfilters/service_generation_customizations.go rename to internal/namevaluesfilters/v1/service_generation_customizations.go index 0f57d9385e27..9af370ab50b4 100644 --- a/internal/generate/namevaluesfilters/service_generation_customizations.go +++ b/internal/namevaluesfilters/v1/service_generation_customizations.go @@ -3,7 +3,7 @@ // This file contains code generation customizations for each AWS Go SDK service. -package namevaluesfilters +package v1 // ServiceFilterPackage determines the service filter type package. func ServiceFilterPackage(serviceName string) string { diff --git a/internal/namevaluesfilters/v2/README.md b/internal/namevaluesfilters/v2/README.md new file mode 100644 index 000000000000..cf3527987a27 --- /dev/null +++ b/internal/namevaluesfilters/v2/README.md @@ -0,0 +1,5 @@ +# namevaluesfiltersv2 + +The `namevaluesfilters/v2` package is designed to provide a consistent interface for handling AWS resource filtering with AWS SDK for Go v2. + +This package implements a single `NameValuesFilters` type, which covers all filter handling logic, such as merging filters, via functions on the single type. The underlying implementation is compatible with Go operations such as `len()`. diff --git a/internal/namevaluesfilters/v2/generate.go b/internal/namevaluesfilters/v2/generate.go new file mode 100644 index 000000000000..b990aefdc7f1 --- /dev/null +++ b/internal/namevaluesfilters/v2/generate.go @@ -0,0 +1,7 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:generate go run ../../generate/namevaluesfilters/v2/main.go +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package v2 diff --git a/internal/namevaluesfilters/v2/name_values_filters.go b/internal/namevaluesfilters/v2/name_values_filters.go new file mode 100644 index 000000000000..18d0279d8b37 --- /dev/null +++ b/internal/namevaluesfilters/v2/name_values_filters.go @@ -0,0 +1,16 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package v2 + +import ( + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" +) + +type NameValuesFilters struct { + namevaluesfilters.NameValuesFilters +} + +func New(i interface{}) NameValuesFilters { + return NameValuesFilters{NameValuesFilters: namevaluesfilters.New(i)} +} diff --git a/internal/namevaluesfilters/v2/service_filters_gen.go b/internal/namevaluesfilters/v2/service_filters_gen.go new file mode 100644 index 000000000000..afbe17801a30 --- /dev/null +++ b/internal/namevaluesfilters/v2/service_filters_gen.go @@ -0,0 +1,55 @@ +// Code generated by generators/servicefilters/main.go; DO NOT EDIT. + +package v2 + +import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports + "github.com/aws/aws-sdk-go-v2/aws" + rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" + secretsmanagertypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" +) + +// []*SERVICE.Filter handling + +// RDSFilters returns rds service filters. +func (filters NameValuesFilters) RDSFilters() []rdstypes.Filter { + m := filters.Map() + + if len(m) == 0 { + return nil + } + + result := make([]rdstypes.Filter, 0, len(m)) + + for k, v := range m { + filter := rdstypes.Filter{ + Name: aws.String(k), + Values: v, + } + + result = append(result, filter) + } + + return result +} + +// SecretsManagerFilters returns secretsmanager service filters. +func (filters NameValuesFilters) SecretsManagerFilters() []secretsmanagertypes.Filter { + m := filters.Map() + + if len(m) == 0 { + return nil + } + + result := make([]secretsmanagertypes.Filter, 0, len(m)) + + for k, v := range m { + filter := secretsmanagertypes.Filter{ + Key: secretsmanagertypes.FilterNameStringType(k), + Values: v, + } + + result = append(result, filter) + } + + return result +} diff --git a/internal/generate/namevaluesfiltersv2/service_generation_customizations.go b/internal/namevaluesfilters/v2/service_generation_customizations.go similarity index 68% rename from internal/generate/namevaluesfiltersv2/service_generation_customizations.go rename to internal/namevaluesfilters/v2/service_generation_customizations.go index 31bbba1fce2a..9e140fb6fe25 100644 --- a/internal/generate/namevaluesfiltersv2/service_generation_customizations.go +++ b/internal/namevaluesfilters/v2/service_generation_customizations.go @@ -3,7 +3,7 @@ // This file contains code generation customizations for each AWS Go SDK service. -package namevaluesfiltersv2 +package v2 import "fmt" @@ -11,7 +11,7 @@ import "fmt" func ServiceFilterPackage(serviceName string) string { switch serviceName { default: - return fmt.Sprintf("%[1]stypes \"github.com/aws/aws-sdk-go-v2/service/%[1]s/types\"", serviceName) + return fmt.Sprintf("%[1]s \"github.com/aws/aws-sdk-go-v2/service/%[2]s/types\"", ServiceFilterPackagePrefix(serviceName), serviceName) } } @@ -34,8 +34,20 @@ func ServiceFilterType(serviceName string) string { // ServiceFilterTypeNameField determines the service filter type name field. func ServiceFilterTypeNameField(serviceName string) string { switch serviceName { - default: + case "secretsmanager": return "Key" + default: + return "Name" + } +} + +// ServiceFilterTypeNameFunc determines the function called on the service filter type name. +func ServiceFilterTypeNameFunc(serviceName string) string { + switch serviceName { + case "secretsmanager": + return fmt.Sprintf("%[1]s.FilterNameStringType", ServiceFilterPackagePrefix(serviceName)) + default: + return "aws.String" } } diff --git a/internal/service/imagebuilder/components_data_source.go b/internal/service/imagebuilder/components_data_source.go index c707acab6a2d..0ec523328c95 100644 --- a/internal/service/imagebuilder/components_data_source.go +++ b/internal/service/imagebuilder/components_data_source.go @@ -13,7 +13,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -53,7 +54,7 @@ func dataSourceComponentsRead(ctx context.Context, d *schema.ResourceData, meta } if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).ImagebuilderFilters() + input.Filters = namevaluesfiltersv1.New(v.(*schema.Set)).ImageBuilderFilters() } var results []*imagebuilder.ComponentVersion diff --git a/internal/service/imagebuilder/container_recipes_data_source.go b/internal/service/imagebuilder/container_recipes_data_source.go index 836ebfdf4587..af125a2578a0 100644 --- a/internal/service/imagebuilder/container_recipes_data_source.go +++ b/internal/service/imagebuilder/container_recipes_data_source.go @@ -13,7 +13,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -53,7 +54,7 @@ func dataSourceContainerRecipesRead(ctx context.Context, d *schema.ResourceData, } if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).ImagebuilderFilters() + input.Filters = namevaluesfiltersv1.New(v.(*schema.Set)).ImageBuilderFilters() } var results []*imagebuilder.ContainerRecipeSummary diff --git a/internal/service/imagebuilder/distribution_configurations_data_source.go b/internal/service/imagebuilder/distribution_configurations_data_source.go index 513236a1cef0..f36f054521b0 100644 --- a/internal/service/imagebuilder/distribution_configurations_data_source.go +++ b/internal/service/imagebuilder/distribution_configurations_data_source.go @@ -12,7 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -43,7 +44,7 @@ func dataSourceDistributionConfigurationsRead(ctx context.Context, d *schema.Res input := &imagebuilder.ListDistributionConfigurationsInput{} if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).ImagebuilderFilters() + input.Filters = namevaluesfiltersv1.New(v.(*schema.Set)).ImageBuilderFilters() } var results []*imagebuilder.DistributionConfigurationSummary diff --git a/internal/service/imagebuilder/image_pipelines_data_source.go b/internal/service/imagebuilder/image_pipelines_data_source.go index 49fcb643ee57..0531e0f38ada 100644 --- a/internal/service/imagebuilder/image_pipelines_data_source.go +++ b/internal/service/imagebuilder/image_pipelines_data_source.go @@ -12,7 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -43,7 +44,7 @@ func dataSourceImagePipelinesRead(ctx context.Context, d *schema.ResourceData, m input := &imagebuilder.ListImagePipelinesInput{} if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).ImagebuilderFilters() + input.Filters = namevaluesfiltersv1.New(v.(*schema.Set)).ImageBuilderFilters() } var results []*imagebuilder.ImagePipeline diff --git a/internal/service/imagebuilder/image_recipes_data_source.go b/internal/service/imagebuilder/image_recipes_data_source.go index 862c84d9df36..9294eb90f407 100644 --- a/internal/service/imagebuilder/image_recipes_data_source.go +++ b/internal/service/imagebuilder/image_recipes_data_source.go @@ -13,7 +13,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -53,7 +54,7 @@ func dataSourceImageRecipesRead(ctx context.Context, d *schema.ResourceData, met } if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).ImagebuilderFilters() + input.Filters = namevaluesfiltersv1.New(v.(*schema.Set)).ImageBuilderFilters() } var results []*imagebuilder.ImageRecipeSummary diff --git a/internal/service/imagebuilder/infrastructure_configurations_data_source.go b/internal/service/imagebuilder/infrastructure_configurations_data_source.go index 0e392a8342e1..a729eeb563b1 100644 --- a/internal/service/imagebuilder/infrastructure_configurations_data_source.go +++ b/internal/service/imagebuilder/infrastructure_configurations_data_source.go @@ -12,7 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -43,7 +44,7 @@ func dataSourceInfrastructureConfigurationsRead(ctx context.Context, d *schema.R input := &imagebuilder.ListInfrastructureConfigurationsInput{} if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).ImagebuilderFilters() + input.Filters = namevaluesfiltersv1.New(v.(*schema.Set)).ImageBuilderFilters() } var results []*imagebuilder.InfrastructureConfigurationSummary diff --git a/internal/service/rds/certificate_data_source.go b/internal/service/rds/certificate_data_source.go index f125f8ab09b7..ec69392de46f 100644 --- a/internal/service/rds/certificate_data_source.go +++ b/internal/service/rds/certificate_data_source.go @@ -8,8 +8,9 @@ import ( "slices" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" + "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -17,10 +18,11 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_rds_certificate") -func DataSourceCertificate() *schema.Resource { +// @SDKDataSource("aws_rds_certificate", name="Certificate") +func dataSourceCertificate() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceCertificateRead, + Schema: map[string]*schema.Schema{ names.AttrARN: { Type: schema.TypeString, @@ -65,7 +67,7 @@ func DataSourceCertificate() *schema.Resource { func dataSourceCertificateRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSConn(ctx) + conn := meta.(*conns.AWSClient).RDSClient(ctx) input := &rds.DescribeCertificatesInput{} @@ -73,24 +75,17 @@ func dataSourceCertificateRead(ctx context.Context, d *schema.ResourceData, meta input.CertificateIdentifier = aws.String(v.(string)) } - var certificates []*rds.Certificate - - err := conn.DescribeCertificatesPagesWithContext(ctx, input, func(page *rds.DescribeCertificatesOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } + var certificates []types.Certificate - for _, certificate := range page.Certificates { - if certificate == nil { - continue - } + pages := rds.NewDescribeCertificatesPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) - certificates = append(certificates, certificate) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading RDS Certificates: %s", err) } - return !lastPage - }) - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading RDS Certificates: %s", err) + + certificates = append(certificates, page.Certificates...) } if len(certificates) == 0 { @@ -98,13 +93,13 @@ func dataSourceCertificateRead(ctx context.Context, d *schema.ResourceData, meta } // client side filtering - var certificate *rds.Certificate + var certificate *types.Certificate if d.Get("latest_valid_till").(bool) { - slices.SortFunc(certificates, func(a, b *rds.Certificate) int { + slices.SortFunc(certificates, func(a, b types.Certificate) int { return a.ValidTill.Compare(*b.ValidTill) }) - certificate = certificates[len(certificates)-1] + certificate = &certificates[len(certificates)-1] } else { if len(certificates) > 1 { return sdkdiag.AppendErrorf(diags, "multiple RDS Certificates match the criteria; try changing search query") @@ -112,27 +107,22 @@ func dataSourceCertificateRead(ctx context.Context, d *schema.ResourceData, meta if len(certificates) == 0 { return sdkdiag.AppendErrorf(diags, "no RDS Certificates match the criteria") } - certificate = certificates[0] + certificate = &certificates[0] } - d.SetId(aws.StringValue(certificate.CertificateIdentifier)) - + d.SetId(aws.ToString(certificate.CertificateIdentifier)) d.Set(names.AttrARN, certificate.CertificateArn) d.Set("certificate_type", certificate.CertificateType) d.Set("customer_override", certificate.CustomerOverride) - if certificate.CustomerOverrideValidTill != nil { - d.Set("customer_override_valid_till", aws.TimeValue(certificate.CustomerOverrideValidTill).Format(time.RFC3339)) + d.Set("customer_override_valid_till", aws.ToTime(certificate.CustomerOverrideValidTill).Format(time.RFC3339)) } - d.Set("thumbprint", certificate.Thumbprint) - if certificate.ValidFrom != nil { - d.Set("valid_from", aws.TimeValue(certificate.ValidFrom).Format(time.RFC3339)) + d.Set("valid_from", aws.ToTime(certificate.ValidFrom).Format(time.RFC3339)) } - if certificate.ValidTill != nil { - d.Set("valid_till", aws.TimeValue(certificate.ValidTill).Format(time.RFC3339)) + d.Set("valid_till", aws.ToTime(certificate.ValidTill).Format(time.RFC3339)) } return diags diff --git a/internal/service/rds/certificate_data_source_test.go b/internal/service/rds/certificate_data_source_test.go index 5017c4a7a4eb..b72e284089f0 100644 --- a/internal/service/rds/certificate_data_source_test.go +++ b/internal/service/rds/certificate_data_source_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go-v2/service/rds" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -63,11 +63,11 @@ func TestAccRDSCertificateDataSource_latestValidTill(t *testing.T) { } func testAccCertificatePreCheck(ctx context.Context, t *testing.T) { - conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) input := &rds.DescribeCertificatesInput{} - _, err := conn.DescribeCertificatesWithContext(ctx, input) + _, err := conn.DescribeCertificates(ctx, input) if acctest.PreCheckSkipError(err) { t.Skipf("skipping acceptance testing: %s", err) diff --git a/internal/service/rds/clusters_data_source.go b/internal/service/rds/clusters_data_source.go index f56fbccf4866..c8cde3ae3b9e 100644 --- a/internal/service/rds/clusters_data_source.go +++ b/internal/service/rds/clusters_data_source.go @@ -12,7 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -48,7 +49,7 @@ func dataSourceClustersRead(ctx context.Context, d *schema.ResourceData, meta in input := &rds.DescribeDBClustersInput{} if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).RDSFilters() + input.Filters = namevaluesfiltersv1.New(v.(*schema.Set)).RDSFilters() } var clusterArns []string diff --git a/internal/service/rds/consts.go b/internal/service/rds/consts.go index 045337bd217c..299a99512889 100644 --- a/internal/service/rds/consts.go +++ b/internal/service/rds/consts.go @@ -321,9 +321,9 @@ const ( ) const ( - ReservedInstanceStateActive = "active" - ReservedInstanceStateRetired = "retired" - ReservedInstanceStatePaymentPending = "payment-pending" + reservedInstanceStateActive = "active" + reservedInstanceStateRetired = "retired" + reservedInstanceStatePaymentPending = "payment-pending" ) const ( diff --git a/internal/service/rds/engine_version_data_source.go b/internal/service/rds/engine_version_data_source.go index 19431053a6b7..0d697e2d909e 100644 --- a/internal/service/rds/engine_version_data_source.go +++ b/internal/service/rds/engine_version_data_source.go @@ -5,189 +5,155 @@ package rds import ( "context" - "log" "sort" "github.com/YakDriver/go-version" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" + awstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv2 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v2" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_rds_engine_version") -func DataSourceEngineVersion() *schema.Resource { +// @SDKDataSource("aws_rds_engine_version", name="Engine Version") +func dataSourceEngineVersion() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceEngineVersionRead, + Schema: map[string]*schema.Schema{ "default_character_set": { Type: schema.TypeString, Computed: true, }, - "default_only": { Type: schema.TypeBool, Optional: true, }, - names.AttrEngine: { Type: schema.TypeString, Required: true, }, - "engine_description": { Type: schema.TypeString, Computed: true, }, - "exportable_log_types": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, - names.AttrFilter: namevaluesfilters.Schema(), - "has_major_target": { Type: schema.TypeBool, Optional: true, }, - "has_minor_target": { Type: schema.TypeBool, Optional: true, }, - "include_all": { Type: schema.TypeBool, Optional: true, }, - "latest": { Type: schema.TypeBool, Optional: true, }, - "parameter_group_family": { Type: schema.TypeString, Computed: true, Optional: true, }, - "preferred_major_targets": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "preferred_upgrade_targets": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "preferred_versions": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - names.AttrStatus: { Type: schema.TypeString, Computed: true, }, - "supported_character_sets": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, - "supported_feature_names": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, - "supported_modes": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, - "supported_timezones": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, - "supports_global_databases": { Type: schema.TypeBool, Computed: true, }, - "supports_limitless_database": { Type: schema.TypeBool, Computed: true, }, - "supports_log_exports_to_cloudwatch": { Type: schema.TypeBool, Computed: true, }, - "supports_parallel_query": { Type: schema.TypeBool, Computed: true, }, - "supports_read_replica": { Type: schema.TypeBool, Computed: true, }, - "valid_major_targets": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, - "valid_minor_targets": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, - "valid_upgrade_targets": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, - names.AttrVersion: { Type: schema.TypeString, Computed: true, Optional: true, }, - "version_actual": { Type: schema.TypeString, Computed: true, }, - "version_description": { Type: schema.TypeString, Computed: true, @@ -198,7 +164,7 @@ func DataSourceEngineVersion() *schema.Resource { func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSConn(ctx) + conn := meta.(*conns.AWSClient).RDSClient(ctx) input := &rds.DescribeDBEngineVersionsInput{ ListSupportedCharacterSets: aws.Bool(true), @@ -210,7 +176,7 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me } if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).RDSFilters() + input.Filters = namevaluesfiltersv2.New(v.(*schema.Set)).RDSFilters() } if v, ok := d.GetOk("parameter_group_family"); ok { @@ -244,21 +210,17 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me input.DefaultOnly = aws.Bool(true) } - log.Printf("[DEBUG] Reading RDS engine versions: %v", input) - var engineVersions []*rds.DBEngineVersion + var engineVersions []awstypes.DBEngineVersion - err := conn.DescribeDBEngineVersionsPagesWithContext(ctx, input, func(resp *rds.DescribeDBEngineVersionsOutput, lastPage bool) bool { - for _, engineVersion := range resp.DBEngineVersions { - if engineVersion == nil { - continue - } + pages := rds.NewDescribeDBEngineVersionsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) - engineVersions = append(engineVersions, engineVersion) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading RDS engine versions: %s", err) } - return !lastPage - }) - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading RDS engine versions: %s", err) + + engineVersions = append(engineVersions, page.DBEngineVersions...) } if len(engineVersions) == 0 { @@ -269,7 +231,7 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me // preferred versions if l := d.Get("preferred_versions").([]interface{}); len(l) > 0 { - var preferredVersions []*rds.DBEngineVersion + var preferredVersions []awstypes.DBEngineVersion for _, elem := range l { preferredVersion, ok := elem.(string) @@ -279,7 +241,7 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me } for _, engineVersion := range engineVersions { - if preferredVersion == aws.StringValue(engineVersion.EngineVersion) { + if preferredVersion == aws.ToString(engineVersion.EngineVersion) { preferredVersions = append(preferredVersions, engineVersion) } } @@ -295,7 +257,7 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me // preferred upgrade targets if l := d.Get("preferred_upgrade_targets").([]interface{}); len(l) > 0 { - var prefUTs []*rds.DBEngineVersion + var prefUTs []awstypes.DBEngineVersion engineVersionsLoop: for _, engineVersion := range engineVersions { @@ -306,7 +268,7 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me continue } - if prefUT == aws.StringValue(upgradeTarget.EngineVersion) { + if prefUT == aws.ToString(upgradeTarget.EngineVersion) { prefUTs = append(prefUTs, engineVersion) continue engineVersionsLoop } @@ -324,7 +286,7 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me // preferred major targets if l := d.Get("preferred_major_targets").([]interface{}); len(l) > 0 { - var prefMTs []*rds.DBEngineVersion + var prefMTs []awstypes.DBEngineVersion majorsLoop: for _, engineVersion := range engineVersions { @@ -335,7 +297,7 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me continue } - if prefMT == aws.StringValue(upgradeTarget.EngineVersion) && aws.BoolValue(upgradeTarget.IsMajorVersionUpgrade) { + if prefMT == aws.ToString(upgradeTarget.EngineVersion) && aws.ToBool(upgradeTarget.IsMajorVersionUpgrade) { prefMTs = append(prefMTs, engineVersion) continue majorsLoop } @@ -352,12 +314,12 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me } if v, ok := d.GetOk("has_minor_target"); ok && v.(bool) { - var wMinor []*rds.DBEngineVersion + var wMinor []awstypes.DBEngineVersion hasMinorLoop: for _, engineVersion := range engineVersions { for _, upgradeTarget := range engineVersion.ValidUpgradeTarget { - if !aws.BoolValue(upgradeTarget.IsMajorVersionUpgrade) { + if !aws.ToBool(upgradeTarget.IsMajorVersionUpgrade) { wMinor = append(wMinor, engineVersion) continue hasMinorLoop } @@ -372,12 +334,12 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me } if v, ok := d.GetOk("has_major_target"); ok && v.(bool) { - var wMajor []*rds.DBEngineVersion + var wMajor []awstypes.DBEngineVersion hasMajorLoop: for _, engineVersion := range engineVersions { for _, upgradeTarget := range engineVersion.ValidUpgradeTarget { - if aws.BoolValue(upgradeTarget.IsMajorVersionUpgrade) { + if aws.ToBool(upgradeTarget.IsMajorVersionUpgrade) { wMajor = append(wMajor, engineVersion) continue hasMajorLoop } @@ -391,19 +353,19 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me engineVersions = wMajor } - var found *rds.DBEngineVersion + var found *awstypes.DBEngineVersion if v, ok := d.GetOk("latest"); ok && v.(bool) { sortEngineVersions(engineVersions) - found = engineVersions[len(engineVersions)-1] + found = &engineVersions[len(engineVersions)-1] } if found == nil && len(engineVersions) == 1 { - found = engineVersions[0] + found = &engineVersions[0] } if found == nil && len(engineVersions) > 0 && prefSearch { - found = engineVersions[0] + found = &engineVersions[0] } if found == nil && len(engineVersions) > 1 { @@ -414,33 +376,23 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me return sdkdiag.AppendErrorf(diags, "no RDS engine versions match the criteria: %+v", input) } - d.SetId(aws.StringValue(found.EngineVersion)) - + d.SetId(aws.ToString(found.EngineVersion)) if found.DefaultCharacterSet != nil { d.Set("default_character_set", found.DefaultCharacterSet.CharacterSetName) } - d.Set(names.AttrEngine, found.Engine) d.Set("engine_description", found.DBEngineDescription) d.Set("exportable_log_types", found.ExportableLogTypes) d.Set("parameter_group_family", found.DBParameterGroupFamily) d.Set(names.AttrStatus, found.Status) - - var characterSets []string - for _, cs := range found.SupportedCharacterSets { - characterSets = append(characterSets, aws.StringValue(cs.CharacterSetName)) - } - d.Set("supported_character_sets", characterSets) - + d.Set("supported_character_sets", tfslices.ApplyToAll(found.SupportedCharacterSets, func(v awstypes.CharacterSet) string { + return aws.ToString(v.CharacterSetName) + })) d.Set("supported_feature_names", found.SupportedFeatureNames) d.Set("supported_modes", found.SupportedEngineModes) - - var timezones []string - for _, tz := range found.SupportedTimezones { - timezones = append(timezones, aws.StringValue(tz.TimezoneName)) - } - d.Set("supported_timezones", timezones) - + d.Set("supported_timezones", tfslices.ApplyToAll(found.SupportedTimezones, func(v awstypes.Timezone) string { + return aws.ToString(v.TimezoneName) + })) d.Set("supports_global_databases", found.SupportsGlobalDatabases) d.Set("supports_limitless_database", found.SupportsLimitlessDatabase) d.Set("supports_log_exports_to_cloudwatch", found.SupportsLogExportsToCloudwatchLogs) @@ -451,14 +403,14 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me var minorTargets []string var majorTargets []string for _, ut := range found.ValidUpgradeTarget { - upgradeTargets = append(upgradeTargets, aws.StringValue(ut.EngineVersion)) + upgradeTargets = append(upgradeTargets, aws.ToString(ut.EngineVersion)) - if aws.BoolValue(ut.IsMajorVersionUpgrade) { - majorTargets = append(majorTargets, aws.StringValue(ut.EngineVersion)) + if aws.ToBool(ut.IsMajorVersionUpgrade) { + majorTargets = append(majorTargets, aws.ToString(ut.EngineVersion)) continue } - minorTargets = append(minorTargets, aws.StringValue(ut.EngineVersion)) + minorTargets = append(minorTargets, aws.ToString(ut.EngineVersion)) } d.Set("valid_upgrade_targets", upgradeTargets) d.Set("valid_minor_targets", minorTargets) @@ -471,13 +423,13 @@ func dataSourceEngineVersionRead(ctx context.Context, d *schema.ResourceData, me return diags } -func sortEngineVersions(engineVersions []*rds.DBEngineVersion) { +func sortEngineVersions(engineVersions []awstypes.DBEngineVersion) { if len(engineVersions) < 2 { return } sort.Slice(engineVersions, func(i, j int) bool { - return version.LessThanWithTime(engineVersions[i].CreateTime, engineVersions[j].CreateTime, aws.StringValue(engineVersions[i].EngineVersion), aws.StringValue(engineVersions[j].EngineVersion)) + return version.LessThanWithTime(engineVersions[i].CreateTime, engineVersions[j].CreateTime, aws.ToString(engineVersions[i].EngineVersion), aws.ToString(engineVersions[j].EngineVersion)) }) } diff --git a/internal/service/rds/engine_version_data_source_test.go b/internal/service/rds/engine_version_data_source_test.go index 53537119ed2a..021c23fbd529 100644 --- a/internal/service/rds/engine_version_data_source_test.go +++ b/internal/service/rds/engine_version_data_source_test.go @@ -10,8 +10,8 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -119,9 +119,9 @@ func TestAccRDSEngineVersionDataSource_preferredVersionsPreferredUpgradeTargets( CheckDestroy: nil, Steps: []resource.TestStep{ { - Config: testAccEngineVersionDataSourceConfig_preferredVersionsPreferredUpgrades(tfrds.InstanceEngineMySQL, `"5.7.37", "5.7.38", "5.7.39"`, `"8.0.34"`), + Config: testAccEngineVersionDataSourceConfig_preferredVersionsPreferredUpgrades(tfrds.InstanceEngineMySQL, `"8.0.32", "8.0.33", "8.0.34"`, `"8.0.37"`), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, names.AttrVersion, "5.7.39"), + resource.TestCheckResourceAttr(dataSourceName, names.AttrVersion, "8.0.34"), ), }, { @@ -389,14 +389,14 @@ func TestAccRDSEngineVersionDataSource_hasMinorMajor(t *testing.T) { } func testAccEngineVersionPreCheck(ctx context.Context, t *testing.T) { - conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) input := &rds.DescribeDBEngineVersionsInput{ Engine: aws.String(tfrds.InstanceEngineMySQL), DefaultOnly: aws.Bool(true), } - _, err := conn.DescribeDBEngineVersionsWithContext(ctx, input) + _, err := conn.DescribeDBEngineVersions(ctx, input) if acctest.PreCheckSkipError(err) { t.Skipf("skipping acceptance testing: %s", err) diff --git a/internal/service/rds/exports_test.go b/internal/service/rds/exports_test.go index be465b9cb7e1..f85e8ab39594 100644 --- a/internal/service/rds/exports_test.go +++ b/internal/service/rds/exports_test.go @@ -15,12 +15,14 @@ var ( ResourceEventSubscription = resourceEventSubscription ResourceInstanceAutomatedBackupsReplication = resourceInstanceAutomatedBackupsReplication ResourceInstanceRoleAssociation = resourceInstanceRoleAssociation + ResourceIntegration = newIntegrationResource ResourceOptionGroup = resourceOptionGroup ResourceParameterGroup = resourceParameterGroup ResourceProxy = resourceProxy ResourceProxyDefaultTargetGroup = resourceProxyDefaultTargetGroup ResourceProxyEndpoint = resourceProxyEndpoint ResourceProxyTarget = resourceProxyTarget + ResourceReservedInstance = resourceReservedInstance ResourceSnapshot = resourceSnapshot ResourceSnapshotCopy = resourceSnapshotCopy ResourceSubnetGroup = resourceSubnetGroup @@ -42,7 +44,9 @@ var ( FindDefaultCertificate = findDefaultCertificate FindDefaultDBProxyTargetGroupByDBProxyName = findDefaultDBProxyTargetGroupByDBProxyName FindEventSubscriptionByID = findEventSubscriptionByID + FindIntegrationByARN = findIntegrationByARN FindOptionGroupByName = findOptionGroupByName + FindReservedDBInstanceByID = findReservedDBInstanceByID ListTags = listTags NewBlueGreenOrchestrator = newBlueGreenOrchestrator ParameterGroupModifyChunk = parameterGroupModifyChunk diff --git a/internal/service/rds/instances_data_source.go b/internal/service/rds/instances_data_source.go index 8df76e763393..05e1059b3516 100644 --- a/internal/service/rds/instances_data_source.go +++ b/internal/service/rds/instances_data_source.go @@ -12,7 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" @@ -47,7 +48,7 @@ func dataSourceInstancesRead(ctx context.Context, d *schema.ResourceData, meta i input := &rds.DescribeDBInstancesInput{} if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).RDSFilters() + input.Filters = namevaluesfiltersv1.New(v.(*schema.Set)).RDSFilters() } filter := tfslices.PredicateTrue[*rds.DBInstance]() diff --git a/internal/service/rds/integration.go b/internal/service/rds/integration.go new file mode 100644 index 000000000000..11d794634af3 --- /dev/null +++ b/internal/service/rds/integration.go @@ -0,0 +1,374 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package rds + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" + awstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "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" + 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" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_rds_integration", name="Integration") +// @Tags(identifierAttribute="arn") +// @Testing(tagsTest=false) +func newIntegrationResource(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &integrationResource{} + + r.SetDefaultCreateTimeout(60 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +const ( + integrationStatusActive = "active" + integrationStatusCreating = "creating" + integrationStatusDeleting = "deleting" + integrationStatusFailed = "failed" + integrationStatusModifying = "modifying" + integrationStatusNeedsAttention = "needs_attention" + integrationStatusSyncing = "syncing" +) + +type integrationResource struct { + framework.ResourceWithConfigure + framework.WithNoOpUpdate[integrationResourceModel] + framework.WithImportByID + framework.WithTimeouts +} + +func (*integrationResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_rds_integration" +} + +func (r *integrationResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "additional_encryption_context": schema.MapAttribute{ + CustomType: fwtypes.MapOfStringType, + ElementType: types.StringType, + Optional: true, + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.RequiresReplace(), + }, + }, + names.AttrARN: framework.ARNAttributeComputedOnly(), + names.AttrID: framework.IDAttribute(), + "integration_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + names.AttrKMSKeyID: schema.StringAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, + }, + "source_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + names.AttrTargetARN: schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + Blocks: map[string]schema.Block{ + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Delete: true, + }), + }, + } +} + +func (r *integrationResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data integrationResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().RDSClient(ctx) + + name := data.IntegrationName.ValueString() + input := &rds.CreateIntegrationInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) + if response.Diagnostics.HasError() { + return + } + + // Additional fields. + input.Tags = getTagsInV2(ctx) + + output, err := conn.CreateIntegration(ctx, input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("creating RDS Integration (%s)", name), err.Error()) + + return + } + + // Set values for unknowns. + data.IntegrationARN = fwflex.StringToFramework(ctx, output.IntegrationArn) + data.setID() + + integration, err := waitIntegrationCreated(ctx, conn, data.ID.ValueString(), r.CreateTimeout(ctx, data.Timeouts)) + + if err != nil { + response.State.SetAttribute(ctx, path.Root(names.AttrID), data.ID) // Set 'id' so as to taint the resource. + response.Diagnostics.AddError(fmt.Sprintf("waiting for RDS Integration (%s) create", data.ID.ValueString()), err.Error()) + + return + } + + // Set values for unknowns. + data.KMSKeyID = fwflex.StringToFramework(ctx, integration.KMSKeyId) + + response.Diagnostics.Append(response.State.Set(ctx, data)...) +} + +func (r *integrationResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data integrationResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + if err := data.InitFromID(); err != nil { + response.Diagnostics.AddError("parsing resource ID", err.Error()) + + return + } + + conn := r.Meta().RDSClient(ctx) + + output, err := findIntegrationByARN(ctx, conn, data.ID.ValueString()) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading RDS Integration (%s)", data.ID.ValueString()), err.Error()) + + return + } + + prevAdditionalEncryptionContext := data.AdditionalEncryptionContext + + // Set attributes for import. + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if response.Diagnostics.HasError() { + return + } + + // Null vs. empty map handling. + if prevAdditionalEncryptionContext.IsNull() && !data.AdditionalEncryptionContext.IsNull() && len(data.AdditionalEncryptionContext.Elements()) == 0 { + data.AdditionalEncryptionContext = prevAdditionalEncryptionContext + } + + setTagsOutV2(ctx, output.Tags) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *integrationResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data integrationResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().RDSClient(ctx) + + _, err := conn.DeleteIntegration(ctx, &rds.DeleteIntegrationInput{ + IntegrationIdentifier: aws.String(data.ID.ValueString()), + }) + + if errs.IsA[*awstypes.IntegrationNotFoundFault](err) { + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting RDS Integration (%s)", data.ID.ValueString()), err.Error()) + + return + } + + if _, err := waitIntegrationDeleted(ctx, conn, data.ID.ValueString(), r.DeleteTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for RDS Integration (%s) delete", data.ID.ValueString()), err.Error()) + + return + } +} + +func (r *integrationResource) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +func findIntegrationByARN(ctx context.Context, conn *rds.Client, arn string) (*awstypes.Integration, error) { + input := &rds.DescribeIntegrationsInput{ + IntegrationIdentifier: aws.String(arn), + } + + return findIntegration(ctx, conn, input, tfslices.PredicateTrue[*awstypes.Integration]()) +} + +func findIntegration(ctx context.Context, conn *rds.Client, input *rds.DescribeIntegrationsInput, filter tfslices.Predicate[*awstypes.Integration]) (*awstypes.Integration, error) { + output, err := findIntegrations(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findIntegrations(ctx context.Context, conn *rds.Client, input *rds.DescribeIntegrationsInput, filter tfslices.Predicate[*awstypes.Integration]) ([]awstypes.Integration, error) { + var output []awstypes.Integration + + pages := rds.NewDescribeIntegrationsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*awstypes.IntegrationNotFoundFault](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + for _, v := range page.Integrations { + if filter(&v) { + output = append(output, v) + } + } + } + + return output, nil +} + +func statusIntegration(ctx context.Context, conn *rds.Client, arn string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findIntegrationByARN(ctx, conn, arn) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.Status), nil + } +} + +func waitIntegrationCreated(ctx context.Context, conn *rds.Client, arn string, timeout time.Duration) (*awstypes.Integration, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{integrationStatusCreating, integrationStatusModifying}, + Target: []string{integrationStatusActive}, + Refresh: statusIntegration(ctx, conn, arn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.Integration); ok { + tfresource.SetLastError(err, errors.Join(tfslices.ApplyToAll(output.Errors, integrationError)...)) + + return output, err + } + + return nil, err +} + +func waitIntegrationDeleted(ctx context.Context, conn *rds.Client, arn string, timeout time.Duration) (*awstypes.Integration, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{integrationStatusDeleting, integrationStatusActive}, + Target: []string{}, + Refresh: statusIntegration(ctx, conn, arn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.Integration); ok { + tfresource.SetLastError(err, errors.Join(tfslices.ApplyToAll(output.Errors, integrationError)...)) + + return output, err + } + + return nil, err +} + +func integrationError(v awstypes.IntegrationError) error { + return fmt.Errorf("%s: %s", aws.ToString(v.ErrorCode), aws.ToString(v.ErrorMessage)) +} + +type integrationResourceModel struct { + AdditionalEncryptionContext fwtypes.MapValueOf[types.String] `tfsdk:"additional_encryption_context"` + ID types.String `tfsdk:"id"` + IntegrationARN types.String `tfsdk:"arn"` + IntegrationName types.String `tfsdk:"integration_name"` + KMSKeyID types.String `tfsdk:"kms_key_id"` + SourceARN fwtypes.ARN `tfsdk:"source_arn"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` + TargetARN fwtypes.ARN `tfsdk:"target_arn"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +func (model *integrationResourceModel) InitFromID() error { + model.IntegrationARN = model.ID + + return nil +} + +func (model *integrationResourceModel) setID() { + model.ID = model.IntegrationARN +} diff --git a/internal/service/rds/integration_test.go b/internal/service/rds/integration_test.go new file mode 100644 index 000000000000..1802fe992293 --- /dev/null +++ b/internal/service/rds/integration_test.go @@ -0,0 +1,468 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package rds_test + +import ( + "context" + "fmt" + "testing" + "time" + + awstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfrds "github.com/hashicorp/terraform-provider-aws/internal/service/rds" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccRDSIntegration_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := acctest.Context(t) + var integration awstypes.Integration + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_integration.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIntegrationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIntegrationConfig_baseClusterWithInstance(rName), + Check: resource.ComposeTestCheckFunc( + waitUntilDBInstanceRebooted(ctx, rName), + ), + }, + { + Config: testAccIntegrationConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIntegrationExists(ctx, resourceName, &integration), + resource.TestCheckResourceAttr(resourceName, "integration_name", rName), + resource.TestCheckResourceAttrPair(resourceName, "source_arn", "aws_rds_cluster.test", names.AttrARN), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttrPair(resourceName, names.AttrTargetARN, "aws_redshiftserverless_namespace.test", names.AttrARN), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccRDSIntegration_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + ctx := acctest.Context(t) + var integration awstypes.Integration + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_integration.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIntegrationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIntegrationConfig_baseClusterWithInstance(rName), + Check: resource.ComposeTestCheckFunc( + waitUntilDBInstanceRebooted(ctx, rName), + ), + }, + { + Config: testAccIntegrationConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckIntegrationExists(ctx, resourceName, &integration), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfrds.ResourceIntegration, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccRDSIntegration_optional(t *testing.T) { + ctx := acctest.Context(t) + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var integration awstypes.Integration + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_integration.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIntegrationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIntegrationConfig_baseClusterWithInstance(rName), + Check: resource.ComposeTestCheckFunc( + waitUntilDBInstanceRebooted(ctx, rName), + ), + }, + { + Config: testAccIntegrationConfig_optional(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIntegrationExists(ctx, resourceName, &integration), + resource.TestCheckResourceAttr(resourceName, "integration_name", rName), + resource.TestCheckResourceAttrPair(resourceName, names.AttrKMSKeyID, "aws_kms_key.test", names.AttrARN), + resource.TestCheckResourceAttrPair(resourceName, "source_arn", "aws_rds_cluster.test", names.AttrARN), + resource.TestCheckResourceAttrPair(resourceName, names.AttrTargetARN, "aws_redshiftserverless_namespace.test", names.AttrARN), + resource.TestCheckResourceAttr(resourceName, "additional_encryption_context.department", "test"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckIntegrationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_rds_integration" { + continue + } + + _, err := tfrds.FindIntegrationByARN(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("RDS Integration %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckIntegrationExists(ctx context.Context, n string, v *awstypes.Integration) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) + + output, err := tfrds.FindIntegrationByARN(ctx, conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func waitUntilDBInstanceRebooted(ctx context.Context, instanceIdentifier string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Wait for rebooting. + time.Sleep(60 * time.Second) + + _, err := tfrds.WaitDBInstanceAvailable(ctx, acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx), instanceIdentifier, 30*time.Minute) + + return err + } +} + +func testAccIntegrationConfig_baseClusterWithInstance(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 3), fmt.Sprintf(` +locals { + cluster_parameters = { + "binlog_replication_globaldb" = { + value = "0" + apply_method = "pending-reboot" + }, + "binlog_format" = { + value = "ROW" + apply_method = "pending-reboot" + }, + "binlog_row_metadata" = { + value = "full" + apply_method = "immediate" + }, + "binlog_row_image" = { + value = "full" + apply_method = "immediate" + }, + "aurora_enhanced_binlog" = { + value = "1" + apply_method = "pending-reboot" + }, + "binlog_backup" = { + value = "0" + apply_method = "pending-reboot" + }, + } +} + +data "aws_caller_identity" "current" {} +data "aws_partition" "current" {} + +resource "aws_security_group" "test" { + name = %[1]q + vpc_id = aws_vpc.test.id + + ingress { + protocol = -1 + self = true + from_port = 0 + to_port = 0 + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = %[1]q + } +} + +resource "aws_db_subnet_group" "test" { + name = %[1]q + subnet_ids = aws_subnet.test[*].id + + tags = { + Name = %[1]q + } +} + +resource "aws_rds_cluster_parameter_group" "test" { + name = %[1]q + family = "aurora-mysql8.0" + + dynamic "parameter" { + for_each = local.cluster_parameters + content { + name = parameter.key + value = parameter.value["value"] + apply_method = parameter.value["apply_method"] + } + } +} + +resource "aws_rds_cluster" "test" { + cluster_identifier = %[1]q + engine = "aurora-mysql" + engine_version = "8.0.mysql_aurora.3.05.2" + database_name = "test" + master_username = "tfacctest" + master_password = "avoid-plaintext-passwords" + skip_final_snapshot = true + + vpc_security_group_ids = [aws_security_group.test.id] + db_subnet_group_name = aws_db_subnet_group.test.name + db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.test.name + + apply_immediately = true +} + +resource "aws_rds_cluster_instance" "test" { + identifier = %[1]q + cluster_identifier = aws_rds_cluster.test.id + instance_class = "db.r6g.large" + engine = aws_rds_cluster.test.engine + engine_version = aws_rds_cluster.test.engine_version +} + +resource "aws_redshift_cluster" "test" { + cluster_identifier = %[1]q + availability_zone = data.aws_availability_zones.available.names[0] + database_name = "mydb" + master_username = "foo" + master_password = "Mustbe8characters" + node_type = "dc2.large" + cluster_type = "single-node" + skip_final_snapshot = true +} +`, rName)) +} + +func testAccIntegrationConfig_base(rName string) string { + return acctest.ConfigCompose(testAccIntegrationConfig_baseClusterWithInstance(rName), fmt.Sprintf(` +resource "aws_redshiftserverless_namespace" "test" { + namespace_name = %[1]q +} + +resource "aws_redshiftserverless_workgroup" "test" { + namespace_name = aws_redshiftserverless_namespace.test.namespace_name + workgroup_name = %[1]q + base_capacity = 8 + + publicly_accessible = false + subnet_ids = aws_subnet.test[*].id + + config_parameter { + parameter_key = "enable_case_sensitive_identifier" + parameter_value = "true" + } + config_parameter { + parameter_key = "auto_mv" + parameter_value = "true" + } + config_parameter { + parameter_key = "datestyle" + parameter_value = "ISO, MDY" + } + config_parameter { + parameter_key = "enable_user_activity_logging" + parameter_value = "true" + } + config_parameter { + parameter_key = "max_query_execution_time" + parameter_value = "14400" + } + config_parameter { + parameter_key = "query_group" + parameter_value = "default" + } + config_parameter { + parameter_key = "require_ssl" + parameter_value = "false" + } + config_parameter { + parameter_key = "search_path" + parameter_value = "$user, public" + } + config_parameter { + parameter_key = "use_fips_ssl" + parameter_value = "false" + } +} + +# The "aws_redshiftserverless_resource_policy" resource doesn't support the following action types. +# Therefore we need to use the "aws_redshift_resource_policy" resource for RedShift-serverless instead. +resource "aws_redshift_resource_policy" "test" { + resource_arn = aws_redshiftserverless_namespace.test.arn + policy = jsonencode({ + Version = "2008-10-17" + Statement = [{ + Effect = "Allow" + Principal = { + AWS = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root" + } + Action = "redshift:CreateInboundIntegration" + Resource = aws_redshiftserverless_namespace.test.arn + }, { + Effect = "Allow" + Principal = { + Service = "redshift.amazonaws.com" + } + Action = "redshift:AuthorizeInboundIntegration" + Resource = aws_redshiftserverless_namespace.test.arn + Condition = { + StringEquals = { + "aws:SourceArn" = aws_rds_cluster.test.arn + } + } + }] + }) +} +`, rName)) +} + +func testAccIntegrationConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccIntegrationConfig_base(rName), fmt.Sprintf(` +resource "aws_rds_integration" "test" { + integration_name = %[1]q + source_arn = aws_rds_cluster.test.arn + target_arn = aws_redshiftserverless_namespace.test.arn + + depends_on = [ + aws_rds_cluster.test, + aws_redshiftserverless_namespace.test, + aws_redshiftserverless_workgroup.test, + aws_redshift_resource_policy.test, + ] +} +`, rName)) +} + +func testAccIntegrationConfig_optional(rName string) string { + return acctest.ConfigCompose(testAccIntegrationConfig_base(rName), fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 10 + policy = data.aws_iam_policy_document.key_policy.json +} + +data "aws_iam_policy_document" "key_policy" { + statement { + actions = ["kms:*"] + resources = ["*"] + principals { + type = "AWS" + identifiers = ["arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"] + } + } + + statement { + actions = ["kms:CreateGrant"] + resources = ["*"] + principals { + type = "Service" + identifiers = ["redshift.amazonaws.com"] + } + } +} + +resource "aws_rds_integration" "test" { + integration_name = %[1]q + source_arn = aws_rds_cluster.test.arn + target_arn = aws_redshiftserverless_namespace.test.arn + kms_key_id = aws_kms_key.test.arn + + additional_encryption_context = { + "department" : "test", + } + + tags = { + Name = %[1]q + } + + depends_on = [ + aws_rds_cluster.test, + aws_redshiftserverless_namespace.test, + aws_redshiftserverless_workgroup.test, + aws_redshift_resource_policy.test, + ] +} +`, rName)) +} diff --git a/internal/service/rds/orderable_instance_data_source.go b/internal/service/rds/orderable_instance_data_source.go index 3f9a707632c7..6ce97d9f3e54 100644 --- a/internal/service/rds/orderable_instance_data_source.go +++ b/internal/service/rds/orderable_instance_data_source.go @@ -8,199 +8,171 @@ import ( "sort" "github.com/YakDriver/go-version" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" + awstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_rds_orderable_db_instance") -func DataSourceOrderableInstance() *schema.Resource { +// @SDKDataSource("aws_rds_orderable_db_instance", name="Orderable DB Instance") +func dataSourceOrderableInstance() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceOrderableInstanceRead, + Schema: map[string]*schema.Schema{ "availability_zone_group": { Type: schema.TypeString, Optional: true, Computed: true, }, - names.AttrAvailabilityZones: { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - names.AttrEngine: { Type: schema.TypeString, Required: true, }, - + "engine_latest_version": { + Type: schema.TypeBool, + Optional: true, + }, names.AttrEngineVersion: { Type: schema.TypeString, Optional: true, Computed: true, }, - "instance_class": { Type: schema.TypeString, Optional: true, Computed: true, }, - - "engine_latest_version": { - Type: schema.TypeBool, - Optional: true, - }, - "license_model": { Type: schema.TypeString, Optional: true, Computed: true, }, - "max_iops_per_db_instance": { Type: schema.TypeInt, Computed: true, }, - "max_iops_per_gib": { Type: schema.TypeFloat, Computed: true, }, - "max_storage_size": { Type: schema.TypeInt, Computed: true, }, - "min_iops_per_db_instance": { Type: schema.TypeInt, Computed: true, }, - "min_iops_per_gib": { Type: schema.TypeFloat, Computed: true, }, - "min_storage_size": { Type: schema.TypeInt, Computed: true, }, - "multi_az_capable": { Type: schema.TypeBool, Computed: true, }, - "outpost_capable": { Type: schema.TypeBool, Computed: true, }, - - "preferred_instance_classes": { + "preferred_engine_versions": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - - "preferred_engine_versions": { + "preferred_instance_classes": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "read_replica_capable": { Type: schema.TypeBool, Optional: true, Computed: true, }, - names.AttrStorageType: { Type: schema.TypeString, Optional: true, Computed: true, }, - "supported_engine_modes": { Type: schema.TypeList, Computed: true, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "supported_network_types": { Type: schema.TypeList, Computed: true, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "supports_clusters": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "supports_enhanced_monitoring": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "supports_global_databases": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "supports_iam_database_authentication": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "supports_iops": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "supports_kerberos_authentication": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "supports_multi_az": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "supports_performance_insights": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "supports_storage_autoscaling": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "supports_storage_encryption": { Type: schema.TypeBool, Optional: true, Computed: true, }, - "vpc": { Type: schema.TypeBool, Optional: true, @@ -212,10 +184,10 @@ func DataSourceOrderableInstance() *schema.Resource { func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSConn(ctx) + conn := meta.(*conns.AWSClient).RDSClient(ctx) input := &rds.DescribeOrderableDBInstanceOptionsInput{ - MaxRecords: aws.Int64(1000), + MaxRecords: aws.Int32(1000), } if v, ok := d.GetOk("availability_zone_group"); ok { @@ -242,93 +214,91 @@ func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData input.Vpc = aws.Bool(v.(bool)) } - var instanceClassResults []*rds.OrderableDBInstanceOption + var instanceClassResults []awstypes.OrderableDBInstanceOption - err := conn.DescribeOrderableDBInstanceOptionsPagesWithContext(ctx, input, func(resp *rds.DescribeOrderableDBInstanceOptionsOutput, lastPage bool) bool { - for _, instanceOption := range resp.OrderableDBInstanceOptions { - if instanceOption == nil { - continue - } + pages := rds.NewDescribeOrderableDBInstanceOptionsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading RDS Orderable DB Instance Options: %s", err) + } + for _, instanceOption := range page.OrderableDBInstanceOptions { if v, ok := d.GetOk("read_replica_capable"); ok { - if aws.BoolValue(instanceOption.ReadReplicaCapable) != v.(bool) { + if aws.ToBool(instanceOption.ReadReplicaCapable) != v.(bool) { continue } } if v, ok := d.GetOk(names.AttrStorageType); ok { - if aws.StringValue(instanceOption.StorageType) != v.(string) { + if aws.ToString(instanceOption.StorageType) != v.(string) { continue } } if v, ok := d.GetOk("supports_clusters"); ok { - if aws.BoolValue(instanceOption.SupportsClusters) != v.(bool) { + if aws.ToBool(instanceOption.SupportsClusters) != v.(bool) { continue } } if v, ok := d.GetOk("supports_enhanced_monitoring"); ok { - if aws.BoolValue(instanceOption.SupportsEnhancedMonitoring) != v.(bool) { + if aws.ToBool(instanceOption.SupportsEnhancedMonitoring) != v.(bool) { continue } } if v, ok := d.GetOk("supports_global_databases"); ok { - if aws.BoolValue(instanceOption.SupportsGlobalDatabases) != v.(bool) { + if aws.ToBool(instanceOption.SupportsGlobalDatabases) != v.(bool) { continue } } if v, ok := d.GetOk("supports_iam_database_authentication"); ok { - if aws.BoolValue(instanceOption.SupportsIAMDatabaseAuthentication) != v.(bool) { + if aws.ToBool(instanceOption.SupportsIAMDatabaseAuthentication) != v.(bool) { continue } } if v, ok := d.GetOk("supports_iops"); ok { - if aws.BoolValue(instanceOption.SupportsIops) != v.(bool) { + if aws.ToBool(instanceOption.SupportsIops) != v.(bool) { continue } } if v, ok := d.GetOk("supports_kerberos_authentication"); ok { - if aws.BoolValue(instanceOption.SupportsKerberosAuthentication) != v.(bool) { + if aws.ToBool(instanceOption.SupportsKerberosAuthentication) != v.(bool) { continue } } if v, ok := d.GetOk("supports_multi_az"); ok { - if aws.BoolValue(instanceOption.MultiAZCapable) != v.(bool) { + if aws.ToBool(instanceOption.MultiAZCapable) != v.(bool) { continue } } if v, ok := d.GetOk("supports_performance_insights"); ok { - if aws.BoolValue(instanceOption.SupportsPerformanceInsights) != v.(bool) { + if aws.ToBool(instanceOption.SupportsPerformanceInsights) != v.(bool) { continue } } if v, ok := d.GetOk("supports_storage_autoscaling"); ok { - if aws.BoolValue(instanceOption.SupportsStorageAutoscaling) != v.(bool) { + if aws.ToBool(instanceOption.SupportsStorageAutoscaling) != v.(bool) { continue } } if v, ok := d.GetOk("supports_storage_encryption"); ok { - if aws.BoolValue(instanceOption.SupportsStorageEncryption) != v.(bool) { + if aws.ToBool(instanceOption.SupportsStorageEncryption) != v.(bool) { continue } } instanceClassResults = append(instanceClassResults, instanceOption) } - return !lastPage - }) - - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading RDS Orderable DB Instance Options: %s", err) } if len(instanceClassResults) == 0 { @@ -336,14 +306,14 @@ func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData } if v, ok := d.GetOk("supported_engine_modes"); ok && len(v.([]interface{})) > 0 { - var matches []*rds.OrderableDBInstanceOption + var matches []awstypes.OrderableDBInstanceOption search := flex.ExpandStringValueList(v.([]interface{})) for _, ic := range instanceClassResults { searchedModes: for _, s := range search { for _, mode := range ic.SupportedEngineModes { - if aws.StringValue(mode) == s { + if mode == s { matches = append(matches, ic) break searchedModes } @@ -359,14 +329,14 @@ func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData } if v, ok := d.GetOk("supported_network_types"); ok && len(v.([]interface{})) > 0 { - var matches []*rds.OrderableDBInstanceOption + var matches []awstypes.OrderableDBInstanceOption search := flex.ExpandStringValueList(v.([]interface{})) for _, ic := range instanceClassResults { searchedNetworks: for _, s := range search { for _, netType := range ic.SupportedNetworkTypes { - if aws.StringValue(netType) == s { + if netType == s { matches = append(matches, ic) break searchedNetworks } @@ -384,12 +354,12 @@ func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData prefSearch := false if v, ok := d.GetOk("preferred_engine_versions"); ok && len(v.([]interface{})) > 0 { - var matches []*rds.OrderableDBInstanceOption + var matches []awstypes.OrderableDBInstanceOption search := flex.ExpandStringValueList(v.([]interface{})) for _, s := range search { for _, ic := range instanceClassResults { - if aws.StringValue(ic.EngineVersion) == s { + if aws.ToString(ic.EngineVersion) == s { matches = append(matches, ic) } // keeping all the instance classes to ensure we can match any preferred instance classes @@ -407,12 +377,12 @@ func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData latestVersion := d.Get("engine_latest_version").(bool) if v, ok := d.GetOk("preferred_instance_classes"); ok && len(v.([]interface{})) > 0 { - var matches []*rds.OrderableDBInstanceOption + var matches []awstypes.OrderableDBInstanceOption search := flex.ExpandStringValueList(v.([]interface{})) for _, s := range search { for _, ic := range instanceClassResults { - if aws.StringValue(ic.DBInstanceClass) == s { + if aws.ToString(ic.DBInstanceClass) == s { matches = append(matches, ic) } @@ -437,19 +407,19 @@ func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData instanceClassResults = matches } - var found *rds.OrderableDBInstanceOption + var found *awstypes.OrderableDBInstanceOption if latestVersion && prefSearch { sortInstanceClassesByVersion(instanceClassResults) - found = instanceClassResults[len(instanceClassResults)-1] + found = &instanceClassResults[len(instanceClassResults)-1] } if found == nil && len(instanceClassResults) > 0 && prefSearch { - found = instanceClassResults[0] + found = &instanceClassResults[0] } if found == nil && len(instanceClassResults) == 1 { - found = instanceClassResults[0] + found = &instanceClassResults[0] } if found == nil && len(instanceClassResults) > 4 { @@ -466,13 +436,11 @@ func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData return sdkdiag.AppendErrorf(diags, "no RDS DB Instance Classes match the criteria; try a different search") } - d.SetId(aws.StringValue(found.DBInstanceClass)) + d.SetId(aws.ToString(found.DBInstanceClass)) d.Set("availability_zone_group", found.AvailabilityZoneGroup) - var availabilityZones []string - for _, v := range found.AvailabilityZones { - availabilityZones = append(availabilityZones, aws.StringValue(v.Name)) - } - d.Set(names.AttrAvailabilityZones, availabilityZones) + d.Set(names.AttrAvailabilityZones, tfslices.ApplyToAll(found.AvailabilityZones, func(v awstypes.AvailabilityZone) string { + return aws.ToString(v.Name) + })) d.Set(names.AttrEngine, found.Engine) d.Set(names.AttrEngineVersion, found.EngineVersion) d.Set("instance_class", found.DBInstanceClass) @@ -487,8 +455,8 @@ func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData d.Set("outpost_capable", found.OutpostCapable) d.Set("read_replica_capable", found.ReadReplicaCapable) d.Set(names.AttrStorageType, found.StorageType) - d.Set("supported_engine_modes", aws.StringValueSlice(found.SupportedEngineModes)) - d.Set("supported_network_types", aws.StringValueSlice(found.SupportedNetworkTypes)) + d.Set("supported_engine_modes", found.SupportedEngineModes) + d.Set("supported_network_types", found.SupportedNetworkTypes) d.Set("supports_clusters", found.SupportsClusters) d.Set("supports_enhanced_monitoring", found.SupportsEnhancedMonitoring) d.Set("supports_global_databases", found.SupportsGlobalDatabases) @@ -504,12 +472,12 @@ func dataSourceOrderableInstanceRead(ctx context.Context, d *schema.ResourceData return diags } -func sortInstanceClassesByVersion(ic []*rds.OrderableDBInstanceOption) { +func sortInstanceClassesByVersion(ic []awstypes.OrderableDBInstanceOption) { if len(ic) < 2 { return } sort.Slice(ic, func(i, j int) bool { - return version.LessThan(aws.StringValue(ic[i].EngineVersion), aws.StringValue(ic[j].EngineVersion)) + return version.LessThan(aws.ToString(ic[i].EngineVersion), aws.ToString(ic[j].EngineVersion)) }) } diff --git a/internal/service/rds/orderable_instance_data_source_test.go b/internal/service/rds/orderable_instance_data_source_test.go index 0dc97dad7bdb..3a3710f90475 100644 --- a/internal/service/rds/orderable_instance_data_source_test.go +++ b/internal/service/rds/orderable_instance_data_source_test.go @@ -10,8 +10,8 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -465,14 +465,14 @@ func TestAccRDSOrderableInstanceDataSource_supportsStorageEncryption(t *testing. } func testAccOrderableInstancePreCheck(ctx context.Context, t *testing.T) { - conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) input := &rds.DescribeOrderableDBInstanceOptionsInput{ Engine: aws.String("mysql"), DBInstanceClass: aws.String("db.m5.xlarge"), } - _, err := conn.DescribeOrderableDBInstanceOptionsWithContext(ctx, input) + _, err := conn.DescribeOrderableDBInstanceOptions(ctx, input) if acctest.PreCheckSkipError(err) { t.Skipf("skipping acceptance testing: %s", err) diff --git a/internal/service/rds/parameter_group_data_source.go b/internal/service/rds/parameter_group_data_source.go index 5fecde68904b..44f1574f088b 100644 --- a/internal/service/rds/parameter_group_data_source.go +++ b/internal/service/rds/parameter_group_data_source.go @@ -6,35 +6,33 @@ package rds import ( "context" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_db_parameter_group") -func DataSourceParameterGroup() *schema.Resource { +// @SDKDataSource("aws_db_parameter_group", name="DB Parameter Group") +func dataSourceParameterGroup() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceParameterGroupRead, + Schema: map[string]*schema.Schema{ names.AttrARN: { Type: schema.TypeString, Computed: true, }, - names.AttrDescription: { Type: schema.TypeString, Computed: true, }, - names.AttrFamily: { Type: schema.TypeString, Computed: true, }, - names.AttrName: { Type: schema.TypeString, Required: true, @@ -45,28 +43,19 @@ func DataSourceParameterGroup() *schema.Resource { func dataSourceParameterGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSConn(ctx) + conn := meta.(*conns.AWSClient).RDSClient(ctx) - groupName := d.Get(names.AttrName).(string) + output, err := findDBParameterGroupByName(ctx, conn, d.Get(names.AttrName).(string)) - input := rds.DescribeDBParameterGroupsInput{ - DBParameterGroupName: aws.String(groupName), - } - - output, err := conn.DescribeDBParameterGroupsWithContext(ctx, &input) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading RDS DB Parameter Groups (%s): %s", d.Get(names.AttrName).(string), err) - } - - if len(output.DBParameterGroups) != 1 || aws.StringValue(output.DBParameterGroups[0].DBParameterGroupName) != groupName { - return sdkdiag.AppendErrorf(diags, "RDS DB Parameter Group not found (%#v): %s", output, err) + return sdkdiag.AppendFromErr(diags, tfresource.SingularDataSourceFindError("RDS DB Parameter Group", err)) } - d.SetId(aws.StringValue(output.DBParameterGroups[0].DBParameterGroupName)) - d.Set(names.AttrName, output.DBParameterGroups[0].DBParameterGroupName) - d.Set(names.AttrARN, output.DBParameterGroups[0].DBParameterGroupArn) - d.Set(names.AttrFamily, output.DBParameterGroups[0].DBParameterGroupFamily) - d.Set(names.AttrDescription, output.DBParameterGroups[0].Description) + d.SetId(aws.ToString(output.DBParameterGroupName)) + d.Set(names.AttrARN, output.DBParameterGroupArn) + d.Set(names.AttrDescription, output.Description) + d.Set(names.AttrFamily, output.DBParameterGroupFamily) + d.Set(names.AttrName, output.DBParameterGroupName) return diags } diff --git a/internal/service/rds/parameter_group_data_source_test.go b/internal/service/rds/parameter_group_data_source_test.go index cf8e3016009d..dd8e951cb877 100644 --- a/internal/service/rds/parameter_group_data_source_test.go +++ b/internal/service/rds/parameter_group_data_source_test.go @@ -7,7 +7,6 @@ import ( "fmt" "testing" - "github.com/YakDriver/regexache" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -25,10 +24,6 @@ func TestAccRDSParameterGroupDataSource_basic(t *testing.T) { ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ - { - Config: testAccParameterGroupDataSourceConfig_nonExistent, - ExpectError: regexache.MustCompile(`not found`), - }, { Config: testAccParameterGroupDataSourceConfig_basic(rName), Check: resource.ComposeTestCheckFunc( @@ -42,12 +37,6 @@ func TestAccRDSParameterGroupDataSource_basic(t *testing.T) { }) } -const testAccParameterGroupDataSourceConfig_nonExistent = ` -data "aws_db_parameter_group" "test" { - name = "tf-acc-test-does-not-exist" -} -` - func testAccParameterGroupDataSourceConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_db_parameter_group" "test" { diff --git a/internal/service/rds/reserved_instance.go b/internal/service/rds/reserved_instance.go index f5121a4791af..89014e845ef1 100644 --- a/internal/service/rds/reserved_instance.go +++ b/internal/service/rds/reserved_instance.go @@ -5,31 +5,29 @@ package rds import ( "context" - "fmt" + "log" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/rds" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" + "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + 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" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" ) -const ( - ResNameReservedInstance = "Reserved Instance" -) - // @SDKResource("aws_rds_reserved_instance", name="Reserved Instance") // @Tags(identifierAttribute="arn") // @Testing(tagsTest=false) -func ResourceReservedInstance() *schema.Resource { +func resourceReservedInstance() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceReservedInstanceCreate, ReadWithoutTimeout: resourceReservedInstanceRead, @@ -45,6 +43,7 @@ func ResourceReservedInstance() *schema.Resource { Update: schema.DefaultTimeout(10 * time.Minute), Delete: schema.DefaultTimeout(1 * time.Minute), }, + Schema: map[string]*schema.Schema{ names.AttrARN: { Type: schema.TypeString, @@ -136,30 +135,31 @@ func ResourceReservedInstance() *schema.Resource { func resourceReservedInstanceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSConn(ctx) + conn := meta.(*conns.AWSClient).RDSClient(ctx) input := &rds.PurchaseReservedDBInstancesOfferingInput{ ReservedDBInstancesOfferingId: aws.String(d.Get("offering_id").(string)), - Tags: getTagsIn(ctx), + Tags: getTagsInV2(ctx), } if v, ok := d.Get(names.AttrInstanceCount).(int); ok && v > 0 { - input.DBInstanceCount = aws.Int64(int64(d.Get(names.AttrInstanceCount).(int))) + input.DBInstanceCount = aws.Int32(int32(d.Get(names.AttrInstanceCount).(int))) } if v, ok := d.Get("reservation_id").(string); ok && v != "" { input.ReservedDBInstanceId = aws.String(v) } - resp, err := conn.PurchaseReservedDBInstancesOfferingWithContext(ctx, input) + output, err := conn.PurchaseReservedDBInstancesOffering(ctx, input) + if err != nil { - return create.AppendDiagError(diags, names.RDS, create.ErrActionCreating, ResNameReservedInstance, fmt.Sprintf("offering_id: %s, reservation_id: %s", d.Get("offering_id").(string), d.Get("reservation_id").(string)), err) + return sdkdiag.AppendErrorf(diags, "creating RDS Reserved Instance: %s", err) } - d.SetId(aws.StringValue(resp.ReservedDBInstance.ReservedDBInstanceId)) + d.SetId(aws.ToString(output.ReservedDBInstance.ReservedDBInstanceId)) - if err := waitReservedInstanceCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return create.AppendDiagError(diags, names.RDS, create.ErrActionWaitingForCreation, ResNameReservedInstance, d.Id(), err) + if _, err := waitReservedInstanceCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for RDS Reserved Instance (%s) create: %s", d.Id(), err) } return append(diags, resourceReservedInstanceRead(ctx, d, meta)...) @@ -167,18 +167,18 @@ func resourceReservedInstanceCreate(ctx context.Context, d *schema.ResourceData, func resourceReservedInstanceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).RDSConn(ctx) + conn := meta.(*conns.AWSClient).RDSClient(ctx) - reservation, err := FindReservedDBInstanceByID(ctx, conn, d.Id()) + reservation, err := findReservedDBInstanceByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { - create.LogNotFoundRemoveState(names.RDS, create.ErrActionReading, ResNameReservedInstance, d.Id()) + log.Printf("[WARN] RDS Reserved Instance (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.RDS, create.ErrActionReading, ResNameReservedInstance, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading RDS Reserved Instance (%s): %s", d.Id(), err) } d.Set(names.AttrARN, reservation.ReservedDBInstanceArn) @@ -194,7 +194,7 @@ func resourceReservedInstanceRead(ctx context.Context, d *schema.ResourceData, m d.Set("product_description", reservation.ProductDescription) d.Set("recurring_charges", flattenRecurringCharges(reservation.RecurringCharges)) d.Set("reservation_id", reservation.ReservedDBInstanceId) - d.Set(names.AttrStartTime, (reservation.StartTime).Format(time.RFC3339)) + d.Set(names.AttrStartTime, reservation.StartTime.Format(time.RFC3339)) d.Set(names.AttrState, reservation.State) d.Set("usage_price", reservation.UsagePrice) @@ -206,38 +206,67 @@ func resourceReservedInstanceUpdate(ctx context.Context, d *schema.ResourceData, return resourceReservedInstanceRead(ctx, d, meta) } -func FindReservedDBInstanceByID(ctx context.Context, conn *rds.RDS, id string) (*rds.ReservedDBInstance, error) { +func findReservedDBInstanceByID(ctx context.Context, conn *rds.Client, id string) (*types.ReservedDBInstance, error) { input := &rds.DescribeReservedDBInstancesInput{ ReservedDBInstanceId: aws.String(id), } + output, err := findReservedDBInstance(ctx, conn, input, tfslices.PredicateTrue[*types.ReservedDBInstance]()) - output, err := conn.DescribeReservedDBInstancesWithContext(ctx, input) + if err != nil { + return nil, err + } - if tfawserr.ErrCodeEquals(err, rds.ErrCodeReservedDBInstanceNotFoundFault) { + // Eventual consistency check. + if aws.ToString(output.ReservedDBInstanceId) != id { return nil, &retry.NotFoundError{ - LastError: err, LastRequest: input, } } + return output, nil +} + +func findReservedDBInstance(ctx context.Context, conn *rds.Client, input *rds.DescribeReservedDBInstancesInput, filter tfslices.Predicate[*types.ReservedDBInstance]) (*types.ReservedDBInstance, error) { + output, err := findReservedDBInstances(ctx, conn, input, filter) + if err != nil { return nil, err } - if output == nil || len(output.ReservedDBInstances) == 0 || output.ReservedDBInstances[0] == nil { - return nil, tfresource.NewEmptyResultError(input) - } + return tfresource.AssertSingleValueResult(output) +} - if count := len(output.ReservedDBInstances); count > 1 { - return nil, tfresource.NewTooManyResultsError(count, input) +func findReservedDBInstances(ctx context.Context, conn *rds.Client, input *rds.DescribeReservedDBInstancesInput, filter tfslices.Predicate[*types.ReservedDBInstance]) ([]types.ReservedDBInstance, error) { + var output []types.ReservedDBInstance + + pages := rds.NewDescribeReservedDBInstancesPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*types.ReservedDBInstanceNotFoundFault](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + for _, v := range page.ReservedDBInstances { + if filter(&v) { + output = append(output, v) + } + } } - return output.ReservedDBInstances[0], nil + return output, nil } -func statusReservedInstance(ctx context.Context, conn *rds.RDS, id string) retry.StateRefreshFunc { +func statusReservedInstance(ctx context.Context, conn *rds.Client, id string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := FindReservedDBInstanceByID(ctx, conn, id) + output, err := findReservedDBInstanceByID(ctx, conn, id) if tfresource.NotFound(err) { return nil, "", nil @@ -247,16 +276,14 @@ func statusReservedInstance(ctx context.Context, conn *rds.RDS, id string) retry return nil, "", err } - return output, aws.StringValue(output.State), nil + return output, aws.ToString(output.State), nil } } -func waitReservedInstanceCreated(ctx context.Context, conn *rds.RDS, id string, timeout time.Duration) error { +func waitReservedInstanceCreated(ctx context.Context, conn *rds.Client, id string, timeout time.Duration) (*types.ReservedDBInstance, error) { stateConf := &retry.StateChangeConf{ - Pending: []string{ - ReservedInstanceStatePaymentPending, - }, - Target: []string{ReservedInstanceStateActive}, + Pending: []string{reservedInstanceStatePaymentPending}, + Target: []string{reservedInstanceStateActive}, Refresh: statusReservedInstance(ctx, conn, id), NotFoundChecks: 5, Timeout: timeout, @@ -264,25 +291,29 @@ func waitReservedInstanceCreated(ctx context.Context, conn *rds.RDS, id string, Delay: 30 * time.Second, } - _, err := stateConf.WaitForStateContext(ctx) + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.ReservedDBInstance); ok { + return output, err + } - return err + return nil, err } -func flattenRecurringCharges(recurringCharges []*rds.RecurringCharge) []interface{} { - if len(recurringCharges) == 0 { +func flattenRecurringCharges(apiObjects []types.RecurringCharge) []interface{} { + if len(apiObjects) == 0 { return []interface{}{} } - var rawRecurringCharges []interface{} - for _, recurringCharge := range recurringCharges { - rawRecurringCharge := map[string]interface{}{ - "recurring_charge_amount": recurringCharge.RecurringChargeAmount, - "recurring_charge_frequency": aws.StringValue(recurringCharge.RecurringChargeFrequency), + var tfList []interface{} + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{ + "recurring_charge_amount": aws.ToFloat64(apiObject.RecurringChargeAmount), + "recurring_charge_frequency": aws.ToString(apiObject.RecurringChargeFrequency), } - rawRecurringCharges = append(rawRecurringCharges, rawRecurringCharge) + tfList = append(tfList, tfMap) } - return rawRecurringCharges + return tfList } diff --git a/internal/service/rds/reserved_instance_test.go b/internal/service/rds/reserved_instance_test.go index 43c2c77271e8..9989dcc34ac2 100644 --- a/internal/service/rds/reserved_instance_test.go +++ b/internal/service/rds/reserved_instance_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go-v2/service/rds/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -28,7 +28,7 @@ func TestAccRDSReservedInstance_basic(t *testing.T) { t.Skipf("Environment variable %s is not set to true", key) } - var reservation rds.ReservedDBInstance + var reservation types.ReservedDBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_reserved_instance.test" dataSourceName := "data.aws_rds_reserved_instance_offering.test" @@ -36,7 +36,7 @@ func TestAccRDSReservedInstance_basic(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: nil, + CheckDestroy: acctest.CheckDestroyNoop, ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), Steps: []resource.TestStep{ { @@ -65,29 +65,22 @@ func TestAccRDSReservedInstance_basic(t *testing.T) { }) } -func testAccReservedInstanceExists(ctx context.Context, n string, reservation *rds.ReservedDBInstance) resource.TestCheckFunc { +func testAccReservedInstanceExists(ctx context.Context, n string, v *types.ReservedDBInstance) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn(ctx) - rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No RDS Reserved Instance reservation id is set") - } + conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx) + + output, err := tfrds.FindReservedDBInstanceByID(ctx, conn, rs.Primary.ID) - resp, err := tfrds.FindReservedDBInstanceByID(ctx, conn, rs.Primary.ID) if err != nil { return err } - if resp == nil { - return fmt.Errorf("RDS Reserved Instance %q does not exist", rs.Primary.ID) - } - - *reservation = *resp + *v = *output return nil } diff --git a/internal/service/rds/service_package_gen.go b/internal/service/rds/service_package_gen.go index 286e6581c98d..c3944913c96c 100644 --- a/internal/service/rds/service_package_gen.go +++ b/internal/service/rds/service_package_gen.go @@ -24,6 +24,13 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ + { + Factory: newIntegrationResource, + Name: "Integration", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }, + }, { Factory: newResourceExportTask, }, @@ -54,8 +61,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac TypeName: "aws_db_instances", }, { - Factory: DataSourceParameterGroup, + Factory: dataSourceParameterGroup, TypeName: "aws_db_parameter_group", + Name: "DB Parameter Group", }, { Factory: dataSourceProxy, @@ -74,8 +82,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "DB Subnet Group", }, { - Factory: DataSourceCertificate, + Factory: dataSourceCertificate, TypeName: "aws_rds_certificate", + Name: "Certificate", }, { Factory: DataSourceCluster, @@ -86,12 +95,14 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac TypeName: "aws_rds_clusters", }, { - Factory: DataSourceEngineVersion, + Factory: dataSourceEngineVersion, TypeName: "aws_rds_engine_version", + Name: "Engine Version", }, { - Factory: DataSourceOrderableInstance, + Factory: dataSourceOrderableInstance, TypeName: "aws_rds_orderable_db_instance", + Name: "Orderable DB Instance", }, { Factory: dataSourceReservedOffering, @@ -262,7 +273,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka TypeName: "aws_rds_global_cluster", }, { - Factory: ResourceReservedInstance, + Factory: resourceReservedInstance, TypeName: "aws_rds_reserved_instance", Name: "Reserved Instance", Tags: &types.ServicePackageResourceTags{ diff --git a/internal/service/route53resolver/query_log_config_data_source.go b/internal/service/route53resolver/query_log_config_data_source.go index ac8dbc1eb105..e22f6e1805d7 100644 --- a/internal/service/route53resolver/query_log_config_data_source.go +++ b/internal/service/route53resolver/query_log_config_data_source.go @@ -14,7 +14,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv1 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v1" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -69,7 +70,7 @@ func dataSourceQueryLogConfigRead(ctx context.Context, d *schema.ResourceData, m input := &route53resolver.ListResolverQueryLogConfigsInput{} if v, ok := d.GetOk(names.AttrFilter); ok && v.(*schema.Set).Len() > 0 { - input.Filters = namevaluesfilters.New(v.(*schema.Set)).Route53resolverFilters() + input.Filters = namevaluesfiltersv1.New(v.(*schema.Set)).Route53ResolverFilters() } var configs []*route53resolver.ResolverQueryLogConfig diff --git a/internal/service/secretsmanager/secrets_data_source.go b/internal/service/secretsmanager/secrets_data_source.go index 112d7db328ca..164800db8a78 100644 --- a/internal/service/secretsmanager/secrets_data_source.go +++ b/internal/service/secretsmanager/secrets_data_source.go @@ -13,7 +13,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfiltersv2" + "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters" + namevaluesfiltersv2 "github.com/hashicorp/terraform-provider-aws/internal/namevaluesfilters/v2" tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -28,7 +29,7 @@ func dataSourceSecrets() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - names.AttrFilter: namevaluesfiltersv2.Schema(), + names.AttrFilter: namevaluesfilters.Schema(), names.AttrNames: { Type: schema.TypeSet, Computed: true, @@ -45,7 +46,7 @@ func dataSourceSecretsRead(ctx context.Context, d *schema.ResourceData, meta int input := &secretsmanager.ListSecretsInput{} if v, ok := d.GetOk(names.AttrFilter); ok { - input.Filters = namevaluesfiltersv2.New(v.(*schema.Set)).SecretsmanagerFilters() + input.Filters = namevaluesfiltersv2.New(v.(*schema.Set)).SecretsManagerFilters() } var results []types.SecretListEntry diff --git a/website/docs/r/rds_integration.html.markdown b/website/docs/r/rds_integration.html.markdown new file mode 100644 index 000000000000..8ad0391a8306 --- /dev/null +++ b/website/docs/r/rds_integration.html.markdown @@ -0,0 +1,142 @@ +--- +subcategory: "RDS (Relational Database)" +layout: "aws" +page_title: "AWS: aws_rds_integration" +description: |- + Terraform resource for managing an AWS RDS (Relational Database) Integration. +--- + +# Resource: aws_rds_integration + +Terraform resource for managing an AWS RDS (Relational Database) zero-ETL integration. You can refer to the [User Guide](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/zero-etl.setting-up.html). + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_redshiftserverless_namespace" "example" { + namespace_name = "redshift-example" +} + +resource "aws_redshiftserverless_workgroup" "example" { + namespace_name = aws_redshiftserverless_namespace.example.namespace_name + workgroup_name = "example-workspace" + base_capacity = 8 + publicly_accessible = false + + subnet_ids = [aws_subnet.example1.id, aws_subnet.example2.id, aws_subnet.example3.id] + + config_parameter { + parameter_key = "enable_case_sensitive_identifier" + parameter_value = "true" + } +} + +resource "aws_rds_integration" "example" { + integration_name = "example" + source_arn = aws_rds_cluster.example.arn + target_arn = aws_redshiftserverless_namespace.example.arn + + lifecycle { + ignore_changes = [ + kms_key_id + ] + } +} +``` + +### Use own KMS key + +```terraform +data "aws_caller_identity" "current" {} + +resource "aws_kms_key" "example" { + deletion_window_in_days = 10 + policy = data.aws_iam_policy_document.key_policy.json +} + +data "aws_iam_policy_document" "key_policy" { + statement { + actions = ["kms:*"] + resources = ["*"] + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] + } + } + + statement { + actions = ["kms:CreateGrant"] + resources = ["*"] + principals { + type = "Service" + identifiers = ["redshift.amazonaws.com"] + } + } +} + +resource "aws_rds_integration" "example" { + integration_name = "example" + source_arn = aws_rds_cluster.example.arn + target_arn = aws_redshiftserverless_namespace.example.arn + kms_key_id = aws_kms_key.example.arn + + additional_encryption_context = { + "example" : "test", + } +} +``` + +## Argument Reference + +For more detailed documentation about each argument, refer to the [AWS official documentation](https://docs.aws.amazon.com/cli/latest/reference/rds/create-integration.html). + +The following arguments are required: + +* `integration_name` - (Required, Forces new resources) Name of the integration. + +* `source_arn` - (Required, Forces new resources) ARN of the database to use as the source for replication. + +* `target_arn` - (Required, Forces new resources) ARN of the Redshift data warehouse to use as the target for replication. + +The following arguments are optional: + +* `kms_key_id` - (Optional, Forces new resources) KMS key identifier for the key to use to encrypt the integration. If you don't specify an encryption key, RDS uses a default AWS owned key. If you use the default AWS owned key, you should ignore `kms_key_id` parameter by using [`lifecycle` parameter](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#ignore_changes) to avoid unintended change after the first creation. + +* `additional_encryption_context` - (Optional, Forces new resources) Set of non-secret key–value pairs that contains additional contextual information about the data. For more information, see the [User Guide](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context). You can only include this parameter if you specify the `kms_key_id` parameter. + +* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Integration. +* `id` - ID of the Integration. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `60m`) +* `update` - (Default `10m`) +* `delete` - (Default `30m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import RDS (Relational Database) Integration using the `arn`. For example: + +```terraform +import { + to = aws_rds_integration.example + id = "arn:aws:rds:us-west-2:123456789012:integration:abcdefgh-0000-1111-2222-123456789012" +} +``` + +Using `terraform import`, import RDS (Relational Database) Integration using the `arn`. For example: + +```console +% terraform import aws_rds_integration.example arn:aws:rds:us-west-2:123456789012:integration:abcdefgh-0000-1111-2222-123456789012 +```