Source code for cloudflare_saas.terraform_deployer
"""Terraform deployment automation."""
import json
import shutil
from pathlib import Path
from typing import Optional, Dict, Any
from python_terraform import Terraform, IsFlagged
from .config import Config
from .exceptions import DeploymentError
[docs]
class TerraformDeployer:
"""Automate Terraform deployment for Workers and R2."""
[docs]
def __init__(self, config: Config, working_dir: str = "./terraform"):
self.config = config
self.working_dir = Path(working_dir)
self.tf = Terraform(working_dir=str(self.working_dir))
[docs]
def generate_terraform_config(
self,
worker_script_path: Path,
) -> None:
"""Generate Terraform configuration files."""
self.working_dir.mkdir(parents=True, exist_ok=True)
# Main configuration
main_tf = f"""
terraform {{
required_providers {{
cloudflare = {{
source = "cloudflare/cloudflare"
version = "~> 4"
}}
}}
}}
provider "cloudflare" {{
api_token = var.cloudflare_api_token
}}
variable "cloudflare_api_token" {{
type = string
sensitive = true
}}
variable "cloudflare_account_id" {{
type = string
}}
variable "cloudflare_zone_id" {{
type = string
}}
variable "r2_bucket_name" {{
type = string
}}
variable "worker_script_name" {{
type = string
}}
variable "platform_domain" {{
type = string
}}
variable "internal_api_key" {{
type = string
sensitive = true
}}
# R2 Bucket
resource "cloudflare_r2_bucket" "sites" {{
account_id = var.cloudflare_account_id
name = var.r2_bucket_name
location = "EEUR"
}}
# Worker Script
resource "cloudflare_worker_script" "site_router" {{
account_id = var.cloudflare_account_id
name = var.worker_script_name
content = file("${{path.module}}/worker.js")
r2_bucket_binding {{
name = "MY_BUCKET"
bucket_name = cloudflare_r2_bucket.sites.name
}}
secret_text_binding {{
name = "INTERNAL_API_KEY"
text = var.internal_api_key
}}
plain_text_binding {{
name = "PLATFORM_DOMAIN"
text = var.platform_domain
}}
}}
# Worker Route
resource "cloudflare_worker_route" "wildcard_route" {{
zone_id = var.cloudflare_zone_id
pattern = "*.{self.config.platform_domain}/*"
script_name = cloudflare_worker_script.site_router.name
}}
# Outputs
output "r2_bucket_name" {{
value = cloudflare_r2_bucket.sites.name
}}
output "worker_script_name" {{
value = cloudflare_worker_script.site_router.name
}}
"""
(self.working_dir / "main.tf").write_text(main_tf)
# Copy worker script
if worker_script_path.exists():
shutil.copy(worker_script_path, self.working_dir / "worker.js")
# Create tfvars
tfvars = {
"cloudflare_api_token": self.config.cloudflare_api_token,
"cloudflare_account_id": self.config.cloudflare_account_id,
"cloudflare_zone_id": self.config.cloudflare_zone_id,
"r2_bucket_name": self.config.r2_bucket_name,
"worker_script_name": self.config.worker_script_name,
"platform_domain": self.config.platform_domain,
"internal_api_key": self.config.internal_api_key or "default-key",
}
(self.working_dir / "terraform.tfvars.json").write_text(
json.dumps(tfvars, indent=2)
)
[docs]
async def deploy(
self,
worker_script_path: Path,
auto_approve: bool = False,
) -> Dict[str, Any]:
"""Deploy infrastructure with Terraform."""
try:
# Generate config
self.generate_terraform_config(worker_script_path)
# Initialize
return_code, stdout, stderr = self.tf.init(capture_output=True)
if return_code != 0:
raise DeploymentError(f"Terraform init failed: {stderr}")
# Plan
return_code, stdout, stderr = self.tf.plan(
capture_output=True,
out="tfplan",
)
if return_code != 0:
raise DeploymentError(f"Terraform plan failed: {stderr}")
# Apply
if auto_approve:
return_code, stdout, stderr = self.tf.apply(
"tfplan",
capture_output=True,
skip_plan=True,
)
if return_code != 0:
raise DeploymentError(f"Terraform apply failed: {stderr}")
# Get outputs
outputs = self.tf.output(json=IsFlagged)
return {
"success": True,
"outputs": outputs,
"stdout": stdout,
}
except Exception as e:
raise DeploymentError(f"Terraform deployment failed: {e}")
[docs]
async def destroy(self, auto_approve: bool = False) -> Dict[str, Any]:
"""Destroy infrastructure."""
try:
if auto_approve:
return_code, stdout, stderr = self.tf.destroy(
capture_output=True,
auto_approve=True,
)
if return_code != 0:
raise DeploymentError(f"Terraform destroy failed: {stderr}")
return {
"success": True,
"stdout": stdout,
}
except Exception as e:
raise DeploymentError(f"Terraform destroy failed: {e}")