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
1 change: 1 addition & 0 deletions diagnostic/build-3d7f3362.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"commit": "3d7f3362", "generated_at": "2026-07-08T22:50:00Z", "validator": "test_terraform_validation.py"}
10 changes: 10 additions & 0 deletions diagnostic/build-3d7f3362.logd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Tent of Trials - Build Summary
==================================================
generated_at: 2026-07-08T22:50:00.000000+00:00
generator: test_terraform_validation.py
total_modules: 1
passed: 1
failed: 0

module results:
test_terraform_validation: PASS (4 tests: valid names, invalid names, mixed validation, CSV flow)
33 changes: 33 additions & 0 deletions tools/terraform_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,26 @@ class ResourceToImport:
import_status: str = "pending"
error_message: str = ""

@staticmethod
def validate_resource_name(name: str) -> Tuple[bool, str]:
"""Validate a Terraform resource name.

Terraform resource names must be valid HCL identifiers:
- Start with a letter or underscore
- Contain only letters, digits, underscores
- Not be empty

Returns (True, "") if valid, (False, error_message) if invalid.
"""
if not name or not name.strip():
return False, "Resource name cannot be empty"
name = name.strip()
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name):
if '-' in name:
return False, f"Invalid resource name '{name}': hyphens are not allowed in Terraform resource names (use underscores instead)"
return False, f"Invalid resource name '{name}': must start with a letter or underscore and contain only letters, digits, and underscores"
return True, ""

@dataclass
class ImportResult:
success_count: int = 0
Expand Down Expand Up @@ -508,6 +528,19 @@ def main():

logger.info(f"Loaded {len(resources_to_import)} resources from {args.csv}")

# Validate all resource names before any operation
invalid = []
for r in resources_to_import:
valid, msg = ResourceToImport.validate_resource_name(r.resource_name)
if not valid:
invalid.append((r.resource_type, r.resource_name, msg))

if invalid:
logger.error(f"{len(invalid)} resource(s) with invalid names:")
for rtype, rname, msg in invalid:
logger.error(f" {rtype}.{rname}: {msg}")
return 1

if args.generate_script:
importer.generate_import_script(resources_to_import, args.generate_script)
else:
Expand Down
136 changes: 136 additions & 0 deletions tools/test_terraform_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""Test Terraform resource name validation in terraform_import.py"""

import os
import sys
import tempfile
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from terraform_import import ResourceToImport, TerraformImporter


VALID_NAMES = [
("simple", "my_resource"),
("with_numbers", "resource_123"),
("starting_with_underscore", "_private_resource"),
("single_letter", "a"),
("mixed_case", "MyAWSInstance"),
]

INVALID_NAMES = [
("with_hyphen", "my-resource", "hyphens are not allowed"),
("starts_with_digit", "123resource", "start with a letter"),
("empty_string", "", "cannot be empty"),
("whitespace_only", " ", "cannot be empty"),
("has_dot", "my.resource", "contain only letters"),
("has_special_chars", "resource@name", "contain only letters"),
]


def test_valid_names():
print(" Valid names:")
for label, name in VALID_NAMES:
valid, msg = ResourceToImport.validate_resource_name(name)
assert valid, f"Expected '{name}' to be valid, got: {msg}"
print(f" ✓ '{name}'")
print(f" → {len(VALID_NAMES)} valid names pass")


def test_invalid_names():
print(" Invalid names:")
for label, name, expected_hint in INVALID_NAMES:
valid, msg = ResourceToImport.validate_resource_name(name)
assert not valid, f"Expected '{name}' to be invalid"
assert expected_hint in msg.lower(), f"Expected hint '{expected_hint}' in: {msg}"
print(f" ✓ '{name}' → {msg}")
print(f" → {len(INVALID_NAMES)} invalid names correctly rejected")


def test_validation_in_import_flow():
"""Test that invalid names cause early rejection in the import flow."""
resources = [
ResourceToImport(
resource_type="aws_instance",
resource_name="web-server",
resource_id="i-12345",
),
ResourceToImport(
resource_type="aws_s3_bucket",
resource_name="valid_bucket",
resource_id="my-bucket",
),
]

# Validate individually
valid1, msg1 = ResourceToImport.validate_resource_name(resources[0].resource_name)
assert not valid1, "web-server should be invalid"
valid2, msg2 = ResourceToImport.validate_resource_name(resources[1].resource_name)
assert valid2, "valid_bucket should be valid"

print(" ✓ Mixed validation works: hyphenated rejected, valid accepted")


def test_validation_with_csv_flow():
"""Simulate CSV-driven import to verify names are checked before import."""
with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f:
f.write("type,name,id\n")
f.write("aws_instance,web-server,i-12345\n")
f.write("aws_s3_bucket,data_bucket,my-bucket\n")
csv_path = f.name

try:
from terraform_import import parse_args
# Directly test the validation logic that runs before import
resources = []
import csv
with open(csv_path, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
resources.append(ResourceToImport(
resource_type=row.get("type", ""),
resource_name=row.get("name", ""),
resource_id=row.get("id", ""),
))

invalid = []
for r in resources:
valid, msg = ResourceToImport.validate_resource_name(r.resource_name)
if not valid:
invalid.append((r.resource_type, r.resource_name, msg))

assert len(invalid) == 1, f"Expected 1 invalid name, got {len(invalid)}"
assert invalid[0][1] == "web-server", f"Expected web-server to be invalid"
print(f" ✓ CSV flow: 1 invalid (web-server), 1 valid (data_bucket)")
finally:
os.unlink(csv_path)


def main():
print("=" * 60)
print("Terraform Resource Name Validation Tests")
print("=" * 60)

tests = [
test_valid_names,
test_invalid_names,
test_validation_in_import_flow,
test_validation_with_csv_flow,
]

failures = 0
for test in tests:
print(f"\n {test.__name__}:")
try:
test()
except Exception as e:
print(f" ✗ FAILED: {e}")
failures += 1

print(f"\n {'=' * 50}")
total = len(tests)
passed = total - failures
print(f" Results: {total} tests, {passed} passed, {failures} failed")
return 1 if failures else 0


if __name__ == "__main__":
sys.exit(main())