Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ephemeral resources: bootstrap provider #39835

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1105238
WIP
johnsonaj Aug 27, 2024
5302505
Merge branch 'main' into f-ephemeral_values_bootstrap
johnsonaj Aug 27, 2024
27f39a2
wrap and register ephemeral resource
johnsonaj Aug 27, 2024
9f471a9
add ephemeral resource to servicepackage generator
johnsonaj Aug 27, 2024
92e5fb9
make gen service packages
johnsonaj Aug 27, 2024
55dcda6
extend ServicePackages interface to make it optional
johnsonaj Aug 27, 2024
e74cfa9
make gen
johnsonaj Aug 27, 2024
bcb0394
Merge branch 'main' into f-ephemeral_values_bootstrap
johnsonaj Aug 27, 2024
1c94392
chore: cleanup
johnsonaj Sep 3, 2024
af46076
resolve conflicts
johnsonaj Sep 6, 2024
aacc5a9
go get -u
johnsonaj Sep 6, 2024
b22d261
wrap remaining ephemeral interfaces
johnsonaj Sep 16, 2024
0d4fab0
resolve conflicts
johnsonaj Sep 16, 2024
6e2ebdb
use replace in go.mod
johnsonaj Sep 16, 2024
20bdb1f
resolve conflicts
johnsonaj Oct 7, 2024
e649dca
go mod tidy
johnsonaj Oct 7, 2024
fca39ff
update deps
johnsonaj Oct 7, 2024
3562adc
update deps
johnsonaj Oct 7, 2024
eb376e7
cleanup
johnsonaj Oct 7, 2024
a711ab9
build tags
johnsonaj Oct 7, 2024
5390ef9
Merge branch 'main' into f-ephemeral_values_bootstrap
johnsonaj Oct 10, 2024
dcc2acf
aws_kms_secrets: add ephemeral resource
johnsonaj Oct 11, 2024
8ce2bf3
Merge branch 'main' into f-ephemeral_values_bootstrap
johnsonaj Oct 11, 2024
3890bb8
aws_kms_secrets: update object type
johnsonaj Oct 15, 2024
05427a0
Merge branch 'main' into f-ephemeral_values_bootstrap
johnsonaj Oct 15, 2024
50e0fcd
register logger
johnsonaj Oct 21, 2024
e8e30cc
resolve conflicts
johnsonaj Oct 21, 2024
e7486c3
resolve conflicts
johnsonaj Oct 29, 2024
e7c0a86
cleanup go.mod
johnsonaj Oct 30, 2024
d9d2bb6
update go.mod
johnsonaj Oct 31, 2024
93d84f4
resolve conflicts
johnsonaj Oct 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ require (
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/hcl/v2 v2.22.0
github.com/hashicorp/terraform-json v0.23.0
github.com/hashicorp/terraform-plugin-framework v1.12.0
github.com/hashicorp/terraform-plugin-framework v1.13.0
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1
github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -648,8 +648,8 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW
github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ=
github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE=
github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw=
github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 h1:SJXL5FfJJm17554Kpt9jFXngdM6fXbnUnZ6iT2IeiYA=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0/go.mod h1:p0phD0IYhsu9bR4+6OetVvvH59I6LwjXGnTVEr8ox6E=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 h1:gm5b1kHgFFhaKFhm4h2TgvMUlNzFAtUqlcOWnWPm+9E=
Expand Down
24 changes: 21 additions & 3 deletions internal/conns/conns.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ type ServicePackage interface {
ServicePackageName() string
}

// ServicePackageWithEphemeralResources is an interface that extends ServicePackage with ephemeral resources.
// Ephemeral resources are resources that are not part of the Terraform state, but are used to create other resources.
type ServicePackageWithEphemeralResources interface {
ServicePackage
EphemeralResources(context.Context) []*types.ServicePackageEphemeralResource
}

type (
contextKeyType int
)
Expand All @@ -29,9 +36,10 @@ var (

// InContext represents the resource information kept in Context.
type InContext struct {
IsDataSource bool // Data source?
ResourceName string // Friendly resource name, e.g. "Subnet"
ServicePackageName string // Canonical name defined as a constant in names package
IsDataSource bool // Data source?
IsEphemeralResource bool // Ephemeral resource?
ResourceName string // Friendly resource name, e.g. "Subnet"
ServicePackageName string // Canonical name defined as a constant in names package
}

func NewDataSourceContext(ctx context.Context, servicePackageName, resourceName string) context.Context {
Expand All @@ -44,6 +52,16 @@ func NewDataSourceContext(ctx context.Context, servicePackageName, resourceName
return context.WithValue(ctx, contextKey, &v)
}

func NewEphemeralResourceContext(ctx context.Context, servicePackageName, resourceName string) context.Context {
v := InContext{
IsEphemeralResource: true,
ResourceName: resourceName,
ServicePackageName: servicePackageName,
}

return context.WithValue(ctx, contextKey, &v)
}

func NewResourceContext(ctx context.Context, servicePackageName, resourceName string) context.Context {
v := InContext{
ResourceName: resourceName,
Expand Down
21 changes: 21 additions & 0 deletions internal/framework/ephemeral_resource_with_configure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package framework

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
)

type EphemeralResourceWithConfigure struct {
withMeta
}

func (e *EphemeralResourceWithConfigure) Configure(_ context.Context, request ephemeral.ConfigureRequest, _ *ephemeral.ConfigureResponse) {
if v, ok := request.ProviderData.(*conns.AWSClient); ok {
e.meta = v
}
}
15 changes: 15 additions & 0 deletions internal/generate/servicepackage/file.gtpl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ import (

type servicePackage struct {}

{{- if .EphemeralResources }}
func (p *servicePackage) EphemeralResources(ctx context.Context) []*types.ServicePackageEphemeralResource {
return []*types.ServicePackageEphemeralResource {
{{- range .EphemeralResources }}
{
Factory: {{ .FactoryName }},
{{- if ne .Name "" }}
Name: "{{ .Name }}",
{{- end }}
},
{{- end }}
}
}
{{- end }}

func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.ServicePackageFrameworkDataSource {
return []*types.ServicePackageFrameworkDataSource {
{{- range .FrameworkDataSources }}
Expand Down
10 changes: 10 additions & 0 deletions internal/generate/servicepackage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func main() {
v := &visitor{
g: g,

ephemeralResources: make([]ResourceDatum, 0),
frameworkDataSources: make([]ResourceDatum, 0),
frameworkResources: make([]ResourceDatum, 0),
sdkDataSources: make(map[string]ResourceDatum),
Expand All @@ -76,6 +77,7 @@ func main() {
GoV2Package: l.GoV2Package(),
ProviderPackage: p,
ProviderNameUpper: l.ProviderNameUpper(),
EphemeralResources: v.ephemeralResources,
FrameworkDataSources: v.frameworkDataSources,
FrameworkResources: v.frameworkResources,
SDKDataSources: v.sdkDataSources,
Expand Down Expand Up @@ -131,6 +133,7 @@ type ServiceDatum struct {
GoV2Package string // AWS SDK for Go v2 package name
ProviderPackage string
ProviderNameUpper string
EphemeralResources []ResourceDatum
FrameworkDataSources []ResourceDatum
FrameworkResources []ResourceDatum
SDKDataSources map[string]ResourceDatum
Expand All @@ -156,6 +159,7 @@ type visitor struct {
functionName string
packageName string

ephemeralResources []ResourceDatum
frameworkDataSources []ResourceDatum
frameworkResources []ResourceDatum
sdkDataSources map[string]ResourceDatum
Expand Down Expand Up @@ -239,6 +243,12 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) {
}

switch annotationName := m[1]; annotationName {
case "EphemeralResource":
if slices.ContainsFunc(v.ephemeralResources, func(d ResourceDatum) bool { return d.FactoryName == v.functionName }) {
v.errs = append(v.errs, fmt.Errorf("duplicate Ephemeral Resource: %s", fmt.Sprintf("%s.%s", v.packageName, v.functionName)))
} else {
v.ephemeralResources = append(v.ephemeralResources, d)
}
case "FrameworkDataSource":
if slices.ContainsFunc(v.frameworkDataSources, func(d ResourceDatum) bool { return d.FactoryName == v.functionName }) {
v.errs = append(v.errs, fmt.Errorf("duplicate Framework Data Source: %s", fmt.Sprintf("%s.%s", v.packageName, v.functionName)))
Expand Down
76 changes: 76 additions & 0 deletions internal/provider/fwprovider/intercept.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/hashicorp/aws-sdk-go-base/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-log/tflog"
Expand Down Expand Up @@ -46,6 +47,12 @@ func (s dataSourceInterceptors) read() []dataSourceInterceptorReadFunc {
})
}

type ephemeralResourceInterceptor interface {
// TODO implement me
}

type ephemeralResourceInterceptors []ephemeralResourceInterceptor

type resourceCRUDRequest interface {
resource.CreateRequest | resource.ReadRequest | resource.UpdateRequest | resource.DeleteRequest
}
Expand Down Expand Up @@ -239,6 +246,75 @@ func (w *wrappedDataSource) Configure(ctx context.Context, request datasource.Co
w.inner.Configure(ctx, request, response)
}

type wrappedEphemeralResource struct {
// bootstrapContext is run on all wrapped methods before any interceptors.
bootstrapContext contextFunc
inner ephemeral.EphemeralResourceWithConfigure
meta *conns.AWSClient
interceptors ephemeralResourceInterceptors
}

func (w *wrappedEphemeralResource) Metadata(ctx context.Context, request ephemeral.MetadataRequest, response *ephemeral.MetadataResponse) {
ctx = w.bootstrapContext(ctx, w.meta)
w.inner.Metadata(ctx, request, response)
}

func (w *wrappedEphemeralResource) Schema(ctx context.Context, request ephemeral.SchemaRequest, response *ephemeral.SchemaResponse) {
ctx = w.bootstrapContext(ctx, w.meta)
w.inner.Schema(ctx, request, response)
}

func (w *wrappedEphemeralResource) Open(ctx context.Context, request ephemeral.OpenRequest, response *ephemeral.OpenResponse) {
ctx = w.bootstrapContext(ctx, w.meta)
w.inner.Open(ctx, request, response)
}

func (w *wrappedEphemeralResource) Configure(ctx context.Context, request ephemeral.ConfigureRequest, response *ephemeral.ConfigureResponse) {
if v, ok := request.ProviderData.(*conns.AWSClient); ok {
w.meta = v
}
ctx = w.bootstrapContext(ctx, w.meta)
w.inner.Configure(ctx, request, response)
}

func (w *wrappedEphemeralResource) Renew(ctx context.Context, request ephemeral.RenewRequest, response *ephemeral.RenewResponse) {
if v, ok := w.inner.(ephemeral.EphemeralResourceWithRenew); ok {
ctx = w.bootstrapContext(ctx, w.meta)
v.Renew(ctx, request, response)
}
}

func (w *wrappedEphemeralResource) Close(ctx context.Context, request ephemeral.CloseRequest, response *ephemeral.CloseResponse) {
if v, ok := w.inner.(ephemeral.EphemeralResourceWithClose); ok {
ctx = w.bootstrapContext(ctx, w.meta)
v.Close(ctx, request, response)
}
}

func (w *wrappedEphemeralResource) ConfigValidators(ctx context.Context) []ephemeral.ConfigValidator {
if v, ok := w.inner.(ephemeral.EphemeralResourceWithConfigValidators); ok {
ctx = w.bootstrapContext(ctx, w.meta)
return v.ConfigValidators(ctx)
}

return nil
}

func (w *wrappedEphemeralResource) ValidateConfig(ctx context.Context, request ephemeral.ValidateConfigRequest, response *ephemeral.ValidateConfigResponse) {
if v, ok := w.inner.(ephemeral.EphemeralResourceWithValidateConfig); ok {
ctx = w.bootstrapContext(ctx, w.meta)
v.ValidateConfig(ctx, request, response)
}
}

func newWrappedEphemeralResource(bootstrapContext contextFunc, inner ephemeral.EphemeralResourceWithConfigure, interceptors ephemeralResourceInterceptors) ephemeral.EphemeralResourceWithConfigure {
return &wrappedEphemeralResource{
bootstrapContext: bootstrapContext,
inner: inner,
interceptors: interceptors,
}
}

func (w *wrappedDataSource) ConfigValidators(ctx context.Context) []datasource.ConfigValidator {
if v, ok := w.inner.(datasource.DataSourceWithConfigValidators); ok {
ctx = w.bootstrapContext(ctx, w.meta)
Expand Down
54 changes: 54 additions & 0 deletions internal/provider/fwprovider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
Expand All @@ -27,6 +28,7 @@ import (

var _ provider.Provider = &fwprovider{}
var _ provider.ProviderWithFunctions = &fwprovider{}
var _ provider.ProviderWithEphemeralResources = &fwprovider{}

// New returns a new, initialized Terraform Plugin Framework-style provider instance.
// The provider instance is fully configured once the `Configure` method has been called.
Expand Down Expand Up @@ -301,6 +303,7 @@ func (p *fwprovider) Configure(ctx context.Context, request provider.ConfigureRe
v := p.Primary.Meta()
response.DataSourceData = v
response.ResourceData = v
response.EphemeralResourceData = v
}

// DataSources returns a slice of functions to instantiate each DataSource
Expand Down Expand Up @@ -458,6 +461,57 @@ func (p *fwprovider) Resources(ctx context.Context) []func() resource.Resource {
return resources
}

// EphemeralResources returns a slice of functions to instantiate each Ephemeral Resource
// implementation.
//
// The resource type name is determined by the Ephemeral Resource implementing
// the Metadata method. All resources must have unique names.
func (p *fwprovider) EphemeralResources(ctx context.Context) []func() ephemeral.EphemeralResource {
var errs []error
var ephemeralResources []func() ephemeral.EphemeralResource

for n, sp := range p.Primary.Meta().(*conns.AWSClient).ServicePackages {
if data, ok := sp.(conns.ServicePackageWithEphemeralResources); ok {
servicePackageName := data.ServicePackageName()

for _, v := range data.EphemeralResources(ctx) {
inner, err := v.Factory(ctx)

if err != nil {
tflog.Warn(ctx, "creating ephemeral resource", map[string]interface{}{
"service_package_name": n,
"error": err.Error(),
})

continue
}

// bootstrapContext is run on all wrapped methods before any interceptors.
bootstrapContext := func(ctx context.Context, meta *conns.AWSClient) context.Context {
ctx = conns.NewEphemeralResourceContext(ctx, servicePackageName, v.Name)
if meta != nil {
ctx = meta.RegisterLogger(ctx)
ctx = flex.RegisterLogger(ctx)
}
return ctx
}

ephemeralResources = append(ephemeralResources, func() ephemeral.EphemeralResource {
return newWrappedEphemeralResource(bootstrapContext, inner, nil)
})
}
}
}

if err := errors.Join(errs...); err != nil {
tflog.Warn(ctx, "registering ephemeral resources", map[string]interface{}{
"error": err.Error(),
})
}

return ephemeralResources
}

// Functions returns a slice of functions to instantiate each Function
// implementation.
//
Expand Down
Loading
Loading