-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
resources/ephemeral: A place to track ephemeral resource instances
- Loading branch information
1 parent
2ab8071
commit 95e1621
Showing
2 changed files
with
136 additions
and
0 deletions.
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
internal/resources/ephemeral/ephemeral_resource_instance.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package ephemeral | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform/internal/providers" | ||
"github.com/hashicorp/terraform/internal/tfdiags" | ||
) | ||
|
||
// ResourceInstance is an interface that must be implemented for each | ||
// active ephemeral resource instance to determine how it should be renewed | ||
// and eventually closed. | ||
type ResourceInstance interface { | ||
// Renew attempts to extend the life of the remote object associated with | ||
// this resource instance, optionally returning a new renewal request to be | ||
// passed to a subsequent call to this method. | ||
// | ||
// If the object's life is not extended successfully then Renew returns | ||
// error diagnostics explaining why not, and future requests that might | ||
// have made use of the object will fail. | ||
Renew(ctx context.Context, req providers.EphemeralRenew) (nextRenew *providers.EphemeralRenew, diags tfdiags.Diagnostics) | ||
|
||
// Close proactively ends the life of the remote object associated with | ||
// this resource instance, if possible. For example, if the remote object | ||
// is a temporary lease for a dynamically-generated secret then this | ||
// might end that lease and thus cause the secret to be promptly revoked. | ||
Close(ctx context.Context) tfdiags.Diagnostics | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package ephemeral | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"sync" | ||
|
||
"github.com/hashicorp/hcl/v2" | ||
"github.com/hashicorp/terraform/internal/addrs" | ||
"github.com/hashicorp/terraform/internal/providers" | ||
"github.com/hashicorp/terraform/internal/tfdiags" | ||
|
||
"github.com/zclconf/go-cty/cty" | ||
) | ||
|
||
// Resources is a tracking structure for active instances of ephemeral | ||
// resources. | ||
// | ||
// The lifecycle of an ephemeral resource instance is quite different than | ||
// other resource modes because it's live for at most the duration of a single | ||
// graph walk, and because it might need periodic "renewing" in order to | ||
// remain live for the necessary duration. | ||
type Resources struct { | ||
active addrs.Map[addrs.ConfigResource, addrs.Map[addrs.AbsResourceInstance, *resourceInstanceInternal]] | ||
mu sync.Mutex | ||
} | ||
|
||
func NewResources() *Resources { | ||
return &Resources{ | ||
active: addrs.MakeMap[addrs.ConfigResource, addrs.Map[addrs.AbsResourceInstance, *resourceInstanceInternal]](), | ||
} | ||
} | ||
|
||
type ResourceInstanceRegistration struct { | ||
Value cty.Value | ||
ConfigBody hcl.Body | ||
Impl ResourceInstance | ||
FirstRenewal *providers.EphemeralRenew | ||
} | ||
|
||
func (r *Resources) RegisterInstance(ctx context.Context, addr addrs.AbsResourceInstance, reg ResourceInstanceRegistration) { | ||
if addr.Resource.Resource.Mode != addrs.EphemeralResourceMode { | ||
panic(fmt.Sprintf("can't register %s as an ephemeral resource instance", addr)) | ||
} | ||
|
||
r.mu.Lock() | ||
defer r.mu.Unlock() | ||
|
||
configAddr := addr.ConfigResource() | ||
if !r.active.Has(configAddr) { | ||
r.active.Put(configAddr, addrs.MakeMap[addrs.AbsResourceInstance, *resourceInstanceInternal]()) | ||
} | ||
r.active.Get(configAddr).Put(addr, &resourceInstanceInternal{ | ||
value: reg.Value, | ||
configBody: reg.ConfigBody, | ||
impl: reg.Impl, | ||
nextRenew: reg.FirstRenewal, | ||
}) | ||
// TODO: If a renewal was requested, start a cancelable goroutine for it. | ||
} | ||
|
||
func (r *Resources) InstanceValue(addr addrs.AbsResourceInstance) (val cty.Value, live bool) { | ||
r.mu.Lock() | ||
defer r.mu.Unlock() | ||
|
||
configAddr := addr.ConfigResource() | ||
insts, ok := r.active.GetOk(configAddr) | ||
if !ok { | ||
return cty.DynamicVal, false | ||
} | ||
inst, ok := insts.GetOk(addr) | ||
if !ok { | ||
return cty.DynamicVal, false | ||
} | ||
// If renewal has failed then we can't assume that the object is still | ||
// live, but we can still return the original value regardless. | ||
return inst.value, !inst.renewDiags.HasErrors() | ||
} | ||
|
||
func (r *Resources) CloseInstances(ctx context.Context, configAddr addrs.ConfigResource) tfdiags.Diagnostics { | ||
r.mu.Lock() | ||
defer r.mu.Unlock() | ||
// TODO: Can we somehow avoid holding the lock for the entire duration? | ||
// Closing an instance is likely to perform a network request, so this | ||
// could potentially take a while and block other work from starting. | ||
|
||
var diags tfdiags.Diagnostics | ||
for _, elem := range r.active.Get(configAddr).Elems { | ||
moreDiags := elem.Value.impl.Close(ctx) | ||
diags = diags.Append(moreDiags.InConfigBody(elem.Value.configBody, elem.Key.String())) | ||
} | ||
r.active.Remove(configAddr) | ||
return diags | ||
} | ||
|
||
type resourceInstanceInternal struct { | ||
value cty.Value | ||
configBody hcl.Body | ||
impl ResourceInstance | ||
nextRenew *providers.EphemeralRenew | ||
renewDiags tfdiags.Diagnostics | ||
} |