【AI相談ログ】自動生成するTerraformコードの作成方法探求

Prompt

Generate Terraform code from the following project information, coding guidelines, and module specification.

First, here is the project information.

# Project Information

1. **Project Overview**
   - The project name is `practitioner`.
   - The purpose of the project is to explore methods for automatically generating Terraform code using AI.

2. **Service and Environment Requirements**
   - AWS will be used as the cloud service.
   - The environments used will be development, staging, and production. It is also acceptable to create a temporary personal testing environment for experimental purposes.

3. **Infrastructure Design Principles**
   - The highest priority is ensuring an appropriate level of security and reliability for service operations.
   - Next, operational excellence and performance efficiency are emphasized.
   - Cost optimization will also be implemented, but not at the expense of compromising security or reliability. Operational excellence and performance efficiency are also effective means of cost optimization and should be treated as such.

Next, here is the coding guideline.

# Terraform Coding Guidelines

## 1. General Structure
- **Directory Layout**: Organize your Terraform code into logical directories for reusability and clarity. Follow the structure below to separate modules from environment-specific configurations:
- **Modules Directory (`/modules`)**: Use a `modules/` directory to store reusable components like compute, database, and network configurations. Each module should have its own directory and at least one `main.tf`, `variables.tf`, `output.tf`, `README.md`, `specification.md`, file.
- **Environment Directories**: Create separate directories for each environment (e.g., `development/`, `staging/`, `production/`). Each environment directory should contain:
    - **`backend.tf`**: Define backend configuration for storing Terraform state remotely.
    - **`main.tf`**: Specify the infrastructure for the environment, leveraging the reusable modules defined in `/modules`.
    - **`variables.tf`**: Define the variables that are specific to the environment.
    - **`specification.md`**: 
    - **`README.md`**: Provide a `README.md` file for each module and environment directory detailing its summary, usage.

- **Naming**: Use snake_case for both directory names and file names.

## 2. Naming Conventions
- **Principal**: Use full formal names instead of abbreviations. However, in cases where the abbreviation is a proper noun, such as an `id`, this restriction does not apply.
- **Resources**: Use a descriptive and standardized naming convention for resources. For example, `resource "aws_instance" "web_server" {}`. Do not use resource names that are redundant with the resource type, such as resource `"aws_instance" "ec2_instance" {}.`. Resource names must not include resource types, good example is `"aws_vpc" "practitioner"`, bad is `"aws_vpc" "practitioner_vpc"`. remove `_vpc`.
- **Variables**: Use snake_case for variable names. For example, `variable "instance_type" {}`.
- **Outputs**: Use a consistent naming convention for outputs. For example, `output "vpc_id" {}`.

## 3. Code Formatting
- **Formatting**: Use `terraform fmt` to automatically format your code to the standard style.

## 4. Modules
- **Reusability**: Use modules to encapsulate reusable code. Each module should have a clear and well-documented purpose.
- **Input Variables**: Minimize hard-coded values. Use input variables to increase flexibility.
- **Outputs**: Define outputs for important values that other modules or configurations might need.

## 5. Variables and Outputs
- **Default Values**: Avoid default values.
- **Descriptions**: Provide meaningful descriptions for variables and outputs to improve code readability.
- **Sensitive Values**: Mark sensitive variables as `sensitive = true` to avoid accidental exposure of secrets.

## 6. State Management
- **Remote State**: Store Terraform state remotely using an `S3` backend.
- **State Files**: Never commit `.tfstate` files to version control.

## 7. Resource Tagging
- **Standard Tags**: Use a consistent tagging strategy across all resources, such as `Name`, `Environment`, and `Project`. Tags help in managing and identifying resources.
    - **Environment**: Add an `Environment` tag (e.g., `development`, `staging`, `production`) to distinguish between different deployments. However, personal testing environments should be named `experimental-[personal name]`. (e.g., `experimental-nakamura`)
    - **Project**: Specify the default project name.
    - **Name**: Specify as `[Environment]-[Project]([-Additional Information(Optional)]`. (e.g., `production-awesomeservice`, `staging-awesomeservice-api`)
- **Default Tags**: Set tags in common settings, `Environment`, `Project`, `Name`

## 8. Best Practices
- **Idempotency**: Write Terraform code that is idempotent. Re-running `terraform apply` should not introduce unintended changes.
- **Avoid Hard-Coding**: Use variables for values that are likely to change between environments or over time, such as AMI IDs, instance sizes, and VPC IDs.
- **Minimize Provider Configuration**: Use a centralized provider configuration whenever possible. Define the provider block at a common location rather than repeating it in multiple modules.
  
## 9. Error Handling and Validation
- **Validation Rules**: Use `validation` blocks, where necessary, to ensure variables meet specific requirements (e.g., `min_length`, `regex`).
- **Preconditions and Postconditions**: Use `precondition` and `postcondition` where applicable to maintain resource integrity.

## 10. Comments and Documentation
- **Commenting**: Add comments to describe non-intuitive code or decisions. However, avoid excessive comments that describe self-explanatory code.

## 11. Versioning
- **Provider Versions**: Specify provider versions explicitly to ensure consistent behavior across environments. Use version constraints like `= 3.0` to specify a strict version.
- **Terraform Version**: Specify the required Terraform version using `terraform { required_version = "= 1.9.6" }` to enforce a strict version.

## 12. Security
- **Secrets Management**: Avoid storing secrets in plaintext within Terraform code. Use secret management tools such as AWS Secrets Manager or environment variables.
- **IAM Policies**: Follow the principle of least privilege when defining IAM roles and policies. Only grant permissions that are necessary.

## 13. Testing and Validation
- **Linting**: Use tools like `tflint` to check for errors and enforce standards.
- **Validation**: Run `terraform validate` before committing code to catch syntax errors.
- **Plan Review**: Always review the output of `terraform plan` to verify changes before applying them.


Lastly, here is the module specification.

# Networking Module Specification

## Summary
This module creates an AWS VPC with IPv6-only networking, consisting of public and private subnets across all available Availability Zones in the specified region.

## Features
- The VPC is created with IPv6-only networking, without IPv4 support.
- Public subnets are provided for external access, while private subnets are used for internal services.
- Subnets are created in all available Availability Zones (AZs) in the specified region, with the subnet in the alphabetically first AZ designated as the main AZ.

## Resources

### aws_vpc
- **Description**: Creates a VPC with IPv6 CIDR allocation.
- **Configuration**:
  - **IPv6 CIDR Block**: Automatically allocated by AWS.
  - **Enable DNS Support**: Enabled.
  - **Enable DNS Hostnames**: Enabled.

### aws_subnet
- **Public Subnets**:
  - **Description**: Provides public subnets in all available AZs for resources that require internet access.
  - **Configuration**:
    - **CIDR Block (IPv6)**: `/64` range for each public subnet.
    - **Map Public IP on Launch**: Enabled.

- **Private Subnets**:
  - **Description**: Provides private subnets in all available AZs for internal resources.
  - **Configuration**:
    - **CIDR Block (IPv6)**: `/64` range for each private subnet.
    - **Map Public IP on Launch**: Disabled.

### aws_egress_only_internet_gateway
- **Description**: Provides outbound-only IPv6 access for resources in private subnets.
- **Configuration**:
  - Attach to the VPC to ensure secure outbound-only communication.

### aws_route_table
- **Public Route Table**:
  - **Description**: Routes IPv6 traffic from public subnets to the Internet Gateway.
  - **Configuration**:
    - **Routes**:
      - Route IPv6 traffic (`::/0`) via the Internet Gateway.

- **Private Route Table**:
  - **Description**: Routes outbound IPv6 traffic from private subnets through the Egress-Only Internet Gateway.
  - **Configuration**:
    - **Routes**:
      - Route IPv6 traffic (`::/0`) via the Egress-Only Internet Gateway.

## Remarks
The design intent of this module is to provide a fully IPv6-capable network in AWS with clear segregation between public and private resources. Using all available AZs ensures high availability and redundancy.

## Prohibit
- **No IPv4 Addressing**: Do not allocate or configure any IPv4 CIDR blocks within the VPC or subnets.
- **No NAT Gateways**: NAT Gateways should not be used, as IPv4 is not supported.
- **No Mixing of Resource Types**: Each resource must be defined individually; do not combine or abstract configurations.
- **No Individual Tagging**: Tags should be managed centrally and not set on individual resources.

The output must be generated according to the following template, without including any explanations or other information.


main.tf

[Write terraform code]

variables.tf

[Write terraform code]

outputs.tf

[Write terraform code]

Response


main.tf

terraform {
  required_version = "= 1.9.6"

  backend "s3" {
    bucket = "<add-your-bucket-name>"
    key    = "global/s3/terraform.tfstate"
    region = "<add-your-region>"
  }
}

provider "aws" {
  region = var.region
}

module "networking" {
  source      = "../modules/networking"
  environment = var.environment
  region      = var.region
}

resource "aws_vpc" "practitioner" {
  assign_generated_ipv6_cidr_block = true
  enable_dns_support               = true
  enable_dns_hostnames             = true

  tags = merge(
    var.default_tags,
    {
      Name        = "${var.environment}-practitioner"
    }
  )
}

resource "aws_subnet" "public" {
  count                   = length(data.aws_availability_zones.available.names)
  vpc_id                  = aws_vpc.practitioner.id
  ipv6_cidr_block         = cidrsubnet(aws_vpc.practitioner.ipv6_cidr_block, 8, count.index)
  map_public_ip_on_launch = true

  tags = merge(
    var.default_tags,
    {
      Name        = "${var.environment}-practitioner-public-${element(data.aws_availability_zones.available.names, count.index)}"
    }
  )
}

resource "aws_subnet" "private" {
  count           = length(data.aws_availability_zones.available.names)
  vpc_id          = aws_vpc.practitioner.id
  ipv6_cidr_block = cidrsubnet(aws_vpc.practitioner.ipv6_cidr_block, 8, count.index + length(data.aws_availability_zones.available.names))

  tags = merge(
    var.default_tags,
    {
      Name        = "${var.environment}-practitioner-private-${element(data.aws_availability_zones.available.names, count.index)}"
    }
  )
}

resource "aws_egress_only_internet_gateway" "practitioner" {
  vpc_id = aws_vpc.practitioner.id

  tags = merge(
    var.default_tags,
    {
      Name        = "${var.environment}-practitioner-eoig"
    }
  )
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.practitioner.id

  route {
    ipv6_cidr_block        = "::/0"
    gateway_id             = aws_internet_gateway.practitioner.id
  }

  tags = merge(
    var.default_tags,
    {
      Name        = "${var.environment}-practitioner-public"
    }
  )
}

resource "aws_route_table_association" "public" {
  count          = length(aws_subnet.public)
  subnet_id      = element(aws_subnet.public.*.id, count.index)
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.practitioner.id

  route {
    ipv6_cidr_block        = "::/0"
    gateway_id             = aws_egress_only_internet_gateway.practitioner.id
  }

  tags = merge(
    var.default_tags,
    {
      Name        = "${var.environment}-practitioner-private"
    }
  )
}

resource "aws_route_table_association" "private" {
  count          = length(aws_subnet.private)
  subnet_id      = element(aws_subnet.private.*.id, count.index)
  route_table_id = aws_route_table.private.id
}

data "aws_availability_zones" "available" {}

resource "aws_internet_gateway" "practitioner" {
  vpc_id = aws_vpc.practitioner.id

  tags = merge(
    var.default_tags,
    {
      Name        = "${var.environment}-practitioner-igw"
    }
  )
}

variables.tf

variable "environment" {
  description = "The environment of the deployment."
  type        = string
}

variable "region" {
  description = "The AWS region to deploy resources."
  type        = string
}

variable "default_tags" {
  description = "Default tags to apply to all resources."
  type        = map(string)
}

provider "aws" {
  region = var.region
}

outputs.tf

output "vpc_id" {
  value       = aws_vpc.practitioner.id
  description = "The ID of the VPC."
}

output "public_subnet_ids" {
  value       = aws_subnet.public[*].id
  description = "The IDs of the public subnets."
}

output "private_subnet_ids" {
  value       = aws_subnet.private[*].id
  description = "The IDs of the private subnets."
}

output "egress_only_internet_gateway_id" {
  value       = aws_egress_only_internet_gateway.practitioner.id
  description = "The ID of the Egress-Only Internet Gateway."
}

解説

アドリブでプロンプトを書くのではなく、各種仕様書などをちゃんと固めた上でAIにリクエストし、ファイル生成まで自動化した方式による記念すべき初生成。 ちなみに実際のファイル生成はPythonで行っている。どろくさーいコードである。

def main():
    current_directory = os.getcwd()
    specification = load(os.path.join(current_directory, "specification.md"))

    coding_guidelines = load(os.path.join(ROOT_PATH, "documents/coding_guidelines.md"))

    project_information = load(
        os.path.join(ROOT_PATH, "documents/project_information.md")
    )

    file_loader = FileSystemLoader(os.path.join(ROOT_PATH, "templates/"))
    env = Environment(loader=file_loader)
    template = env.get_template("prompt_generate_terraform_code.j2")
    data = {
        "specification": specification,
        "project_information": project_information,
        "coding_guideline": coding_guidelines,
    }
    prompt = template.render(data)

    print("prompt is ...")
    print(prompt)

    response = ask_gpt(prompt)

    print(f"response is {response}.")
    main_contents, variables_contents, outputs_contents = extract_tf_contents(response)
    save(os.path.join(current_directory, "main.tf"), main_contents)
    save(os.path.join(current_directory, "variables.tf"), variables_contents)
    save(os.path.join(current_directory, "outputs.tf"), outputs_contents)

でも動くから良し! 大半AIに書いてもらっていて大して時間もかかっていないのでさらに良し!!