rpcapi+stacks: Stacks runtime can see whether experiments are allowed

We allow experiments only in alpha builds, and so we propagate the flag
for whether that's allowed in from "package main". We previously had that
plumbed in only as far as the rpcapi startup.

This plumbs the flag all the way into package stackeval so that we can
in turn propagate it to Terraform's module config loader, which is
ultimately the one responsible for ensuring that language experiments can
be enabled only when the flag is set.

Therefore it will now be possible to opt in to language experiments in
modules that are used in stack components.
pull/34651/head
Martin Atkins 2 years ago
parent 15cb549a5c
commit 7dad938fdb

@ -42,7 +42,7 @@ type Client struct {
func NewInternalClient(ctx context.Context, clientCaps *terraform1.ClientCapabilities) (*Client, error) {
fakeListener := bufconn.Listen(4 * 1024 * 1024 /* buffer size */)
srv := grpc.NewServer()
registerGRPCServices(srv)
registerGRPCServices(srv, &serviceOpts{})
go func() {
if err := srv.Serve(fakeListener); err != nil {

@ -28,19 +28,22 @@ func (p *corePlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker,
}
func (p *corePlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
registerGRPCServices(s)
generalOpts := &serviceOpts{
experimentsAllowed: p.experimentsAllowed,
}
registerGRPCServices(s, generalOpts)
return nil
}
func registerGRPCServices(s *grpc.Server) {
func registerGRPCServices(s *grpc.Server, opts *serviceOpts) {
// We initially only register the setup server, because the registration
// of other services can vary depending on the capabilities negotiated
// during handshake.
setup := newSetupServer(serverHandshake(s))
setup := newSetupServer(serverHandshake(s, opts))
terraform1.RegisterSetupServer(s, setup)
}
func serverHandshake(s *grpc.Server) func(context.Context, *terraform1.ClientCapabilities) (*terraform1.ServerCapabilities, error) {
func serverHandshake(s *grpc.Server, opts *serviceOpts) func(context.Context, *terraform1.ClientCapabilities) (*terraform1.ServerCapabilities, error) {
dependencies := dynrpcserver.NewDependenciesStub()
terraform1.RegisterDependenciesServer(s, dependencies)
stacks := dynrpcserver.NewStacksStub()
@ -71,7 +74,7 @@ func serverHandshake(s *grpc.Server) func(context.Context, *terraform1.ClientCap
// doing real work. In future the details of what we register here
// might vary based on the negotiated capabilities.
dependencies.ActivateRPCServer(newDependenciesServer(handles, services))
stacks.ActivateRPCServer(newStacksServer(handles))
stacks.ActivateRPCServer(newStacksServer(handles, opts))
packages.ActivateRPCServer(newPackagesServer(services))
// If the client requested any extra capabililties that we're going
@ -79,3 +82,12 @@ func serverHandshake(s *grpc.Server) func(context.Context, *terraform1.ClientCap
return &terraform1.ServerCapabilities{}, nil
}
}
// serviceOpts are options that could potentially apply to all of our
// individual RPC services.
//
// This could potentially be embedded inside a service-specific options
// structure, if needed.
type serviceOpts struct {
experimentsAllowed bool
}

@ -31,14 +31,16 @@ import (
type stacksServer struct {
terraform1.UnimplementedStacksServer
handles *handleTable
handles *handleTable
experimentsAllowed bool
}
var _ terraform1.StacksServer = (*stacksServer)(nil)
func newStacksServer(handles *handleTable) *stacksServer {
func newStacksServer(handles *handleTable, opts *serviceOpts) *stacksServer {
return &stacksServer{
handles: handles,
handles: handles,
experimentsAllowed: opts.experimentsAllowed,
}
}
@ -104,7 +106,8 @@ func (s *stacksServer) ValidateStackConfiguration(ctx context.Context, req *terr
}
diags := stackruntime.Validate(ctx, &stackruntime.ValidateRequest{
Config: cfg,
Config: cfg,
ExperimentsAllowed: s.experimentsAllowed,
})
return &terraform1.ValidateStackConfiguration_Response{
Diagnostics: diagnosticsToProto(diags),
@ -230,11 +233,12 @@ func (s *stacksServer) PlanStackChanges(req *terraform1.PlanStackChanges_Request
changesCh := make(chan stackplan.PlannedChange, 8)
diagsCh := make(chan tfdiags.Diagnostic, 2)
rtReq := stackruntime.PlanRequest{
PlanMode: planMode,
Config: cfg,
PrevState: prevState,
ProviderFactories: providerFactories,
InputValues: inputValues,
PlanMode: planMode,
Config: cfg,
PrevState: prevState,
ProviderFactories: providerFactories,
InputValues: inputValues,
ExperimentsAllowed: s.experimentsAllowed,
}
rtResp := stackruntime.PlanResponse{
PlannedChanges: changesCh,
@ -368,9 +372,10 @@ func (s *stacksServer) ApplyStackChanges(req *terraform1.ApplyStackChanges_Reque
changesCh := make(chan stackstate.AppliedChange, 8)
diagsCh := make(chan tfdiags.Diagnostic, 2)
rtReq := stackruntime.ApplyRequest{
Config: cfg,
ProviderFactories: providerFactories,
RawPlan: req.PlannedChanges,
Config: cfg,
ProviderFactories: providerFactories,
RawPlan: req.PlannedChanges,
ExperimentsAllowed: s.experimentsAllowed,
}
rtResp := stackruntime.ApplyResponse{
AppliedChanges: changesCh,
@ -503,10 +508,11 @@ func (s *stacksServer) OpenStackInspector(ctx context.Context, req *terraform1.O
}
hnd := s.handles.NewStackInspector(&stacksInspector{
Config: cfg,
State: state,
ProviderFactories: providerFactories,
InputValues: inputValues,
Config: cfg,
State: state,
ProviderFactories: providerFactories,
InputValues: inputValues,
ExperimentsAllowed: s.experimentsAllowed,
})
return &terraform1.OpenStackInspector_Response{

@ -29,10 +29,11 @@ import (
// provide what they want to inspect just once and then perform any number
// of subsequent inspection actions against it.
type stacksInspector struct {
Config *stackconfig.Config
State *stackstate.State
ProviderFactories map[addrs.Provider]providers.Factory
InputValues map[stackaddrs.InputVariable]stackruntime.ExternalInputValue
Config *stackconfig.Config
State *stackstate.State
ProviderFactories map[addrs.Provider]providers.Factory
InputValues map[stackaddrs.InputVariable]stackruntime.ExternalInputValue
ExperimentsAllowed bool
}
// InspectExpressionResult evaluates a given expression string in the
@ -57,11 +58,12 @@ func (i *stacksInspector) InspectExpressionResult(ctx context.Context, req *terr
}
val, moreDiags := stackruntime.EvalExpr(ctx, expr, &stackruntime.EvalExprRequest{
Config: i.Config,
State: i.State,
EvalStackInstance: stackAddr,
InputValues: i.InputValues,
ProviderFactories: i.ProviderFactories,
Config: i.Config,
State: i.State,
EvalStackInstance: stackAddr,
InputValues: i.InputValues,
ProviderFactories: i.ProviderFactories,
ExperimentsAllowed: i.ExperimentsAllowed,
})
diags = diags.Append(moreDiags)
if val == cty.NilVal {

@ -28,7 +28,7 @@ func TestStacksOpenCloseStackConfiguration(t *testing.T) {
ctx := context.Background()
handles := newHandleTable()
stacksServer := newStacksServer(handles)
stacksServer := newStacksServer(handles, &serviceOpts{})
// In normal use a client would have previously opened a source bundle
// using Dependencies.OpenSourceBundle, so we'll simulate the effect
@ -110,7 +110,7 @@ func TestStacksFindStackConfigurationComponents(t *testing.T) {
ctx := context.Background()
handles := newHandleTable()
stacksServer := newStacksServer(handles)
stacksServer := newStacksServer(handles, &serviceOpts{})
// In normal use a client would have previously opened a source bundle
// using Dependencies.OpenSourceBundle, so we'll simulate the effect
@ -226,7 +226,7 @@ func TestStacksPlanStackChanges(t *testing.T) {
ctx := context.Background()
handles := newHandleTable()
stacksServer := newStacksServer(handles)
stacksServer := newStacksServer(handles, &serviceOpts{})
fakeSourceBundle := &sourcebundle.Bundle{}
bundleHnd := handles.NewSourceBundle(fakeSourceBundle)

@ -96,6 +96,8 @@ type ApplyRequest struct {
RawPlan []*anypb.Any
ProviderFactories map[addrs.Provider]providers.Factory
ExperimentsAllowed bool
}
// ApplyResponse is used by [Apply] to describe the results of applying.

@ -28,6 +28,7 @@ func EvalExpr(ctx context.Context, expr hcl.Expression, req *EvalExprRequest) (c
InputVariableValues: req.InputValues,
ProviderFactories: req.ProviderFactories,
})
main.AllowLanguageExperiments(req.ExperimentsAllowed)
return main.EvalExpr(ctx, expr, req.EvalStackInstance, stackeval.InspectPhase)
}
@ -50,4 +51,6 @@ type EvalExprRequest struct {
// configurations corresponding to these.
InputValues map[stackaddrs.InputVariable]ExternalInputValue
ProviderFactories map[addrs.Provider]providers.Factory
ExperimentsAllowed bool
}

@ -26,6 +26,8 @@ type ApplyOpts struct {
// unrecognized then the apply phase will use this to emit the necessary
// "discard" events to keep the state consistent.
PrevStateDescKeys collections.Set[statekeys.Key]
ExperimentsAllowed bool
}
// Applyable is an interface implemented by types which represent objects

@ -126,6 +126,7 @@ func (c *ComponentConfig) CheckModuleTree(ctx context.Context) (*configs.Config,
// source files on disk (an implementation detail) rather than
// preserving the source address abstraction.
parser := configs.NewParser(afero.NewOsFs())
parser.AllowLanguageExperiments(c.main.LanguageExperimentsAllowed())
if !parser.IsConfigDir(rootModuleDir) {
diags = diags.Append(&hcl.Diagnostic{

@ -65,6 +65,11 @@ type Main struct {
// This must never be used outside of test code in this package.
testOnlyGlobals map[string]cty.Value
// languageExperimentsAllowed gets set if our caller enables the use
// of language experiments by calling [Main.AllowLanguageExperiments]
// shortly after creating this object.
languageExperimentsAllowed bool
// The remaining fields memoize other objects we might create in response
// to method calls. Must lock "mu" before interacting with them.
mu sync.Mutex
@ -156,6 +161,22 @@ func NewForInspecting(config *stackconfig.Config, state *stackstate.State, opts
}
}
// AllowLanguageExperiments changes the flag for whether language experiments
// are allowed during evaluation.
//
// Call this very shortly after creating a [Main], before performing any other
// actions on it. Changing this setting after other methods have been called
// will produce unpredictable results.
func (m *Main) AllowLanguageExperiments(allow bool) {
m.languageExperimentsAllowed = allow
}
// LanguageExperimentsAllowed returns true if language experiments are allowed
// to be used during evaluation.
func (m *Main) LanguageExperimentsAllowed() bool {
return m.languageExperimentsAllowed
}
// Validating returns true if the receiving [Main] is configured for validating.
//
// If this returns false then validation methods may panic or return strange

@ -199,6 +199,7 @@ func ApplyPlan(ctx context.Context, config *stackconfig.Config, rawPlan []*anypb
})
main := NewForApplying(config, plan.RootInputValues, plan, results, opts)
main.AllowLanguageExperiments(opts.ExperimentsAllowed)
begin(ctx, main) // the change tasks registered above become runnable
// With the planned changes now in progress, we'll visit everything and

@ -49,6 +49,7 @@ func Plan(ctx context.Context, req *PlanRequest, resp *PlanResponse) {
ForcePlanTimestamp: req.ForcePlanTimestamp,
})
main.AllowLanguageExperiments(req.ExperimentsAllowed)
main.PlanAll(ctx, stackeval.PlanOutput{
AnnouncePlannedChange: func(ctx context.Context, change stackplan.PlannedChange) {
resp.PlannedChanges <- change
@ -97,6 +98,8 @@ type PlanRequest struct {
// to return the given value instead of whatever real time the plan
// operation started. This is for testing purposes only.
ForcePlanTimestamp *time.Time
ExperimentsAllowed bool
}
// PlanResponse is used by [Plan] to describe the results of planning.

@ -19,6 +19,7 @@ func Validate(ctx context.Context, req *ValidateRequest) tfdiags.Diagnostics {
defer span.End()
main := stackeval.NewForValidating(req.Config, stackeval.ValidateOpts{})
main.AllowLanguageExperiments(req.ExperimentsAllowed)
diags := main.ValidateAll(ctx)
diags = diags.Append(
main.DoCleanup(ctx),
@ -32,5 +33,7 @@ func Validate(ctx context.Context, req *ValidateRequest) tfdiags.Diagnostics {
type ValidateRequest struct {
Config *stackconfig.Config
ExperimentsAllowed bool
// TODO: Provider factories and other similar such things
}

Loading…
Cancel
Save