From 3c14eeb94552fc1b61485abeabeefa9b95dcc85e Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 5 Jan 2024 17:10:48 -0800 Subject: [PATCH] stackeval: Make some provisioners available to stack components This makes the built-in "remote-exec" and "file" provisioners available for use in the modules that implement stack components. These are both relatively easy and low-risk to include because they are builtins and don't require anything from outside of Terraform itself. For now this intentionally excludes local-exec because we'll want to think about what constraints we want to put on it, if any, to help ensure we can meet the goal of stack configurations being portable between different execution environments without significant modification, and our current stacks execution environment doesn't guarantee the availability of any external software _at all_. The motivation for adding this now is just to give some better feedback when someone uses a module using one of these provisioners, since otherwise they'll see just a confusing generic error message from the modules runtime about the provisioners not being available. I expect we'll revisit this later and consider expanding it to at least include local-exec, and _maybe_ external provisioner plugins, although that's more questionable because the provisioner plugin mechanism is incredibly legacy and doesn't have any way to work outside of local Terraform CLI usage today. There are no tests here yet because these provisioners are not mockable and would depend on having an SSH or WinRM server to connect to. Later we should ponder how to make this more testable, which might mean making another part of the system responsible for actually providing the provisioner factories and thus our tests here can use fakes. The goal here is just to get this done in a relatively lightweight way for better feedback during preview though, so we're not yet ready to make significant time investments here. --- .../internal/stackeval/component_instance.go | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/internal/stacks/stackruntime/internal/stackeval/component_instance.go b/internal/stacks/stackruntime/internal/stackeval/component_instance.go index 1926f32757..b214f11922 100644 --- a/internal/stacks/stackruntime/internal/stackeval/component_instance.go +++ b/internal/stacks/stackruntime/internal/stackeval/component_instance.go @@ -12,6 +12,8 @@ import ( "github.com/zclconf/go-cty/cty/convert" "github.com/hashicorp/terraform/internal/addrs" + fileProvisioner "github.com/hashicorp/terraform/internal/builtin/provisioners/file" + remoteExecProvisioner "github.com/hashicorp/terraform/internal/builtin/provisioners/remote-exec" "github.com/hashicorp/terraform/internal/collections" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/instances" @@ -19,6 +21,7 @@ import ( "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/promising" "github.com/hashicorp/terraform/internal/providers" + "github.com/hashicorp/terraform/internal/provisioners" "github.com/hashicorp/terraform/internal/stacks/stackaddrs" "github.com/hashicorp/terraform/internal/stacks/stackconfig/stackconfigtypes" "github.com/hashicorp/terraform/internal/stacks/stackplan" @@ -490,6 +493,7 @@ func (c *ComponentInstance) CheckModuleTreePlan(ctx context.Context) (*plans.Pla }, }, PreloadedProviderSchemas: providerSchemas, + Provisioners: c.availableProvisioners(), }) if err != nil { // Should not get here because we should always pass a valid @@ -662,6 +666,7 @@ func (c *ComponentInstance) ApplyModuleTreePlan(ctx context.Context, plan *plans tfHook, }, PreloadedProviderSchemas: providerSchemas, + Provisioners: c.availableProvisioners(), }) if err != nil { // Should not get here because we should always pass a valid @@ -1301,6 +1306,35 @@ func (c *ComponentInstance) resourceTypeSchema(ctx context.Context, providerType return ret, nil } +// availableProvisioners returns the table of provisioner factories that should +// be made available to modules in this component. +func (c *ComponentInstance) availableProvisioners() map[string]provisioners.Factory { + return map[string]provisioners.Factory{ + "remote-exec": func() (provisioners.Interface, error) { + return remoteExecProvisioner.New(), nil + }, + "file": func() (provisioners.Interface, error) { + return fileProvisioner.New(), nil + }, + "local-exec": func() (provisioners.Interface, error) { + // We don't yet have any way to ensure a consistent execution + // environment for local-exec, which means that use of this + // provisioner is very likely to hurt portability between + // local and remote usage of stacks. Existing use of local-exec + // also tends to assume a writable module directory, whereas + // stack components execute from a read-only directory. + // + // Therefore we'll leave this unavailable for now with an explicit + // error message, although we might revisit this later if there's + // a strong reason to allow it and if we can find a suitable + // way to avoid the portability pitfalls that might inhibit + // moving execution of a stack from one execution environment to + // another. + return nil, fmt.Errorf("local-exec provisioners are not supported in stack components; use provider functionality or remote provisioners instead") + }, + } +} + func (c *ComponentInstance) tracingName() string { return c.Addr().String() }