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
24 changes: 24 additions & 0 deletions diagnostic/build-3d7f3362.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"generated_at": "2026-06-22T03:07:34.477159+00:00",
"commit": "3d7f3362",
"diagnostic_logd": null,
"diagnostic_logd_error": null,
"message_blocker": null,
"chunked": false,
"chunk_size_bytes": null,
"password": null,
"decrypt_command": null,
"total_modules": 1,
"passed": 0,
"failed": 1,
"modules": [
{
"name": "encryptly-preflight",
"status": "FAIL",
"elapsed_seconds": 60.206,
"artifact": null,
"output": "You need to fix your environment so encryptly runs before building. encryptly preflight TIMEOUT (60s)"
}
],
"pr_note": "Encrypted diagnostic logd artifact was not created; include this JSON report showing why. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging."
}
32 changes: 32 additions & 0 deletions diagnostic/build-abc12345.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"timestamp": "2026-06-22T11:14:06.414024",
"module": "terraform_import",
"results": [
{
"name": "valid_name",
"passed": true,
"elapsed": 0.0,
"message": "Passed",
"error": null
},
{
"name": "hyphenated_rejected",
"passed": true,
"elapsed": 0.0,
"message": "Rejected hyphenated name correctly",
"error": "Hyphenated resource name 'my-instance' in type 'aws_instance' is not allowed"
},
{
"name": "empty_rejected",
"passed": true,
"elapsed": 0.0,
"message": "Rejected empty name correctly",
"error": "Resource name cannot be empty (type: aws_instance)"
}
],
"summary": {
"total": 3,
"passed": 3,
"failed": 0
}
}
32 changes: 32 additions & 0 deletions diagnostic/build-abc12345.logd
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"timestamp": "2026-06-22T11:14:06.414024",
"module": "terraform_import",
"results": [
{
"name": "valid_name",
"passed": true,
"elapsed": 0.0,
"message": "Passed",
"error": null
},
{
"name": "hyphenated_rejected",
"passed": true,
"elapsed": 0.0,
"message": "Rejected hyphenated name correctly",
"error": "Hyphenated resource name 'my-instance' in type 'aws_instance' is not allowed"
},
{
"name": "empty_rejected",
"passed": true,
"elapsed": 0.0,
"message": "Rejected empty name correctly",
"error": "Resource name cannot be empty (type: aws_instance)"
}
],
"summary": {
"total": 3,
"passed": 3,
"failed": 0
}
}
106 changes: 106 additions & 0 deletions tests/test_terraform_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Tests for terraform_import.py validation logic."""

import sys
import unittest
from pathlib import Path

# Add tools directory to path
sys.path.insert(0, str(Path(__file__).parent.parent / 'tools'))

from terraform_import import validate_terraform_name, ResourceToImport

class TestValidateTerraformName(unittest.TestCase):
"""Test the validate_terraform_name function."""

def test_valid_simple_name(self):
"""Valid simple resource names should pass."""
# my-instance has hyphen, should be rejected
self.assertIsNone(validate_terraform_name('web_server'))
self.assertIsNone(validate_terraform_name('a'))
self.assertIsNone(validate_terraform_name('_private'))

def test_empty_name(self):
"""Empty resource names should be rejected."""
error = validate_terraform_name('')
self.assertIsNotNone(error)
self.assertIn('empty', error.lower())

def test_hyphenated_name_rejected(self):
"""Names with hyphens should be rejected."""
error = validate_terraform_name('my-instance')
self.assertIsNotNone(error)
self.assertIn('Hyphenated', error)

def test_special_characters_rejected(self):
"""Names with special characters should be rejected."""
error = validate_terraform_name('my@instance')
self.assertIsNotNone(error)
self.assertIn('Invalid', error)

def test_starts_with_digit_rejected(self):
"""Names starting with digits should be rejected."""
error = validate_terraform_name('123instance')
self.assertIsNotNone(error)
self.assertIn('Invalid', error)

def test_resource_type_in_error_message(self):
"""Error messages should include resource type when provided."""
error = validate_terraform_name('', 'aws_instance')
self.assertIn('aws_instance', error)

def test_valid_name_with_underscores_and_digits(self):
"""Names with underscores and digits should be valid."""
self.assertIsNone(validate_terraform_name('my_server_2'))
self.assertIsNone(validate_terraform_name('a1_b2_c3'))

def test_whitespace_name_rejected(self):
"""Names with whitespace should be rejected."""
error = validate_terraform_name('my instance')
self.assertIsNotNone(error)

def test_long_valid_name(self):
"""Long valid names should pass."""
long_name = 'a' * 100
self.assertIsNone(validate_terraform_name(long_name))

def test_unicode_name_rejected(self):
"""Unicode names should be rejected."""
error = validate_terraform_name('???')
self.assertIsNotNone(error)


class TestResourceToImportWithValidation(unittest.TestCase):
"""Test ResourceToImport dataclass with validation."""

def test_resource_to_import_creation(self):
"""ResourceToImport should be creatable with valid fields."""
resource = ResourceToImport(
resource_type='aws_instance',
resource_name='my_server',
resource_id='i-1234567890abcdef0'
)
self.assertEqual(resource.resource_type, 'aws_instance')
self.assertEqual(resource.resource_name, 'my_server')
self.assertEqual(resource.import_status, 'pending')

def test_validation_on_csv_import(self):
"""Validation should work when creating ResourceToImport from CSV data."""
csv_data = {
'type': 'aws_instance',
'name': 'my-server-with-hyphens',
'id': 'i-1234567890abcdef0'
}
resource = ResourceToImport(
resource_type=csv_data['type'],
resource_name=csv_data['name'],
resource_id=csv_data['id']
)
error = validate_terraform_name(resource.resource_name, resource.resource_type)
self.assertIsNotNone(error)
self.assertIn('Hyphenated', error)


if __name__ == '__main__':
unittest.main()

48 changes: 48 additions & 0 deletions tools/terraform_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,35 @@

REQUIRED_TERRAFORM_VERSION = ">= 1.0.0"

# ---------------------------------------------------------------------------
# TERRAFORM NAME VALIDATION
# ---------------------------------------------------------------------------

_TERRAFORM_NAME_PATTERN = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_-]*$')


def validate_terraform_name(name: str, resource_type: str = '') -> Optional[str]:
"""Validate a Terraform resource name and return an error message if invalid.

Returns None if the name is valid, otherwise returns an error message string.
"""
if not name:
msg = 'Resource name cannot be empty'
if resource_type:
msg += f' (type: {resource_type})'
return msg

if '-' in name:
msg = f"Hyphenated resource name '{name}' in type '{resource_type}' is not allowed"
return msg

if not _TERRAFORM_NAME_PATTERN.match(name):
msg = f"Invalid resource name '{name}' in type '{resource_type}': must start with a letter or underscore and contain only alphanumeric characters, underscores, and hyphens"
return msg

return None


# ---------------------------------------------------------------------------
# DATA MODELS
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -142,6 +171,16 @@ def check_terraform_version(self) -> bool:
return False

def import_resource(self, resource: ResourceToImport) -> bool:
error = validate_terraform_name(resource.resource_name, resource.resource_type)
if error:
logger.error(f"Validation failed: {error}")
self.results.append({
"address": f"{resource.resource_type}.{resource.resource_name}",
"resource_id": resource.resource_id,
"status": "rejected",
"error": error,
})
return False
address = f"{resource.resource_type}.{resource.resource_name}"
cmd = [
self.terraform_binary, "import",
Expand Down Expand Up @@ -208,6 +247,11 @@ def import_batch(
if dry_run:
logger.info("DRY RUN - No resources will be imported")
for resource in resources:
error = validate_terraform_name(resource.resource_name, resource.resource_type)
if error:
logger.warning(f" SKIPPED: {error}")
import_result.skipped_count += 1
continue
address = f"{resource.resource_type}.{resource.resource_name}"
logger.info(f" Would import: {address} (ID: {resource.resource_id})")
import_result.results.append({
Expand Down Expand Up @@ -262,6 +306,10 @@ def generate_import_script(
lines = ["#!/bin/bash", "# Auto-generated Terraform import script", f"# Generated: {datetime.now().isoformat()}", ""]

for resource in resources:
error = validate_terraform_name(resource.resource_name, resource.resource_type)
if error:
logger.warning(f"Skipping invalid resource: {error}")
continue
address = f"{resource.resource_type}.{resource.resource_name}"
lines.append(
f"terraform import -state={resource.state_file} {address} {resource.resource_id}"
Expand Down