Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "infra-diff",
"image": "mcr.microsoft.com/devcontainers/typescript-node:22",
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
"ghcr.io/devcontainers-contrib/features/terraform-asdf:2": {
"version": "1.13.4"
}
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"biomejs.biome",
"hashicorp.terraform"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome"
}
}
},
"postCreateCommand": "npm install",
"forwardPorts": [50000],
"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
],
"remoteUser": "node"
}
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@ updates:
schedule:
interval: "weekly"
open-pull-requests-limit: 5

# Monitor Terraform
- package-ecosystem: "terraform"
directory: "/e2e/terraform"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
16 changes: 15 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,23 @@ jobs:
- run: npm test
- run: npm run test:e2e:file-reading

test-terraform-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version-file: ".nvmrc"
- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
terraform_version: 1.13.4
- run: npm ci
- name: Run Terraform integration tests
run: npm run test:e2e:terraform

validate-action:
runs-on: ubuntu-latest
needs: [lint, test]
needs: [lint, test, test-terraform-integration]
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
Expand Down
1 change: 1 addition & 0 deletions .terraform-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.13.4
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
version: '3.8'
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'version' field in docker-compose.yml is deprecated as of Docker Compose v1.27.0+ and is no longer required. Consider removing this line as modern versions of Docker Compose ignore it.

Suggested change
version: '3.8'

Copilot uses AI. Check for mistakes.

services:
moto:
image: motoserver/moto:5.0.0
container_name: infra-diff-moto-test
ports:
- "50000:5000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
140 changes: 140 additions & 0 deletions e2e/terraform-integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { execSync } from "node:child_process";
import { existsSync, rmSync, unlinkSync } from "node:fs";
import * as path from "node:path";
import { GenericContainer, type StartedTestContainer } from "testcontainers";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { ParsePlanUseCase } from "../src/domain/usecases/ParsePlanUseCase";
import { ReadPlanFileUseCase } from "../src/domain/usecases/ReadPlanFileUseCase";
import { FilesystemAdapter } from "../src/infrastructure/adapters/FilesystemAdapter";

describe("E2E: Terraform Integration with moto", () => {
const terraformDir = path.join(process.cwd(), "e2e", "terraform");
const planFile = path.join(terraformDir, "plan.bin");
const planJsonFile = path.join(terraformDir, "plan.json");
let motoContainer: StartedTestContainer;
let motoPort: number;

beforeAll(async () => {
// Start moto server using testcontainers
console.log("Starting moto server...");
try {
motoPort = 50000;
motoContainer = await new GenericContainer("motoserver/moto:5.0.0")
.withExposedPorts({ container: 5000, host: motoPort })
.withStartupTimeout(120000)
.start();

console.log(`Moto server is ready on port ${motoPort}`);
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.log for test output violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Suggested change
console.log(`Moto server is ready on port ${motoPort}`);
console.log(JSON.stringify({ event: "moto_server_ready", port: motoPort }));

Copilot uses AI. Check for mistakes.

// Initialize Terraform
console.log("Initializing Terraform...");
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.log for test output violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Suggested change
console.log("Initializing Terraform...");
console.log(JSON.stringify({ message: "Initializing Terraform..." }));

Copilot uses AI. Check for mistakes.
execSync("terraform init", { cwd: terraformDir, stdio: "pipe" });

Check failure on line 31 in e2e/terraform-integration.test.ts

View workflow job for this annotation

GitHub Actions / validate

e2e/terraform-integration.test.ts > E2E: Terraform Integration with moto

Error: Command failed: terraform init /bin/sh: 1: terraform: not found ❯ e2e/terraform-integration.test.ts:31:4 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { status: 127, signal: null, output: [ null, '<Buffer(0) ...>', '<Buffer(33) ...>' ], pid: 3192, stdout: '<Buffer(0) ...>', stderr: '<Buffer(33) ...>' }

Check failure on line 31 in e2e/terraform-integration.test.ts

View workflow job for this annotation

GitHub Actions / test

e2e/terraform-integration.test.ts > E2E: Terraform Integration with moto

Error: Command failed: terraform init /bin/sh: 1: terraform: not found ❯ e2e/terraform-integration.test.ts:31:4 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { status: 127, signal: null, output: [ null, '<Buffer(0) ...>', '<Buffer(33) ...>' ], pid: 3138, stdout: '<Buffer(0) ...>', stderr: '<Buffer(33) ...>' }

// Generate plan
console.log("Generating Terraform plan...");
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.log for test output violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Suggested change
console.log("Generating Terraform plan...");
console.log(JSON.stringify({ event: "Generating Terraform plan" }));

Copilot uses AI. Check for mistakes.
execSync(`terraform plan -out=${planFile}`, {

Check failure on line 35 in e2e/terraform-integration.test.ts

View workflow job for this annotation

GitHub Actions / test-terraform-integration

e2e/terraform-integration.test.ts > E2E: Terraform Integration with moto

Error: Command failed: terraform plan -out=/home/runner/work/infra-diff/infra-diff/e2e/terraform/plan.bin ╷ │ Error: No valid credential sources found │ │ with provider["registry.terraform.io/hashicorp/aws"], │ on provider.tf line 1, in provider "aws": │ 1: provider "aws" { │ │ Please see https://registry.terraform.io/providers/hashicorp/aws │ for more information about providing credentials. │ │ Error: failed to refresh cached credentials, no EC2 IMDS role found, │ operation error ec2imds: GetMetadata, access disabled to EC2 IMDS via │ client option, or "AWS_EC2_METADATA_DISABLED" environment variable │ ╵ ❯ e2e/terraform-integration.test.ts:35:4 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { status: 1, signal: null, output: [ null, '<Buffer(146) ...>', '<Buffer(785) ...>' ], pid: 3140, stdout: '<Buffer(146) ...>', stderr: '<Buffer(785) ...>' }

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI 5 days ago

The best fix is to avoid constructing shell command strings by interpolating dynamic paths. Instead, use argument arrays to pass parameters directly to the underlying process, thereby avoiding shell interpolation altogether (which can be tricked by paths with spaces or shell metacharacters). Specifically, for the vulnerable terraform plan -out=${planFile} invocation at line 35, move from backtick/interpolated string usage to argument array usage like ["plan", "-out", planFile]. Change the invocation on line 35 to:

execSync("terraform plan -out=${planFile}", { ... });

to

execSync("terraform", ["plan", "-out", planFile], { ... });

Also, be sure to select the correct method signature: execSync(command, options) runs via the shell, while execFileSync(file, args, options) runs the file directly with argument separation (the secure way). Therefore, switch to execFileSync from the node:child_process module.

In addition, you must import execFileSync at the top if not already imported (which is not done in the shown code), since currently only execSync is imported.

The fix only needs to be applied to line 35 (and the import statement at the top).


Suggested changeset 1
e2e/terraform-integration.test.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/e2e/terraform-integration.test.ts b/e2e/terraform-integration.test.ts
--- a/e2e/terraform-integration.test.ts
+++ b/e2e/terraform-integration.test.ts
@@ -1,4 +1,4 @@
-import { execSync } from "node:child_process";
+import { execSync, execFileSync } from "node:child_process";
 import { existsSync, rmSync, unlinkSync } from "node:fs";
 import * as path from "node:path";
 import { GenericContainer, type StartedTestContainer } from "testcontainers";
@@ -32,7 +32,7 @@
 
 			// Generate plan
 			console.log("Generating Terraform plan...");
-			execSync(`terraform plan -out=${planFile}`, {
+			execFileSync("terraform", ["plan", "-out", planFile], {
 				cwd: terraformDir,
 				stdio: "pipe",
 			});
EOF
@@ -1,4 +1,4 @@
import { execSync } from "node:child_process";
import { execSync, execFileSync } from "node:child_process";
import { existsSync, rmSync, unlinkSync } from "node:fs";
import * as path from "node:path";
import { GenericContainer, type StartedTestContainer } from "testcontainers";
@@ -32,7 +32,7 @@

// Generate plan
console.log("Generating Terraform plan...");
execSync(`terraform plan -out=${planFile}`, {
execFileSync("terraform", ["plan", "-out", planFile], {
cwd: terraformDir,
stdio: "pipe",
});
Copilot is powered by AI and may make mistakes. Always verify output.
cwd: terraformDir,
stdio: "pipe",
});

// Convert plan to JSON
console.log("Converting plan to JSON...");
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.log for test output violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Copilot uses AI. Check for mistakes.
execSync(`terraform show -json ${planFile} > ${planJsonFile}`, {

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI 5 days ago

To fix the highlighted issue, we should avoid dynamically constructing a shell command string where path variables could include unsafe shell metacharacters. Instead, we should invoke the underlying command directly using an API that accepts command arguments as an array, with output redirection done programmatically, not via shell syntax. Specifically, for terraform show -json ${planFile} > ${planJsonFile}, we should:

  1. Use execFileSync from child_process to execute:
    • Command: terraform
    • Arguments: ["show", "-json", planFile]
  2. Capture the stdout result of the command and write it to planJsonFile using node's fs functionality (writeFileSync).
  3. Remove the output redirection (> ...) from the shell command string and the need for a shell.

Only lines 42-45 are affected in e2e/terraform-integration.test.ts.

No new method definitions are required, but we must ensure that writeFileSync is imported from fs if not already present.

Suggested changeset 1
e2e/terraform-integration.test.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/e2e/terraform-integration.test.ts b/e2e/terraform-integration.test.ts
--- a/e2e/terraform-integration.test.ts
+++ b/e2e/terraform-integration.test.ts
@@ -1,5 +1,5 @@
 import { execSync } from "node:child_process";
-import { existsSync, rmSync, unlinkSync } from "node:fs";
+import { existsSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
 import * as path from "node:path";
 import { GenericContainer, type StartedTestContainer } from "testcontainers";
 import { afterAll, beforeAll, describe, expect, it } from "vitest";
@@ -39,11 +39,11 @@
 
 			// Convert plan to JSON
 			console.log("Converting plan to JSON...");
-			execSync(`terraform show -json ${planFile} > ${planJsonFile}`, {
+			const planJsonOutput = execSync("terraform show -json " + planFile, {
 				cwd: terraformDir,
 				stdio: "pipe",
-				shell: "/bin/bash",
 			});
+			writeFileSync(planJsonFile, planJsonOutput);
 		} catch (error) {
 			console.error("Setup failed:", error);
 			throw error;
EOF
@@ -1,5 +1,5 @@
import { execSync } from "node:child_process";
import { existsSync, rmSync, unlinkSync } from "node:fs";
import { existsSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
import * as path from "node:path";
import { GenericContainer, type StartedTestContainer } from "testcontainers";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
@@ -39,11 +39,11 @@

// Convert plan to JSON
console.log("Converting plan to JSON...");
execSync(`terraform show -json ${planFile} > ${planJsonFile}`, {
const planJsonOutput = execSync("terraform show -json " + planFile, {
cwd: terraformDir,
stdio: "pipe",
shell: "/bin/bash",
});
writeFileSync(planJsonFile, planJsonOutput);
} catch (error) {
console.error("Setup failed:", error);
throw error;
Copilot is powered by AI and may make mistakes. Always verify output.
cwd: terraformDir,
stdio: "pipe",
shell: "/bin/bash",
});
} catch (error) {
console.error("Setup failed:", error);
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.error violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Copilot uses AI. Check for mistakes.
throw error;
}
}, 120000); // 2 minute timeout for setup
Comment on lines +17 to +51
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The beforeAll hook contains multiple complex setup steps (container startup, terraform init, plan, and JSON conversion) that violate the principle of single responsibility. Consider extracting these steps into separate, well-named helper functions to improve readability and maintainability.

Copilot uses AI. Check for mistakes.

Comment on lines +10 to +52
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.log for test output violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Suggested change
describe("E2E: Terraform Integration with moto", () => {
const terraformDir = path.join(process.cwd(), "e2e", "terraform");
const planFile = path.join(terraformDir, "plan.bin");
const planJsonFile = path.join(terraformDir, "plan.json");
let motoContainer: StartedTestContainer;
let motoPort: number;
beforeAll(async () => {
// Start moto server using testcontainers
console.log("Starting moto server...");
try {
motoPort = 50000;
motoContainer = await new GenericContainer("motoserver/moto:5.0.0")
.withExposedPorts({ container: 5000, host: motoPort })
.withStartupTimeout(120000)
.start();
console.log(`Moto server is ready on port ${motoPort}`);
// Initialize Terraform
console.log("Initializing Terraform...");
execSync("terraform init", { cwd: terraformDir, stdio: "pipe" });
// Generate plan
console.log("Generating Terraform plan...");
execSync(`terraform plan -out=${planFile}`, {
cwd: terraformDir,
stdio: "pipe",
});
// Convert plan to JSON
console.log("Converting plan to JSON...");
execSync(`terraform show -json ${planFile} > ${planJsonFile}`, {
cwd: terraformDir,
stdio: "pipe",
shell: "/bin/bash",
});
} catch (error) {
console.error("Setup failed:", error);
throw error;
}
}, 120000); // 2 minute timeout for setup

Copilot uses AI. Check for mistakes.
afterAll(async () => {
// Cleanup
console.log("Cleaning up...");
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.log for test output violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Suggested change
console.log("Cleaning up...");
console.log(JSON.stringify({ event: "cleanup", message: "Cleaning up..." }));

Copilot uses AI. Check for mistakes.

// Remove plan files
try {
if (existsSync(planFile)) {
unlinkSync(planFile);
}
if (existsSync(planJsonFile)) {
unlinkSync(planJsonFile);
}
} catch (error) {
console.error("Failed to cleanup plan files:", error);
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.error violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Suggested change
console.error("Failed to cleanup plan files:", error);
console.log(JSON.stringify({ level: "error", message: "Failed to cleanup plan files", error: error instanceof Error ? error.message : String(error) }));

Copilot uses AI. Check for mistakes.
}

// Remove .terraform directory
try {
const terraformStateDir = path.join(terraformDir, ".terraform");
if (existsSync(terraformStateDir)) {
rmSync(terraformStateDir, { recursive: true, force: true });
}
} catch (error) {
console.error("Failed to cleanup .terraform directory:", error);
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.error violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Suggested change
console.error("Failed to cleanup .terraform directory:", error);
console.log(JSON.stringify({ level: "error", message: "Failed to cleanup .terraform directory", error }));

Copilot uses AI. Check for mistakes.
}

// Remove lock file
try {
const lockFile = path.join(terraformDir, ".terraform.lock.hcl");
if (existsSync(lockFile)) {
unlinkSync(lockFile);
}
} catch (error) {
console.error("Failed to cleanup lock file:", error);
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.error violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Copilot uses AI. Check for mistakes.
}

// Stop and remove moto container
try {
if (motoContainer) {
await motoContainer.stop();
console.log("Moto container stopped");
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.log for test output violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Copilot uses AI. Check for mistakes.
}
} catch (error) {
console.error("Failed to stop moto container:", error);
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.error violates the operational convention stated in the coding guidelines: 'ensure all application logs are structured as JSON objects, except when logging to the console in GitHub Actions.' Since this is a test file, consider using the test framework's logging capabilities or a structured logger.

Copilot uses AI. Check for mistakes.
}
}, 60000); // 1 minute timeout for cleanup

it("should parse real Terraform plan output", async () => {
// Verify plan JSON file was created
expect(existsSync(planJsonFile)).toBe(true);

// Read the plan file
const fileReader = new FilesystemAdapter();
const readUseCase = new ReadPlanFileUseCase(fileReader);
const readResult = await readUseCase.execute(planJsonFile);

expect(readResult.path).toBe(planJsonFile);
expect(readResult.content).toBeTruthy();
expect(readResult.content.length).toBeGreaterThan(0);

// Parse the plan
const parseUseCase = new ParsePlanUseCase();
const plan = await parseUseCase.parse(readResult.content);

// Verify plan structure
expect(plan).toBeDefined();
expect(plan.formatVersion).toBeTruthy();
expect(plan.terraformVersion).toBeTruthy();
expect(plan.resourceChanges).toBeDefined();
expect(Array.isArray(plan.resourceChanges)).toBe(true);

// Verify we have the expected SQS queue resource change
expect(plan.resourceChanges.length).toBeGreaterThan(0);

const sqsQueue = plan.resourceChanges.find(
(rc) => rc.type === "aws_sqs_queue" && rc.name === "queue",
);

expect(sqsQueue).toBeDefined();
expect(sqsQueue?.address).toBe("aws_sqs_queue.queue");
expect(sqsQueue?.actions).toContain("create");

// Verify the queue has expected configuration
expect(sqsQueue?.after).toBeDefined();
expect(sqsQueue?.after?.name).toBe("test-queue");
expect(sqsQueue?.after?.tags).toBeDefined();
});
});
3 changes: 3 additions & 0 deletions e2e/terraform/backend.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
terraform {
backend "local" {}
}
12 changes: 12 additions & 0 deletions e2e/terraform/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
provider "aws" {
region = "us-east-1"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true

endpoints {
sts = "http://localhost:50000"
s3 = "http://localhost:50000"
sqs = "http://localhost:50000"
}
}
7 changes: 7 additions & 0 deletions e2e/terraform/sqs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resource "aws_sqs_queue" "queue" {
name = "test-queue"
tags = {
github = "https://github.com/zpratt/infra-diff.git"
random = 1234
}
}
16 changes: 16 additions & 0 deletions features/end-to-end-with-terraform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# End to End with Terraform

Goal: Demonstrate how Infra Diff can be used in an end-to-end workflow with Terraform and moto to provide a fake of the AWS API. The goal is to produce a real binary terraform plan, convert it to json, and then run infra-diff against that json plan.

## Context

In this workflow, we will use Terraform to create a simple infrastructure setup on AWS. We will use moto to mock the AWS API, allowing us to generate a binary terraform plan without needing access to a real AWS account. This plan will then be converted to JSON format and analyzed using Infra Diff to identify any potential changes or issues. The test itself should be implemented in typescript, using the infra-diff library to run the analysis programmatically, treating the production code as if it is a complete black box. We should run moto in a docker container to ensure a clean and isolated environment for the test. Everything that is created should be runnable locally with a single command and should also be included in our CI pipeline to ensure consistent results across different environments. I've added example terraform fixtures in the e2e/terraform directory to get started. Ensure you examine the e2e/terraform/provider.tf file to see how to configure terraform to point at the moto server.

## Requirements

- Ensure the pipeline uses setup-terraform action to install terraform
- Ensure we're testing with the latest version of terraform
- Use moto server in a docker container to mock AWS API
- When running locally, create a devcontainer environment that we can use to run the tests with a specific version of node and terraform.
- Write the test in typescript using infra-diff library to analyze the terraform plan json output.
- Ensure the entire setup can be run with a single command both locally and in CI.
Loading
Loading