Skip to content

Commit 9930511

Browse files
feat: OpenTofu support
1 parent 27b60d5 commit 9930511

File tree

23 files changed

+818
-19
lines changed

23 files changed

+818
-19
lines changed

.github/workflows/common-test.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,3 +584,59 @@ jobs:
584584
run: npx vitest --retry 1 test/terraform-basic.test.ts
585585
- name: Test - observability mode
586586
run: OBSERVABLE_MODE=true npx vitest --retry 1 test/terraform-basic.test.ts
587+
588+
test-opentofu-basic:
589+
runs-on: ubuntu-latest
590+
concurrency:
591+
group: test-opentofu-basic
592+
steps:
593+
- uses: actions/checkout@v4
594+
- name: Use Node.js
595+
uses: actions/setup-node@v4
596+
with:
597+
node-version: ${{ env.node_version }}
598+
registry-url: 'https://registry.npmjs.org'
599+
- name: Install dependencies
600+
run: |
601+
node prepareForTest.js opentofu-basic
602+
npm i
603+
- name: Download build artifact
604+
uses: actions/download-artifact@v4
605+
if: ${{ inputs.mode == 'build' }}
606+
with:
607+
name: dist
608+
path: dist
609+
- name: Install lambda-live-debugger globally
610+
if: ${{ inputs.mode == 'global' }}
611+
run: |
612+
npm i lambda-live-debugger -g
613+
working-directory: test
614+
- name: Install lambda-live-debugger locally
615+
if: ${{ inputs.mode == 'local' }}
616+
run: |
617+
npm i lambda-live-debugger
618+
working-directory: test
619+
- name: Configure AWS Credentials
620+
uses: aws-actions/configure-aws-credentials@v4
621+
with:
622+
aws-region: eu-west-1
623+
role-to-assume: ${{ secrets.AWS_ROLE }}
624+
role-session-name: GitHubActions
625+
- name: Setup OpenTofu
626+
uses: opentofu/setup-opentofu@v1
627+
- name: OpenTofu Init
628+
run: |
629+
./create_bucket.sh lld-opentofu-basic
630+
tofu init -backend-config="bucket=lld-opentofu-basic"
631+
working-directory: test/opentofu-basic
632+
- name: Destroy
633+
run: npm run destroy
634+
working-directory: test/opentofu-basic
635+
continue-on-error: true
636+
- name: Deploy
637+
run: npm run deploy
638+
working-directory: test/opentofu-basic
639+
- name: Test
640+
run: npx vitest --retry 1 test/opentofu-basic.test.ts
641+
- name: Test - observability mode
642+
run: OBSERVABLE_MODE=true npx vitest --retry 1 test/opentofu-basic.test.ts

.vscode/launch.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,26 @@
261261
"type": "node",
262262
"cwd": "${workspaceRoot}/test/terraform-basic"
263263
},
264+
{
265+
"name": "LLDebugger - OpenTofu basic",
266+
"program": "${workspaceRoot}/node_modules/tsx/dist/cli.mjs",
267+
"args": ["../../src/lldebugger.ts", "--config-env=test"],
268+
"request": "launch",
269+
"skipFiles": ["<node_internals>/**"],
270+
"console": "integratedTerminal",
271+
"type": "node",
272+
"cwd": "${workspaceRoot}/test/opentofu-basic"
273+
},
274+
{
275+
"name": "LLDebugger - OpenTofu basic - observability",
276+
"program": "${workspaceRoot}/node_modules/tsx/dist/cli.mjs",
277+
"args": ["../../src/lldebugger.ts", "--config-env=test", "-o"],
278+
"request": "launch",
279+
"skipFiles": ["<node_internals>/**"],
280+
"console": "integratedTerminal",
281+
"type": "node",
282+
"cwd": "${workspaceRoot}/test/opentofu-basic"
283+
},
264284
{
265285
"name": "LLDebugger - CDK config",
266286
"program": "${workspaceRoot}/node_modules/tsx/dist/cli.mjs",

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
"test-sam-alt-observable": "npm run build && RUN_TEST_FROM_CLI=true OBSERVABLE_MODE=true vitest run test/sam-alt.test.ts",
7373
"test-terraform-basic": "npm run build && RUN_TEST_FROM_CLI=true vitest run test/terraform-basic.test.ts",
7474
"test-terraform-basic-observable": "npm run build && RUN_TEST_FROM_CLI=true OBSERVABLE_MODE=true vitest run test/terraform-basic.test.ts",
75+
"test-opentofu-basic": "npm run build && RUN_TEST_FROM_CLI=true vitest run test/opentofu-basic.test.ts",
76+
"test-opentofu-basic-observable": "npm run build && RUN_TEST_FROM_CLI=true OBSERVABLE_MODE=true vitest run test/opentofu-basic.test.ts",
7577
"docs:dev": "vitepress dev",
7678
"docs:build": "vitepress build",
7779
"docs:preview": "vitepress preview"
@@ -154,6 +156,7 @@
154156
"test/osls-esbuild-cjs",
155157
"test/sam-basic",
156158
"test/sam-alt",
157-
"test/terraform-basic"
159+
"test/terraform-basic",
160+
"test/opentofu-basic"
158161
]
159-
}
162+
}

src/frameworks/openTofuFramework.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { exec } from 'child_process';
2+
import { promisify } from 'util';
3+
import { TerraformFramework } from './terraformFramework.js';
4+
5+
export const execAsync = promisify(exec);
6+
7+
/**
8+
* Support for Terraform framework
9+
*/
10+
export class OpenTofuFramework extends TerraformFramework {
11+
/**
12+
* Framework name
13+
*/
14+
public get name(): string {
15+
return 'opentofu';
16+
}
17+
18+
/**
19+
* Name of the framework in logs
20+
*/
21+
protected get logName(): string {
22+
return 'OpenTofu';
23+
}
24+
25+
/**
26+
* Get OpenTofu state CI command
27+
*/
28+
protected get stateCommand(): string {
29+
return 'tofu show --json';
30+
}
31+
}
32+
33+
export const openTofuFramework = new OpenTofuFramework();

src/frameworks/terraformFramework.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ export class TerraformFramework implements IFramework {
4040
return 'terraform';
4141
}
4242

43+
/**
44+
* Name of the framework in logs
45+
*/
46+
protected get logName(): string {
47+
return 'Terrform';
48+
}
49+
4350
/**
4451
* Can this class handle the current project
4552
* @returns
@@ -51,7 +58,7 @@ export class TerraformFramework implements IFramework {
5158

5259
if (!r) {
5360
Logger.verbose(
54-
`[Terraform] This is not a Terraform project. There are no *.tf files in ${path.resolve('.')} folder.`,
61+
`[${this.logName}] This is not a Terraform or OpenTofu project. There are no *.tf files in ${path.resolve('.')} folder.`,
5562
);
5663
}
5764

@@ -69,7 +76,7 @@ export class TerraformFramework implements IFramework {
6976
const lambdas = this.extractLambdaInfo(state);
7077

7178
Logger.verbose(
72-
'[Terraform] Found Lambdas:',
79+
`[${this.logName}] Found Lambdas:`,
7380
JSON.stringify(lambdas, null, 2),
7481
);
7582

@@ -78,7 +85,7 @@ export class TerraformFramework implements IFramework {
7885
const tsOutDir = await this.getTsConfigOutDir();
7986

8087
if (tsOutDir) {
81-
Logger.verbose('[Terraform] tsOutDir:', tsOutDir);
88+
Logger.verbose(`[${this.logName}] tsOutDir:`, tsOutDir);
8289
}
8390

8491
for (const func of lambdas) {
@@ -143,7 +150,7 @@ export class TerraformFramework implements IFramework {
143150
packageJsonPath,
144151
esBuildOptions: undefined,
145152
metadata: {
146-
framework: 'terraform',
153+
framework: this.name,
147154
},
148155
};
149156

@@ -164,7 +171,7 @@ export class TerraformFramework implements IFramework {
164171
for (const resource of resources) {
165172
if (resource.type === 'aws_lambda_function') {
166173
Logger.verbose(
167-
'[Terraform] Found Lambda:',
174+
`[${this.logName}] Found Lambda:`,
168175
JSON.stringify(resource, null, 2),
169176
);
170177

@@ -235,42 +242,49 @@ export class TerraformFramework implements IFramework {
235242
return lambdas;
236243
}
237244

245+
/**
246+
* Get Terraform state CI command
247+
*/
248+
protected get stateCommand(): string {
249+
return 'terraform show --json';
250+
}
251+
238252
protected async readTerraformState(): Promise<TerraformResource[]> {
239253
// Is there a better way to get the Terraform state???
240254

241255
let output: any;
242256

243257
// get state by running "terraform show --json" command
244258
try {
245-
output = await execAsync('terraform show --json');
259+
output = await execAsync(this.stateCommand);
246260
} catch (error: any) {
247261
throw new Error(
248-
`Failed to get Terraform state from 'terraform show --json' command: ${error.message}`,
262+
`[${this.logName}] Failed to getstate from '${this.stateCommand}' command: ${error.message}`,
249263
{ cause: error },
250264
);
251265
}
252266

253267
if (output.stderr) {
254268
throw new Error(
255-
`Failed to get Terraform state from 'terraform show --json' command: ${output.stderr}`,
269+
`[${this.logName}] Failed to get state from '${this.stateCommand}' command: ${output.stderr}`,
256270
);
257271
}
258272

259273
if (!output.stdout) {
260274
throw new Error(
261-
"Failed to get Terraform state from 'terraform show --json' command",
275+
`[${this.logName}] Failed to get state from '${this.stateCommand}' command`,
262276
);
263277
}
264278

265279
let jsonString: string | undefined = output.stdout;
266280

267-
Logger.verbose('Terraform state:', jsonString);
281+
Logger.verbose(`[${this.logName}] State:`, jsonString);
268282

269283
jsonString = jsonString?.split('\n').find((line) => line.startsWith('{'));
270284

271285
if (!jsonString) {
272286
throw new Error(
273-
'Failed to get Terraform state. JSON string not found in the output.',
287+
`[${this.logName}] Failed to get state. JSON string not found in the output.`,
274288
);
275289
}
276290

@@ -288,10 +302,10 @@ export class TerraformFramework implements IFramework {
288302
return [...rootResources, ...childResources] as TerraformResource[];
289303
} catch (error: any) {
290304
//save state to file
291-
await fs.writeFile('terraform-state.json', jsonString);
292-
Logger.error('Failed to parse Terraform state JSON:', error);
305+
await fs.writeFile(`${this.name}-state.json`, jsonString);
306+
Logger.error(`[${this.logName}] Failed to parse state JSON:`, error);
293307
throw new Error(
294-
`Failed to parse Terraform state JSON: ${error.message}`,
308+
`Failed to parse ${this.logName} state JSON: ${error.message}`,
295309
{ cause: error },
296310
);
297311
}
@@ -314,11 +328,11 @@ export class TerraformFramework implements IFramework {
314328
}
315329
}
316330
if (!tsConfigPath) {
317-
Logger.verbose('[Terraform] tsconfig.json not found');
331+
Logger.verbose(`[${this.logName}] tsconfig.json not found`);
318332
return undefined;
319333
}
320334

321-
Logger.verbose('[Terraform] tsconfig.json found:', tsConfigPath);
335+
Logger.verbose(`[${this.logName}] tsconfig.json found:`, tsConfigPath);
322336
const configFile = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
323337
const compilerOptions = ts.parseJsonConfigFileContent(
324338
configFile.config,

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ export { CdkFramework } from './frameworks/cdkFramework.js';
99
export { SlsFramework } from './frameworks/slsFramework.js';
1010
export { SamFramework } from './frameworks/samFramework.js';
1111
export { TerraformFramework } from './frameworks/terraformFramework.js';
12+
export { OpenTofuFramework } from './frameworks/openTofuFramework.js';
1213
export { type IFramework } from './frameworks/iFrameworks.js';
1314
export { type AwsConfiguration } from './types/awsConfiguration.js';

src/resourceDiscovery.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { cdkFramework } from './frameworks/cdkFramework.js';
33
import { slsFramework } from './frameworks/slsFramework.js';
44
import { samFramework } from './frameworks/samFramework.js';
55
import { terraformFramework } from './frameworks/terraformFramework.js';
6+
import { openTofuFramework } from './frameworks/openTofuFramework.js';
67
import { LldConfig } from './types/lldConfig.js';
78
import { LambdaResource } from './types/resourcesDiscovery.js';
89
import { Logger } from './logger.js';
@@ -16,6 +17,7 @@ const frameworksSupported: IFramework[] = [
1617
slsFramework,
1718
samFramework,
1819
terraformFramework,
20+
openTofuFramework,
1921
];
2022

2123
/**

0 commit comments

Comments
 (0)