Skip to content

Commit 366132a

Browse files
authored
Use terraform cli by default, make using opentofu optional (#339)
* Use terraform cli by default, opt into using opentofu * TestS3BucketModSecret using both tf and opentofu * lint * Fix TestPulumiSchemaForModuleHasLanguageInfoGo * install terraform CLI in setup-tools * refactor to use module executor variable => WIP * Schema inference tests, Delete, Read using PickModuleRuntime with tests * lint NewRuntimeFromExecutable * lint tests * better error message in resolveModuleSources * include runtime description in error message * more debugging * TestNewTerraformInit * show terraform version in CI * rerun actions * use bash to show terraform version * implement test logger for debugging * cleanup module after extracting content * error on cleaup * unique workspaces for terraform vs tofu * revert workdir changes, log picked runtime * AuxProviderServer initialized once per test * update the version of vpc module we are testing against * use macos-latest instead of ubuntu-latest * cleanup tfmodules_test * prepend workspace with executor during tests * revert adding working directory to tests * propagate the module executor * Fixed TestApplyModuleOverrides * use module dir in TestExtractModuleContentWorks * run terraform and tofu tests sequentially * TESTPARALLELISM := 1 * run opentofu then terraform in tests * larger runner with specific working directory per executor * clean working directory before extracting * clean working dir before running the rest of the test * Split TestExtractModuleContentWorks * remove cleanup of dirs * Run prerequisite tests twice per executor * Remove testUsingExecutor * back to ubuntu latest * fix failing tests * run acceptance tests without using t.Setenv(...) * remove parallel from tests using multiple executors * Remove t.Parallel() from TestUnknownsInCreatePlanBySchemaTypeSecrets * Separate tests per executor * use executor-specific working directory when initialiting component * use newPulumiTest * uncomment debug options * fix more views test * Fix TestDiffDetail * Fix TestState to run both terraform and opentofu * Skip hanging refresh tests * Unskip TestRefreshOpenTofu and TestRefreshNoChangesOpenTofu * a bit of cleanup
1 parent 8c0b4b5 commit 366132a

File tree

37 files changed

+1626
-818
lines changed

37 files changed

+1626
-818
lines changed

.github/actions/setup-tools/action.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,11 @@ runs:
8989
uses: gradle/gradle-build-action@ac2d340dc04d9e1113182899e983b5400c17cda1 # v3
9090
with:
9191
gradle-version: 7.6
92+
93+
- name: Setup Terraform
94+
uses: hashicorp/setup-terraform@v3
95+
96+
- name: Show Terraform Version
97+
shell: bash
98+
run: |
99+
terraform -version

.github/workflows/prerequisites.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ jobs:
6666
tofu_wrapper: false
6767
- name: Build provider binary
6868
run: make provider
69-
- name: Unit-test provider code
70-
run: make test_provider
69+
- name: Unit-test provider code with opentofu
70+
run: PULUMI_TERRAFORM_MODULE_EXECUTOR=opentofu make test_provider
71+
- name: Unit-test provider code with terraform
72+
run: PULUMI_TERRAFORM_MODULE_EXECUTOR=terraform make test_provider
7173
- name: Upload coverage reports to Codecov
7274
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
7375
env:

pkg/modprovider/constants.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
package modprovider
22

3-
const defaultComponentTypeName = "Module"
3+
const (
4+
defaultComponentTypeName = "Module"
5+
moduleExecutorVariableName = "executor"
6+
moduleExecutorEnvironmentVariable = "PULUMI_TERRAFORM_MODULE_EXECUTOR"
7+
)

pkg/modprovider/helpers_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,26 @@ import (
99

1010
"github.com/pulumi/pulumi-terraform-module/pkg/auxprovider"
1111
"github.com/pulumi/pulumi-terraform-module/pkg/tfsandbox"
12+
"github.com/pulumi/pulumi-terraform-module/pkg/tofuresolver"
1213
)
1314

15+
type testLogger struct {
16+
logs []string
17+
}
18+
19+
func (l *testLogger) Log(_ context.Context, level tfsandbox.LogLevel, msg string) {
20+
l.logs = append(l.logs, string(level)+": "+msg)
21+
}
22+
23+
func (l *testLogger) LogStatus(_ context.Context, level tfsandbox.LogLevel, msg string) {
24+
l.logs = append(l.logs, string(level)+": "+msg)
25+
}
26+
1427
//nolint:unused
15-
func newTestTofu(t *testing.T) *tfsandbox.Tofu {
28+
func newTestTofu(t *testing.T) *tfsandbox.ModuleRuntime {
1629
srv := newTestAuxProviderServer(t)
1730

18-
tofu, err := tfsandbox.NewTofu(context.Background(), tfsandbox.DiscardLogger, nil, srv)
31+
tofu, err := tfsandbox.NewTofu(context.Background(), tfsandbox.DiscardLogger, nil, srv, tofuresolver.ResolveOpts{})
1932
require.NoError(t, err)
2033

2134
t.Cleanup(func() {

pkg/modprovider/module.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,13 @@ func (h *moduleHandler) prepSandbox(
104104
moduleSource TFModuleSource,
105105
moduleVersion TFModuleVersion,
106106
providersConfig map[string]resource.PropertyMap,
107-
) (*tfsandbox.Tofu, error) {
107+
executor string,
108+
) (*tfsandbox.ModuleRuntime, error) {
108109
logger := newResourceLogger(h.hc, urn)
109-
wd := tfsandbox.ModuleInstanceWorkdir(urn)
110-
tf, err := tfsandbox.NewTofu(ctx, logger, wd, h.auxProviderServer)
110+
wd := tfsandbox.ModuleInstanceWorkdir(executor, urn)
111+
tf, err := tfsandbox.PickModuleRuntime(ctx, logger, wd, h.auxProviderServer, executor)
111112
if err != nil {
112-
return nil, fmt.Errorf("Sandbox construction failed: %w", err)
113+
return nil, fmt.Errorf("sandbox construction failed: %w", err)
113114
}
114115

115116
// Important: the name of the module instance in TF must be at least unique enough to
@@ -130,7 +131,7 @@ func (h *moduleHandler) prepSandbox(
130131
moduleVersion, tf.WorkingDir(),
131132
moduleInputs, outputSpecs, providersConfig)
132133
if err != nil {
133-
return nil, fmt.Errorf("Seed file generation failed: %w", err)
134+
return nil, fmt.Errorf("seed file generation failed: %w", err)
134135
}
135136

136137
if oldOutputs != nil {
@@ -143,7 +144,7 @@ func (h *moduleHandler) prepSandbox(
143144

144145
err = tf.Init(ctx, logger)
145146
if err != nil {
146-
return nil, fmt.Errorf("Init failed: %w", err)
147+
return nil, fmt.Errorf("init failed: %w", err)
147148
}
148149

149150
return tf, nil
@@ -161,6 +162,7 @@ func (h *moduleHandler) applyModuleOperation(
161162
inferredModule *InferredModuleSchema,
162163
packageName packageName,
163164
preview bool,
165+
executor string,
164166
) (resource.PropertyMap, []*pulumirpc.ViewStep, error) {
165167
tf, err := h.prepSandbox(
166168
ctx,
@@ -171,6 +173,7 @@ func (h *moduleHandler) applyModuleOperation(
171173
moduleSource,
172174
moduleVersion,
173175
providersConfig,
176+
executor,
174177
)
175178
if err != nil {
176179
return nil, nil, fmt.Errorf("Failed preparing tofu sandbox: %w", err)
@@ -249,7 +252,7 @@ func (h *moduleHandler) initializationError(outputs resource.PropertyMap, reason
249252
// Pulls the TF state and formats module outputs with the special __ meta-properties.
250253
func (h *moduleHandler) outputs(
251254
ctx context.Context,
252-
tf *tfsandbox.Tofu,
255+
tf *tfsandbox.ModuleRuntime,
253256
tfState *tfsandbox.State,
254257
) (resource.PropertyMap, error) {
255258
rawState, rawLockFile, err := tf.PullStateAndLockFile(ctx)
@@ -273,13 +276,14 @@ func (h *moduleHandler) Create(
273276
providersConfig map[string]resource.PropertyMap,
274277
inferredModule *InferredModuleSchema,
275278
packageName packageName,
279+
executor string,
276280
) (*pulumirpc.CreateResponse, error) {
277281
urn := urn.URN(req.GetUrn())
278282
logger := newResourceLogger(h.hc, urn)
279283

280284
statusClient, err := h.statusPool.Acquire(ctx, logger, req.ResourceStatusAddress)
281285
if err != nil {
282-
return nil, err
286+
return nil, fmt.Errorf("acquiring status client failed in Create: %w", err)
283287
}
284288
defer statusClient.Release()
285289

@@ -301,6 +305,7 @@ func (h *moduleHandler) Create(
301305
inferredModule,
302306
packageName,
303307
req.GetPreview(),
308+
executor,
304309
)
305310

306311
// Publish views even if applyErr != nil as is the case of partial failures.
@@ -336,6 +341,7 @@ func (h *moduleHandler) Update(
336341
providersConfig map[string]resource.PropertyMap,
337342
inferredModule *InferredModuleSchema,
338343
packageName packageName,
344+
executor string,
339345
) (*pulumirpc.UpdateResponse, error) {
340346
urn := urn.URN(req.GetUrn())
341347
logger := newResourceLogger(h.hc, urn)
@@ -352,7 +358,7 @@ func (h *moduleHandler) Update(
352358

353359
statusClient, err := h.statusPool.Acquire(ctx, logger, req.ResourceStatusAddress)
354360
if err != nil {
355-
return nil, err
361+
return nil, fmt.Errorf("acquiring status client failed in Update: %w", err)
356362
}
357363
defer statusClient.Release()
358364

@@ -369,6 +375,7 @@ func (h *moduleHandler) Update(
369375
inferredModule,
370376
packageName,
371377
req.GetPreview(),
378+
executor,
372379
)
373380
// TODO[pulumi/pulumi-terraform-module#342] partial error handling needs to modify this.
374381
if err != nil {
@@ -401,13 +408,14 @@ func (h *moduleHandler) Delete(
401408
moduleVersion TFModuleVersion,
402409
inferredModule *InferredModuleSchema,
403410
providersConfig map[string]resource.PropertyMap,
411+
executor string,
404412
) (*emptypb.Empty, error) {
405413
urn := urn.URN(req.GetUrn())
406414
logger := newResourceLogger(h.hc, resource.URN(req.GetUrn()))
407415

408416
statusClient, err := h.statusPool.Acquire(ctx, logger, req.ResourceStatusAddress)
409417
if err != nil {
410-
return nil, err
418+
return nil, fmt.Errorf("acquiring status client failed in Delete: %w", err)
411419
}
412420
defer statusClient.Release()
413421

@@ -430,9 +438,10 @@ func (h *moduleHandler) Delete(
430438
moduleSource,
431439
moduleVersion,
432440
providersConfig,
441+
executor,
433442
)
434443
if err != nil {
435-
return nil, fmt.Errorf("Failed preparing tofu sandbox: %w", err)
444+
return nil, fmt.Errorf("failed preparing tofu sandbox: %w", err)
436445
}
437446

438447
// TODO[pulumi/pulumi-terraform-module#247] once the engine is ready to receive view steps multiple times, the
@@ -477,6 +486,7 @@ func (h *moduleHandler) Read(
477486
moduleVersion TFModuleVersion,
478487
inferredModule *InferredModuleSchema,
479488
providersConfig map[string]resource.PropertyMap,
489+
executor string,
480490
) (*pulumirpc.ReadResponse, error) {
481491
if req.Inputs == nil {
482492
return nil, fmt.Errorf("Read() is currently only supported for pulumi refresh")
@@ -510,6 +520,7 @@ func (h *moduleHandler) Read(
510520
moduleSource,
511521
moduleVersion,
512522
providersConfig,
523+
executor,
513524
)
514525
if err != nil {
515526
return nil, fmt.Errorf("Failed preparing tofu sandbox: %w", err)

pkg/modprovider/module_component.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func newModuleComponentResource(
6969
packageRef string,
7070
providerSelfURN pulumi.URN,
7171
providersConfig map[string]resource.PropertyMap,
72+
moduleExecutor string,
7273
opts ...pulumi.ResourceOption,
7374
) (componentUrn *urn.URN, moduleStateResource *moduleStateResource, outputs pulumi.Map, finalError error) {
7475
component := ModuleComponentResource{}
@@ -129,14 +130,23 @@ func newModuleComponentResource(
129130
}
130131
}()
131132

132-
wd := tfsandbox.ModuleInstanceWorkdir(urn)
133+
wd := tfsandbox.ModuleInstanceWorkdir(moduleExecutor, urn)
133134
logger := newComponentLogger(ctx.Log, &component)
134135

135-
tf, err := tfsandbox.NewTofu(ctx.Context(), logger, wd, auxProviderServer)
136+
// decide whether to use opentofu or terraform
137+
tf, err := tfsandbox.PickModuleRuntime(
138+
ctx.Context(),
139+
logger, wd, auxProviderServer, moduleExecutor,
140+
)
141+
136142
if err != nil {
137-
return nil, nil, nil, fmt.Errorf("Sandbox construction failed: %w", err)
143+
return nil, nil, nil,
144+
fmt.Errorf("failed to choose module runtime from executor %s: %w",
145+
moduleExecutor, err)
138146
}
139147

148+
logger.LogStatus(ctx.Context(), tfsandbox.Debug, "Using "+tf.Description())
149+
140150
// Important: the name of the module instance in TF must be at least unique enough to
141151
// include the Pulumi resource name to avoid Duplicate URN errors. For now we reuse the
142152
// Pulumi name as present in the module URN.
@@ -155,7 +165,7 @@ func newModuleComponentResource(
155165
moduleInputs, outputSpecs, providersConfig)
156166

157167
if err != nil {
158-
return nil, nil, nil, fmt.Errorf("Seed file generation failed: %w", err)
168+
return nil, nil, nil, fmt.Errorf("seed file generation failed: %w", err)
159169
}
160170

161171
var moduleOutputs resource.PropertyMap
@@ -166,7 +176,7 @@ func newModuleComponentResource(
166176

167177
err = tf.Init(ctx.Context(), logger)
168178
if err != nil {
169-
return nil, nil, nil, fmt.Errorf("Init failed: %w", err)
179+
return nil, nil, nil, fmt.Errorf("init failed: %w", err)
170180
}
171181

172182
var childResources []*childResource
@@ -216,7 +226,7 @@ func newModuleComponentResource(
216226
}
217227
})
218228
if err := errors.Join(errs...); err != nil {
219-
return nil, nil, nil, fmt.Errorf("Child resource init failed: %w", err)
229+
return nil, nil, nil, fmt.Errorf("child resource init failed: %w", err)
220230
}
221231
moduleOutputs = plan.Outputs()
222232
} else {
@@ -258,7 +268,7 @@ func newModuleComponentResource(
258268
}
259269
})
260270
if err := errors.Join(errs...); err != nil {
261-
return nil, nil, nil, fmt.Errorf("Child resource init failed: %w", err)
271+
return nil, nil, nil, fmt.Errorf("child resource init failed: %w", err)
262272
}
263273

264274
moduleOutputs = tfState.Outputs()
@@ -272,7 +282,7 @@ func newModuleComponentResource(
272282
}
273283

274284
if applyErr != nil {
275-
return nil, nil, nil, fmt.Errorf("Apply failed: %w", applyErr)
285+
return nil, nil, nil, fmt.Errorf("apply failed: %w", applyErr)
276286
}
277287

278288
marshalledOutputs := pulumix.MustUnmarshalPropertyMap(ctx, moduleOutputs)

pkg/modprovider/schema.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,24 @@ func pulumiSchemaForModule(pargs *ParameterizeArgs, inferredModule *InferredModu
108108
}
109109
}
110110

111+
moduleExecutorVariable := schema.PropertySpec{
112+
TypeSpec: schema.TypeSpec{
113+
Type: "string",
114+
},
115+
116+
Description: "Sets the executor used to run the module.",
117+
Default: "",
118+
DefaultInfo: &schema.DefaultSpec{
119+
Environment: []string{moduleExecutorEnvironmentVariable},
120+
},
121+
}
122+
123+
if inferredModule.ProvidersConfig.Variables == nil {
124+
inferredModule.ProvidersConfig.Variables = map[string]schema.PropertySpec{}
125+
}
126+
127+
inferredModule.ProvidersConfig.Variables[moduleExecutorVariableName] = moduleExecutorVariable
128+
111129
packageSpec := &schema.PackageSpec{
112130
Name: string(packageName),
113131
Version: string(pkgVer),

0 commit comments

Comments
 (0)