diff --git a/internal/providers/ephemeral.go b/internal/providers/ephemeral.go index 9512ec51a7..0a4df9f31f 100644 --- a/internal/providers/ephemeral.go +++ b/internal/providers/ephemeral.go @@ -27,6 +27,9 @@ type OpenEphemeralResourceRequest struct { // Computed-only attributes are always null in the configuration, because // they can be set only in the response. Config cty.Value + + // ClientCapabilities contains information about the client's capabilities. + ClientCapabilities ClientCapabilities } // OpenEphemeralResourceRequest represents the response from an OpenEphemeralResource @@ -54,7 +57,7 @@ type OpenEphemeralResourceResponse struct { // where a final value cannot be predicted. Result cty.Value - // InternalContext is any internal data needed by the provider to perform a + // Private is any internal data needed by the provider to perform a // subsequent [Interface.CloseEphemeralResource] request for the same object. The // provider may choose any encoding format to represent the needed data, // because Terraform Core treats this field as opaque. @@ -71,16 +74,16 @@ type OpenEphemeralResourceResponse struct { // received by exactly the same plugin instance that returned this value, // and so it's valid for this to refer to in-memory state belonging to the // provider instance. - InternalContext []byte + Private []byte - // Renew, if set, signals that the opened object has an inherent expration - // time and so must be "renewed" if Terraform needs to use it beyond that - // expiration time. + // RenewAt, if non-zero, signals that the opened object has an inherent + // expiration time and so must be "renewed" if Terraform needs to use it + // beyond that expiration time. // // If a provider sets this field then it may receive a subsequent - // [Interface.RenewEphemeralResource] call, if Terraform expects to need the + // Interface.RenewEphemeralResource call, if Terraform expects to need the // object beyond the expiration time. - Renew *EphemeralRenew + RenewAt time.Time // Diagnostics describes any problems encountered while opening the // ephemeral resource. If this contains errors then the other response @@ -91,11 +94,11 @@ type OpenEphemeralResourceResponse struct { // EphemeralRenew describes when and how Terraform Core must request renewal // of an ephemeral resource instance in order to continue using it. type EphemeralRenew struct { - // ExpireTime is the deadline before which Terraform must renew the + // RenewAt is the deadline before which Terraform must renew the // ephemeral resource instance. - ExpireTime time.Time + RenewAt time.Time - // InternalContext is any internal data needed by the provider to + // Private is any internal data needed by the provider to // perform a subsequent [Interface.RenewEphemeralResource] request. The provider // may choose any encoding format to represent the needed data, because // Terraform Core treats this field as opaque. @@ -113,7 +116,7 @@ type EphemeralRenew struct { // the OpenEphemeralResource or RenewEphemeralResource request that produced this internal // context, and so it's valid for this to refer to in-memory state in the // provider object. - InternalContext []byte + Private []byte } // RenewEphemeralResourceRequest represents the arguments for the RenewEphemeralResource @@ -124,22 +127,28 @@ type RenewEphemeralResourceRequest struct { // [OpenEphemeralResourceRequest]. TypeName string - // InternalContext echoes verbatim the value from the field of the same + // Private echoes verbatim the value from the field of the same // name from the most recent [EphemeralRenew] object, received from either // an [OpenEphemeralResourceResponse] or a [RenewEphemeralResourceResponse] object. - InternalContext []byte + Private []byte } // RenewEphemeralResourceRequest represents the response from a RenewEphemeralResource // operation on a provider. type RenewEphemeralResourceResponse struct { - // RenewAgain, if set, describes a new expiration deadline for the + // RenewAt, if non-zero, describes a new expiration deadline for the // object, possibly causing a further call to [Interface.RenewEphemeralResource] // if Terraform needs to exceed the updated deadline. // // If this is not set then Terraform Core will not make any further // renewal requests for the remaining life of the object. - RenewAgain *EphemeralRenew + RenewAt time.Time + + // Private is any internal data needed by the provider to + // perform a subsequent [Interface.RenewEphemeralResource] request. The provider + // may choose any encoding format to represent the needed data, because + // Terraform Core treats this field as opaque. + Private []byte // Diagnostics describes any problems encountered while renewing the // ephemeral resource instance. If this contains errors then the other @@ -163,9 +172,9 @@ type CloseEphemeralResourceRequest struct { // [OpenEphemeralResourceRequest]. TypeName string - // InternalContext echoes verbatim the value from the field of the same + // Private echoes verbatim the value from the field of the same // name from the corresponding [OpenEphemeralResourceResponse] object. - InternalContext []byte + Private []byte } // CloseEphemeralResourceRequest represents the response from a CloseEphemeralResource diff --git a/internal/resources/ephemeral/ephemeral_resource_test.go b/internal/resources/ephemeral/ephemeral_resource_test.go index 7daac84c5a..d3dad6be87 100644 --- a/internal/resources/ephemeral/ephemeral_resource_test.go +++ b/internal/resources/ephemeral/ephemeral_resource_test.go @@ -65,16 +65,16 @@ func TestResources(t *testing.T) { Value: cty.ObjectVal(map[string]cty.Value{ "test": cty.StringVal("ephemeral.test.a[0]"), }), - Impl: testA0, - FirstRenewal: &providers.EphemeralRenew{ExpireTime: time.Now().Add(10 * time.Millisecond)}, + Impl: testA0, + RenewAt: time.Now().Add(10 * time.Millisecond), }) resources.RegisterInstance(ctx, ephemA1, ResourceInstanceRegistration{ Value: cty.ObjectVal(map[string]cty.Value{ "test": cty.StringVal("ephemeral.test.a[1]"), }), - Impl: testA1, - FirstRenewal: &providers.EphemeralRenew{ExpireTime: time.Now().Add(10 * time.Millisecond)}, + Impl: testA1, + RenewAt: time.Now().Add(10 * time.Millisecond), }) resources.RegisterInstance(ctx, ephemB, ResourceInstanceRegistration{ @@ -194,16 +194,16 @@ func TestResourcesCancellation(t *testing.T) { Value: cty.ObjectVal(map[string]cty.Value{ "test": cty.StringVal("ephemeral.test.a[0]"), }), - Impl: testA0, - FirstRenewal: &providers.EphemeralRenew{ExpireTime: time.Now().Add(10 * time.Millisecond)}, + Impl: testA0, + RenewAt: time.Now().Add(10 * time.Millisecond), }) resources.RegisterInstance(ctx, ephemA1, ResourceInstanceRegistration{ Value: cty.ObjectVal(map[string]cty.Value{ "test": cty.StringVal("ephemeral.test.a[1]"), }), - Impl: testA1, - FirstRenewal: &providers.EphemeralRenew{ExpireTime: time.Now().Add(10 * time.Millisecond)}, + Impl: testA1, + RenewAt: time.Now().Add(10 * time.Millisecond), }) resources.RegisterInstance(ctx, ephemB, ResourceInstanceRegistration{ @@ -283,7 +283,7 @@ type testResourceInstance struct { func (r *testResourceInstance) Renew(ctx context.Context, req providers.EphemeralRenew) (*providers.EphemeralRenew, tfdiags.Diagnostics) { nextRenew := &providers.EphemeralRenew{ - ExpireTime: time.Now().Add(r.renewInterval), + RenewAt: time.Now().Add(r.renewInterval), } r.Lock() defer r.Unlock() diff --git a/internal/resources/ephemeral/ephemeral_resources.go b/internal/resources/ephemeral/ephemeral_resources.go index 773a3d59b3..eb745f76d7 100644 --- a/internal/resources/ephemeral/ephemeral_resources.go +++ b/internal/resources/ephemeral/ephemeral_resources.go @@ -40,10 +40,11 @@ func NewResources() *Resources { } type ResourceInstanceRegistration struct { - Value cty.Value - ConfigBody hcl.Body - Impl ResourceInstance - FirstRenewal *providers.EphemeralRenew + Value cty.Value + ConfigBody hcl.Body + Impl ResourceInstance + RenewAt time.Time + Private []byte } func (r *Resources) RegisterInstance(ctx context.Context, addr addrs.AbsResourceInstance, reg ResourceInstanceRegistration) { @@ -64,12 +65,17 @@ func (r *Resources) RegisterInstance(ctx context.Context, addr addrs.AbsResource impl: reg.Impl, renewCancel: noopCancel, } - if reg.FirstRenewal != nil { + if !reg.RenewAt.IsZero() { ctx, cancel := context.WithCancel(ctx) ri.renewCancel = cancel + renewal := &providers.EphemeralRenew{ + RenewAt: reg.RenewAt, + Private: reg.Private, + } + r.wg.Add(1) - go ri.handleRenewal(ctx, &r.wg, reg.FirstRenewal) + go ri.handleRenewal(ctx, &r.wg, renewal) } r.active.Get(configAddr).Put(addr, ri) } @@ -220,7 +226,7 @@ func (r *resourceInstanceInternal) close(ctx context.Context) tfdiags.Diagnostic func (r *resourceInstanceInternal) handleRenewal(ctx context.Context, wg *sync.WaitGroup, firstRenewal *providers.EphemeralRenew) { defer wg.Done() - t := time.NewTimer(time.Until(firstRenewal.ExpireTime)) + t := time.NewTimer(time.Until(firstRenewal.RenewAt)) nextRenew := firstRenewal for { select { @@ -242,7 +248,7 @@ func (r *resourceInstanceInternal) handleRenewal(ctx context.Context, wg *sync.W return } nextRenew = anotherRenew - t.Reset(time.Until(anotherRenew.ExpireTime)) + t.Reset(time.Until(anotherRenew.RenewAt)) r.renewMu.Unlock() case <-ctx.Done(): // If we're cancelled then we'll halt renewing immediately. diff --git a/internal/terraform/node_resource_ephemeral.go b/internal/terraform/node_resource_ephemeral.go index ef9a9029bd..489cd2d6c2 100644 --- a/internal/terraform/node_resource_ephemeral.go +++ b/internal/terraform/node_resource_ephemeral.go @@ -122,14 +122,15 @@ func ephemeralResourceOpen(ctx EvalContext, inp ephemeralResourceInput) tfdiags. impl := &ephemeralResourceInstImpl{ addr: inp.addr, provider: provider, - internal: resp.InternalContext, + internal: resp.Private, } ephemerals.RegisterInstance(ctx.StopCtx(), inp.addr, ephemeral.ResourceInstanceRegistration{ - Value: resultVal, - ConfigBody: config.Config, - Impl: impl, - FirstRenewal: resp.Renew, + Value: resultVal, + ConfigBody: config.Config, + Impl: impl, + RenewAt: resp.RenewAt, + Private: resp.Private, }) return diags @@ -185,8 +186,8 @@ var _ ephemeral.ResourceInstance = (*ephemeralResourceInstImpl)(nil) func (impl *ephemeralResourceInstImpl) Close(ctx context.Context) tfdiags.Diagnostics { log.Printf("[TRACE] ephemeralResourceInstImpl: closing %s", impl.addr) resp := impl.provider.CloseEphemeralResource(providers.CloseEphemeralResourceRequest{ - TypeName: impl.addr.Resource.Resource.Type, - InternalContext: impl.internal, + TypeName: impl.addr.Resource.Resource.Type, + Private: impl.internal, }) return resp.Diagnostics } @@ -195,8 +196,14 @@ func (impl *ephemeralResourceInstImpl) Close(ctx context.Context) tfdiags.Diagno func (impl *ephemeralResourceInstImpl) Renew(ctx context.Context, req providers.EphemeralRenew) (nextRenew *providers.EphemeralRenew, diags tfdiags.Diagnostics) { log.Printf("[TRACE] ephemeralResourceInstImpl: renewing %s", impl.addr) resp := impl.provider.RenewEphemeralResource(providers.RenewEphemeralResourceRequest{ - TypeName: impl.addr.Resource.Resource.Type, - InternalContext: req.InternalContext, + TypeName: impl.addr.Resource.Resource.Type, + Private: req.Private, }) - return resp.RenewAgain, resp.Diagnostics + + if !resp.RenewAt.IsZero() { + nextRenew.RenewAt = resp.RenewAt + nextRenew.Private = resp.Private + } + + return nextRenew, resp.Diagnostics }