Files
SnarfCode/src/iac_reverse/generator/provider_block.py
2026-05-22 00:19:30 -04:00

198 lines
6.9 KiB
Python

"""Provider block generator for Terraform HCL output.
Generates a providers.tf file containing:
- A terraform { required_providers { ... } } block listing all providers used
- Individual provider configuration blocks with platform-specific settings
(endpoints, certificates, credentials) for each distinct provider type.
"""
from __future__ import annotations
from iac_reverse.models import ProviderType, ScanProfile, GeneratedFile
# ---------------------------------------------------------------------------
# Provider metadata: maps ProviderType to Terraform provider details
# ---------------------------------------------------------------------------
# Each entry: (terraform_provider_name, source, version_constraint)
_PROVIDER_METADATA: dict[ProviderType, tuple[str, str, str]] = {
ProviderType.KUBERNETES: (
"kubernetes",
"hashicorp/kubernetes",
"~> 2.0",
),
ProviderType.DOCKER_SWARM: (
"docker",
"kreuzwerker/docker",
"~> 3.0",
),
ProviderType.SYNOLOGY: (
"synology",
"synology-community/synology",
"~> 0.2",
),
ProviderType.HARVESTER: (
"harvester",
"harvester/harvester",
"~> 0.6",
),
ProviderType.BARE_METAL: (
"redfish",
"dell/redfish",
"~> 1.0",
),
ProviderType.WINDOWS: (
"windows",
"hashicorp/windows",
"~> 0.1",
),
}
def _generate_provider_config(
provider_type: ProviderType, profile: ScanProfile
) -> str:
"""Generate the provider configuration block for a given provider type.
Uses credentials and endpoints from the ScanProfile to populate
platform-specific configuration attributes.
"""
tf_name = _PROVIDER_METADATA[provider_type][0]
lines: list[str] = []
lines.append(f'provider "{tf_name}" {{')
if provider_type == ProviderType.KUBERNETES:
host = profile.credentials.get("host", "")
cluster_ca = profile.credentials.get("cluster_ca_certificate", "")
token = profile.credentials.get("token", "")
lines.append(f' host = "{host}"')
lines.append(f' cluster_ca_certificate = "{cluster_ca}"')
lines.append(f' token = "{token}"')
elif provider_type == ProviderType.DOCKER_SWARM:
host = profile.credentials.get("host", "")
cert_path = profile.credentials.get("cert_path", "")
lines.append(f' host = "{host}"')
lines.append(f' cert_path = "{cert_path}"')
elif provider_type == ProviderType.SYNOLOGY:
url = profile.credentials.get("url", "")
username = profile.credentials.get("username", "")
password = profile.credentials.get("password", "")
lines.append(f' url = "{url}"')
lines.append(f' username = "{username}"')
lines.append(f' password = "{password}"')
elif provider_type == ProviderType.HARVESTER:
kubeconfig = profile.credentials.get("kubeconfig", "")
lines.append(f' kubeconfig = "{kubeconfig}"')
elif provider_type == ProviderType.BARE_METAL:
endpoint = profile.credentials.get("endpoint", "")
username = profile.credentials.get("username", "")
password = profile.credentials.get("password", "")
lines.append(f' endpoint = "{endpoint}"')
lines.append(f' username = "{username}"')
lines.append(f' password = "{password}"')
elif provider_type == ProviderType.WINDOWS:
host = profile.credentials.get("host", "")
username = profile.credentials.get("username", "")
password = profile.credentials.get("password", "")
lines.append(f' host = "{host}"')
lines.append(f' username = "{username}"')
lines.append(f' password = "{password}"')
lines.append("")
lines.append(" winrm {")
winrm_port = profile.credentials.get("winrm_port", "5985")
winrm_use_ssl = profile.credentials.get("winrm_use_ssl", "false")
lines.append(f" port = {winrm_port}")
lines.append(f" use_ssl = {winrm_use_ssl}")
lines.append(" }")
lines.append("}")
return "\n".join(lines)
def _generate_required_providers_block(
provider_types: set[ProviderType],
) -> str:
"""Generate the terraform { required_providers { ... } } block."""
lines: list[str] = []
lines.append("terraform {")
lines.append(" required_providers {")
for provider_type in sorted(provider_types, key=lambda p: p.value):
tf_name, source, version = _PROVIDER_METADATA[provider_type]
lines.append(f" {tf_name} = {{")
lines.append(f' source = "{source}"')
lines.append(f' version = "{version}"')
lines.append(" }")
lines.append(" }")
lines.append("}")
return "\n".join(lines)
# ---------------------------------------------------------------------------
# ProviderBlockGenerator
# ---------------------------------------------------------------------------
class ProviderBlockGenerator:
"""Generates Terraform provider configuration blocks.
Accepts a list of ScanProfiles and a set of ProviderTypes used in the
generated code, and produces a providers.tf file containing:
- A terraform { required_providers { ... } } block
- Individual provider blocks with platform-specific configuration
"""
def generate(
self,
profiles: list[ScanProfile],
provider_types: set[ProviderType],
) -> GeneratedFile:
"""Generate the providers.tf file content.
Args:
profiles: List of ScanProfiles providing credentials/endpoints.
provider_types: Set of distinct ProviderTypes used in the code.
Returns:
A GeneratedFile with filename "providers.tf" and the HCL content.
"""
# Build a map from ProviderType -> first matching profile
profile_map: dict[ProviderType, ScanProfile] = {}
for profile in profiles:
if profile.provider not in profile_map:
profile_map[profile.provider] = profile
sections: list[str] = []
# 1. required_providers block
sections.append(_generate_required_providers_block(provider_types))
# 2. Individual provider configuration blocks
for provider_type in sorted(provider_types, key=lambda p: p.value):
profile = profile_map.get(provider_type)
if profile is not None:
sections.append(
_generate_provider_config(provider_type, profile)
)
else:
# Generate a placeholder block if no profile matches
tf_name = _PROVIDER_METADATA[provider_type][0]
sections.append(
f'provider "{tf_name}" {{\n # No profile provided\n}}'
)
content = "\n\n".join(sections) + "\n"
return GeneratedFile(
filename="providers.tf",
content=content,
resource_count=0,
)