From 0ebc66a2fc5ccd7dfafa72d850db34779d032bf1 Mon Sep 17 00:00:00 2001 From: o-webdev Date: Sat, 28 Mar 2026 15:19:17 +0000 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20v0.5.3=20=E2=80=94=20wider=20table?= =?UTF-8?q?=20columns,=20Azure/GCP=20E2E=20fixtures,=20all=20three=20provi?= =?UTF-8?q?ders=20in=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/greenops-e2e.yml | 15 ++++++++++++++- dist/index.cjs | 14 +++++++------- formatters/table.test.ts | 14 ++++++++++++++ formatters/table.ts | 14 +++++++------- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/.github/workflows/greenops-e2e.yml b/.github/workflows/greenops-e2e.yml index 55ca30e..c1ba6c6 100644 --- a/.github/workflows/greenops-e2e.yml +++ b/.github/workflows/greenops-e2e.yml @@ -9,6 +9,7 @@ on: - 'cli.ts' - 'factors.json' - 'dist/index.cjs' + - 'fixtures/**' - '.github/workflows/greenops-e2e.yml' jobs: @@ -20,8 +21,20 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Run GreenOps against fixture plan + - name: Run GreenOps — AWS fixture uses: ./ with: plan-file: fixtures/tfplan.e2e.json github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Run GreenOps — Azure fixture + uses: ./ + with: + plan-file: fixtures/tfplan.azure.e2e.json + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Run GreenOps — GCP fixture + uses: ./ + with: + plan-file: fixtures/tfplan.gcp.e2e.json + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/dist/index.cjs b/dist/index.cjs index 488a91d..b7026a4 100755 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -2986,11 +2986,11 @@ function formatTable(result2) { return out + `No compatible infrastructure detected. `; } - out += `\u250C${"\u2500".repeat(38)}\u252C${"\u2500".repeat(13)}\u252C${"\u2500".repeat(13)}\u252C${"\u2500".repeat(11)}\u252C${"\u2500".repeat(11)}\u252C${"\u2500".repeat(9)}\u252C${"\u2500".repeat(13)}\u2510 + out += `\u250C${"\u2500".repeat(38)}\u252C${"\u2500".repeat(20)}\u252C${"\u2500".repeat(16)}\u252C${"\u2500".repeat(11)}\u252C${"\u2500".repeat(11)}\u252C${"\u2500".repeat(9)}\u252C${"\u2500".repeat(13)}\u2510 `; - out += `\u2502 ${truncate("Resource", 36)} \u2502 ${truncate("Instance", 11)} \u2502 ${truncate("Region", 11)} \u2502 ${truncate("Scope 2", 9)} \u2502 ${truncate("Scope 3", 9)} \u2502 ${truncate("Water", 7)} \u2502 ${truncate("Action", 11)} \u2502 + out += `\u2502 ${truncate("Resource", 36)} \u2502 ${truncate("Instance", 18)} \u2502 ${truncate("Region", 14)} \u2502 ${truncate("Scope 2", 9)} \u2502 ${truncate("Scope 3", 9)} \u2502 ${truncate("Water", 7)} \u2502 ${truncate("Action", 11)} \u2502 `; - out += `\u251C${"\u2500".repeat(38)}\u253C${"\u2500".repeat(13)}\u253C${"\u2500".repeat(13)}\u253C${"\u2500".repeat(11)}\u253C${"\u2500".repeat(11)}\u253C${"\u2500".repeat(9)}\u253C${"\u2500".repeat(13)}\u2524 + out += `\u251C${"\u2500".repeat(38)}\u253C${"\u2500".repeat(20)}\u253C${"\u2500".repeat(16)}\u253C${"\u2500".repeat(11)}\u253C${"\u2500".repeat(11)}\u253C${"\u2500".repeat(9)}\u253C${"\u2500".repeat(13)}\u2524 `; const analysed = result2.resources.filter((r) => r.baseline.confidence !== "LOW_ASSUMED_DEFAULT"); const unsupportedResources = result2.resources.filter((r) => r.baseline.confidence === "LOW_ASSUMED_DEFAULT"); @@ -2999,18 +2999,18 @@ function formatTable(result2) { const scope3 = formatGrams(r.baseline.embodiedCo2eGramsPerMonth); const water = formatWater2(r.baseline.waterLitresPerMonth); const action = r.recommendation ? `\x1B[33mUPGRADE\x1B[0m` : `\x1B[32mOK\x1B[0m`; - out += `\u2502 ${truncate(r.input.resourceId, 36)} \u2502 ${truncate(r.input.instanceType, 11)} \u2502 ${truncate(r.input.region, 11)} \u2502 ${truncate(scope2, 9)} \u2502 ${truncate(scope3, 9)} \u2502 ${truncate(water, 7)} \u2502 ${truncate(action, 11)} \u2502 + out += `\u2502 ${truncate(r.input.resourceId, 36)} \u2502 ${truncate(r.input.instanceType, 18)} \u2502 ${truncate(r.input.region, 14)} \u2502 ${truncate(scope2, 9)} \u2502 ${truncate(scope3, 9)} \u2502 ${truncate(water, 7)} \u2502 ${truncate(action, 11)} \u2502 `; } for (const s of result2.skipped) { - out += `\u2502 \x1B[90m${truncate(s.resourceId, 36)}\x1B[0m \u2502 \x1B[90m${truncate("---", 11)}\x1B[0m \u2502 \x1B[90m${truncate("---", 11)}\x1B[0m \u2502 \x1B[90m${truncate("---", 9)}\x1B[0m \u2502 \x1B[90m${truncate("---", 9)}\x1B[0m \u2502 \x1B[90m${truncate("---", 7)}\x1B[0m \u2502 \x1B[33m${truncate("\u26A0 SKIPPED", 11)}\x1B[0m \u2502 + out += `\u2502 \x1B[90m${truncate(s.resourceId, 36)}\x1B[0m \u2502 \x1B[90m${truncate("---", 18)}\x1B[0m \u2502 \x1B[90m${truncate("---", 14)}\x1B[0m \u2502 \x1B[90m${truncate("---", 9)}\x1B[0m \u2502 \x1B[90m${truncate("---", 9)}\x1B[0m \u2502 \x1B[90m${truncate("---", 7)}\x1B[0m \u2502 \x1B[33m${truncate("\u26A0 SKIPPED", 11)}\x1B[0m \u2502 `; } for (const r of unsupportedResources) { - out += `\u2502 \x1B[90m${truncate(r.input.resourceId, 36)}\x1B[0m \u2502 \x1B[90m${truncate(r.input.instanceType, 11)}\x1B[0m \u2502 \x1B[90m${truncate(r.input.region, 11)}\x1B[0m \u2502 \x1B[90m${truncate("---", 9)}\x1B[0m \u2502 \x1B[90m${truncate("---", 9)}\x1B[0m \u2502 \x1B[90m${truncate("---", 7)}\x1B[0m \u2502 \x1B[33m${truncate("\u26A0 UNKNOWN", 11)}\x1B[0m \u2502 + out += `\u2502 \x1B[90m${truncate(r.input.resourceId, 36)}\x1B[0m \u2502 \x1B[90m${truncate(r.input.instanceType, 18)}\x1B[0m \u2502 \x1B[90m${truncate(r.input.region, 14)}\x1B[0m \u2502 \x1B[90m${truncate("---", 9)}\x1B[0m \u2502 \x1B[90m${truncate("---", 9)}\x1B[0m \u2502 \x1B[90m${truncate("---", 7)}\x1B[0m \u2502 \x1B[33m${truncate("\u26A0 UNKNOWN", 11)}\x1B[0m \u2502 `; } - out += `\u2514${"\u2500".repeat(38)}\u2534${"\u2500".repeat(13)}\u2534${"\u2500".repeat(13)}\u2534${"\u2500".repeat(11)}\u2534${"\u2500".repeat(11)}\u2534${"\u2500".repeat(9)}\u2534${"\u2500".repeat(13)}\u2518 + out += `\u2514${"\u2500".repeat(38)}\u2534${"\u2500".repeat(20)}\u2534${"\u2500".repeat(16)}\u2534${"\u2500".repeat(11)}\u2534${"\u2500".repeat(11)}\u2534${"\u2500".repeat(9)}\u2534${"\u2500".repeat(13)}\u2518 `; out += `Scope 2: ${formatGrams(result2.totals.currentCo2eGramsPerMonth)} | Scope 3: ${formatGrams(result2.totals.currentEmbodiedCo2eGramsPerMonth)} | Lifecycle: ${formatGrams(result2.totals.currentLifecycleCo2eGramsPerMonth)} diff --git a/formatters/table.test.ts b/formatters/table.test.ts index deded8e..6020ca9 100644 --- a/formatters/table.test.ts +++ b/formatters/table.test.ts @@ -81,6 +81,20 @@ describe('formatTable', () => { assert.ok(table.includes('SKIPPED'), 'Should show SKIPPED for skipped resources'); }); + it('shows Azure instance names without truncation (Standard_D2s_v3 fits in 18-char column)', () => { + const result = makeMockResult({ + resources: [{ + input: { resourceId: 'azurerm_linux_virtual_machine.api', instanceType: 'Standard_D2s_v3', region: 'eastus', provider: 'azure' as const }, + baseline: makeMockBaseline(), + recommendation: null, + }], + totals: makeMockTotals({ currentCo2eGramsPerMonth: 1000 }), + }); + const table = formatTable(result); + assert.ok(table.includes('Standard_D2s_v3'), 'Standard_D2s_v3 should not be truncated in the 18-char column'); + assert.ok(!table.includes('Standard_...'), 'Should not truncate Standard_ names to Standard_...'); + }); + it('shows UNKNOWN marker for LOW_ASSUMED_DEFAULT resources instead of OK/UPGRADE', () => { const result = makeMockResult({ resources: [ diff --git a/formatters/table.ts b/formatters/table.ts index 818a19e..8d600a0 100644 --- a/formatters/table.ts +++ b/formatters/table.ts @@ -19,9 +19,9 @@ export function formatTable(result: PlanAnalysisResult): string { return out + `No compatible infrastructure detected.\n`; } - out += `┌${'─'.repeat(38)}┬${'─'.repeat(13)}┬${'─'.repeat(13)}┬${'─'.repeat(11)}┬${'─'.repeat(11)}┬${'─'.repeat(9)}┬${'─'.repeat(13)}┐\n`; - out += `│ ${truncate('Resource', 36)} │ ${truncate('Instance', 11)} │ ${truncate('Region', 11)} │ ${truncate('Scope 2', 9)} │ ${truncate('Scope 3', 9)} │ ${truncate('Water', 7)} │ ${truncate('Action', 11)} │\n`; - out += `├${'─'.repeat(38)}┼${'─'.repeat(13)}┼${'─'.repeat(13)}┼${'─'.repeat(11)}┼${'─'.repeat(11)}┼${'─'.repeat(9)}┼${'─'.repeat(13)}┤\n`; + out += `┌${'─'.repeat(38)}┬${'─'.repeat(20)}┬${'─'.repeat(16)}┬${'─'.repeat(11)}┬${'─'.repeat(11)}┬${'─'.repeat(9)}┬${'─'.repeat(13)}┐\n`; + out += `│ ${truncate('Resource', 36)} │ ${truncate('Instance', 18)} │ ${truncate('Region', 14)} │ ${truncate('Scope 2', 9)} │ ${truncate('Scope 3', 9)} │ ${truncate('Water', 7)} │ ${truncate('Action', 11)} │\n`; + out += `├${'─'.repeat(38)}┼${'─'.repeat(20)}┼${'─'.repeat(16)}┼${'─'.repeat(11)}┼${'─'.repeat(11)}┼${'─'.repeat(9)}┼${'─'.repeat(13)}┤\n`; // Separate analysed resources from LOW_ASSUMED_DEFAULT (unsupported instance/region) const analysed = result.resources.filter(r => r.baseline.confidence !== 'LOW_ASSUMED_DEFAULT'); @@ -32,17 +32,17 @@ export function formatTable(result: PlanAnalysisResult): string { const scope3 = formatGrams(r.baseline.embodiedCo2eGramsPerMonth); const water = formatWater(r.baseline.waterLitresPerMonth); const action = r.recommendation ? `\x1b[33mUPGRADE\x1b[0m` : `\x1b[32mOK\x1b[0m`; - out += `│ ${truncate(r.input.resourceId, 36)} │ ${truncate(r.input.instanceType, 11)} │ ${truncate(r.input.region, 11)} │ ${truncate(scope2, 9)} │ ${truncate(scope3, 9)} │ ${truncate(water, 7)} │ ${truncate(action, 11)} │\n`; + out += `│ ${truncate(r.input.resourceId, 36)} │ ${truncate(r.input.instanceType, 18)} │ ${truncate(r.input.region, 14)} │ ${truncate(scope2, 9)} │ ${truncate(scope3, 9)} │ ${truncate(water, 7)} │ ${truncate(action, 11)} │\n`; } // Skipped: known_after_apply and other runtime-unresolvable resources for (const s of result.skipped) { - out += `│ \x1b[90m${truncate(s.resourceId, 36)}\x1b[0m │ \x1b[90m${truncate('---', 11)}\x1b[0m │ \x1b[90m${truncate('---', 11)}\x1b[0m │ \x1b[90m${truncate('---', 9)}\x1b[0m │ \x1b[90m${truncate('---', 9)}\x1b[0m │ \x1b[90m${truncate('---', 7)}\x1b[0m │ \x1b[33m${truncate('⚠ SKIPPED', 11)}\x1b[0m │\n`; + out += `│ \x1b[90m${truncate(s.resourceId, 36)}\x1b[0m │ \x1b[90m${truncate('---', 18)}\x1b[0m │ \x1b[90m${truncate('---', 14)}\x1b[0m │ \x1b[90m${truncate('---', 9)}\x1b[0m │ \x1b[90m${truncate('---', 9)}\x1b[0m │ \x1b[90m${truncate('---', 7)}\x1b[0m │ \x1b[33m${truncate('⚠ SKIPPED', 11)}\x1b[0m │\n`; } // Skipped: unsupported instance types not in the ledger for (const r of unsupportedResources) { - out += `│ \x1b[90m${truncate(r.input.resourceId, 36)}\x1b[0m │ \x1b[90m${truncate(r.input.instanceType, 11)}\x1b[0m │ \x1b[90m${truncate(r.input.region, 11)}\x1b[0m │ \x1b[90m${truncate('---', 9)}\x1b[0m │ \x1b[90m${truncate('---', 9)}\x1b[0m │ \x1b[90m${truncate('---', 7)}\x1b[0m │ \x1b[33m${truncate('⚠ UNKNOWN', 11)}\x1b[0m │\n`; + out += `│ \x1b[90m${truncate(r.input.resourceId, 36)}\x1b[0m │ \x1b[90m${truncate(r.input.instanceType, 18)}\x1b[0m │ \x1b[90m${truncate(r.input.region, 14)}\x1b[0m │ \x1b[90m${truncate('---', 9)}\x1b[0m │ \x1b[90m${truncate('---', 9)}\x1b[0m │ \x1b[90m${truncate('---', 7)}\x1b[0m │ \x1b[33m${truncate('⚠ UNKNOWN', 11)}\x1b[0m │\n`; } - out += `└${'─'.repeat(38)}┴${'─'.repeat(13)}┴${'─'.repeat(13)}┴${'─'.repeat(11)}┴${'─'.repeat(11)}┴${'─'.repeat(9)}┴${'─'.repeat(13)}┘\n\n`; + out += `└${'─'.repeat(38)}┴${'─'.repeat(20)}┴${'─'.repeat(16)}┴${'─'.repeat(11)}┴${'─'.repeat(11)}┴${'─'.repeat(9)}┴${'─'.repeat(13)}┘\n\n`; out += `Scope 2: ${formatGrams(result.totals.currentCo2eGramsPerMonth)} | Scope 3: ${formatGrams(result.totals.currentEmbodiedCo2eGramsPerMonth)} | Lifecycle: ${formatGrams(result.totals.currentLifecycleCo2eGramsPerMonth)}\n`; out += `Water: ${formatWater(result.totals.currentWaterLitresPerMonth)} | Cost: $${result.totals.currentCostUsdPerMonth.toFixed(2)}/month\n`; From 8b207c33e4c144421681347cb2f0c2b5f2a42952 Mon Sep 17 00:00:00 2001 From: omar <116458341+omrdev1@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:22:18 +0000 Subject: [PATCH 2/3] fix: add Azure E2E fixture file --- fixtures/tfplan.azure.e2e.json | 134 +++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 fixtures/tfplan.azure.e2e.json diff --git a/fixtures/tfplan.azure.e2e.json b/fixtures/tfplan.azure.e2e.json new file mode 100644 index 0000000..583ae33 --- /dev/null +++ b/fixtures/tfplan.azure.e2e.json @@ -0,0 +1,134 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "azurerm_linux_virtual_machine.api", + "mode": "managed", + "type": "azurerm_linux_virtual_machine", + "name": "api", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "admin_username": "adminuser", + "location": "eastus", + "name": "greenops-e2e-api", + "resource_group_name": "greenops-e2e-rg", + "size": "Standard_D2s_v3", + "tags": { "environment": "e2e" } + } + }, + { + "address": "azurerm_linux_virtual_machine.worker", + "mode": "managed", + "type": "azurerm_linux_virtual_machine", + "name": "worker", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "admin_username": "adminuser", + "location": "swedencentral", + "name": "greenops-e2e-worker", + "resource_group_name": "greenops-e2e-rg", + "size": "Standard_D4s_v3", + "tags": { "environment": "e2e" } + } + } + ] + } + }, + "resource_changes": [ + { + "address": "azurerm_linux_virtual_machine.api", + "mode": "managed", + "type": "azurerm_linux_virtual_machine", + "name": "api", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": ["create"], + "before": null, + "after": { + "admin_username": "adminuser", + "location": "eastus", + "name": "greenops-e2e-api", + "resource_group_name": "greenops-e2e-rg", + "size": "Standard_D2s_v3", + "tags": { "environment": "e2e" } + }, + "after_unknown": { "id": true, "private_ip_address": true, "public_ip_address": true, "virtual_machine_id": true }, + "before_sensitive": false, + "after_sensitive": { "admin_password": true } + } + }, + { + "address": "azurerm_linux_virtual_machine.worker", + "mode": "managed", + "type": "azurerm_linux_virtual_machine", + "name": "worker", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": ["create"], + "before": null, + "after": { + "admin_username": "adminuser", + "location": "swedencentral", + "name": "greenops-e2e-worker", + "resource_group_name": "greenops-e2e-rg", + "size": "Standard_D4s_v3", + "tags": { "environment": "e2e" } + }, + "after_unknown": { "id": true, "private_ip_address": true, "public_ip_address": true, "virtual_machine_id": true }, + "before_sensitive": false, + "after_sensitive": { "admin_password": true } + } + } + ], + "configuration": { + "provider_config": { + "azurerm": { + "name": "azurerm", + "full_name": "registry.terraform.io/hashicorp/azurerm", + "version_constraint": "~> 3.0", + "expressions": {} + } + }, + "root_module": { + "resources": [ + { + "address": "azurerm_linux_virtual_machine.api", + "mode": "managed", + "type": "azurerm_linux_virtual_machine", + "name": "api", + "provider_config_key": "azurerm", + "expressions": { + "admin_username": { "constant_value": "adminuser" }, + "location": { "constant_value": "eastus" }, + "name": { "constant_value": "greenops-e2e-api" }, + "resource_group_name": { "constant_value": "greenops-e2e-rg" }, + "size": { "constant_value": "Standard_D2s_v3" } + }, + "schema_version": 0 + }, + { + "address": "azurerm_linux_virtual_machine.worker", + "mode": "managed", + "type": "azurerm_linux_virtual_machine", + "name": "worker", + "provider_config_key": "azurerm", + "expressions": { + "admin_username": { "constant_value": "adminuser" }, + "location": { "constant_value": "swedencentral" }, + "name": { "constant_value": "greenops-e2e-worker" }, + "resource_group_name": { "constant_value": "greenops-e2e-rg" }, + "size": { "constant_value": "Standard_D4s_v3" } + }, + "schema_version": 0 + } + ] + } + }, + "timestamp": "2026-03-28T12:00:00Z", + "errored": false +} From d4e926787773d8e6e51a0d116797e31bb4128236 Mon Sep 17 00:00:00 2001 From: omar <116458341+omrdev1@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:22:35 +0000 Subject: [PATCH 3/3] fix: add GCP E2E fixture file --- fixtures/tfplan.gcp.e2e.json | 129 +++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 fixtures/tfplan.gcp.e2e.json diff --git a/fixtures/tfplan.gcp.e2e.json b/fixtures/tfplan.gcp.e2e.json new file mode 100644 index 0000000..71316ec --- /dev/null +++ b/fixtures/tfplan.gcp.e2e.json @@ -0,0 +1,129 @@ +{ + "format_version": "1.2", + "terraform_version": "1.7.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "google_compute_instance.api", + "mode": "managed", + "type": "google_compute_instance", + "name": "api", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 6, + "values": { + "name": "greenops-e2e-api", + "machine_type": "n2-standard-2", + "zone": "us-central1-a", + "tags": ["e2e"], + "labels": { "environment": "e2e" } + } + }, + { + "address": "google_compute_instance.worker", + "mode": "managed", + "type": "google_compute_instance", + "name": "worker", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 6, + "values": { + "name": "greenops-e2e-worker", + "machine_type": "t2a-standard-2", + "zone": "europe-north1-a", + "tags": ["e2e"], + "labels": { "environment": "e2e" } + } + } + ] + } + }, + "resource_changes": [ + { + "address": "google_compute_instance.api", + "mode": "managed", + "type": "google_compute_instance", + "name": "api", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": ["create"], + "before": null, + "after": { + "name": "greenops-e2e-api", + "machine_type": "n2-standard-2", + "zone": "us-central1-a", + "tags": ["e2e"], + "labels": { "environment": "e2e" } + }, + "after_unknown": { "id": true, "instance_id": true, "network_interface": true, "self_link": true }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "google_compute_instance.worker", + "mode": "managed", + "type": "google_compute_instance", + "name": "worker", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": ["create"], + "before": null, + "after": { + "name": "greenops-e2e-worker", + "machine_type": "t2a-standard-2", + "zone": "europe-north1-a", + "tags": ["e2e"], + "labels": { "environment": "e2e" } + }, + "after_unknown": { "id": true, "instance_id": true, "network_interface": true, "self_link": true }, + "before_sensitive": false, + "after_sensitive": {} + } + } + ], + "configuration": { + "provider_config": { + "google": { + "name": "google", + "full_name": "registry.terraform.io/hashicorp/google", + "version_constraint": "~> 5.0", + "expressions": { + "project": { "constant_value": "greenops-e2e-project" }, + "region": { "constant_value": "us-central1" } + } + } + }, + "root_module": { + "resources": [ + { + "address": "google_compute_instance.api", + "mode": "managed", + "type": "google_compute_instance", + "name": "api", + "provider_config_key": "google", + "expressions": { + "name": { "constant_value": "greenops-e2e-api" }, + "machine_type": { "constant_value": "n2-standard-2" }, + "zone": { "constant_value": "us-central1-a" } + }, + "schema_version": 6 + }, + { + "address": "google_compute_instance.worker", + "mode": "managed", + "type": "google_compute_instance", + "name": "worker", + "provider_config_key": "google", + "expressions": { + "name": { "constant_value": "greenops-e2e-worker" }, + "machine_type": { "constant_value": "t2a-standard-2" }, + "zone": { "constant_value": "europe-north1-a" } + }, + "schema_version": 6 + } + ] + } + }, + "timestamp": "2026-03-28T12:00:00Z", + "errored": false +}