264 lines
13 KiB
Markdown
264 lines
13 KiB
Markdown
---
|
|
description: 'Terraform Conventions and Guidelines on SAP Business Technology Platform (SAP BTP).'
|
|
applyTo: '**/*.terraform, **/*.tf, **/*.tfvars, **/*.tflint.hcl, **/*.tfstate, **/*.tf.json, **/*.tfvars.json'
|
|
---
|
|
|
|
# Terraform on SAP BTP - Best Practices and Conventions
|
|
|
|
## General Instructions
|
|
|
|
- Use Terraform to provision and manage infrastructure.
|
|
- Use version control for your Terraform configurations.
|
|
|
|
## Security
|
|
|
|
- Always use the latest stable version of Terraform and its providers.
|
|
- Regularly update your Terraform configurations to incorporate security patches and improvements.
|
|
- Never commit sensitive information such as credentials, API keys, passwords, certificates, or Terraform state to version control.
|
|
- Use `.gitignore` to exclude files containing sensitive information from version control.
|
|
- Always mark sensitive variables as `sensitive = true` in your Terraform configurations.
|
|
- This prevents sensitive values from being displayed in the Terraform plan or apply output.
|
|
- Use `ephemeral` secrets with write-only parameters when supported (Terraform v1.11+) to avoid storing secrets in state files.
|
|
- Avoid outputting sensitive data unless absolutely necessary.
|
|
- Regularly review and audit your Terraform configurations for security vulnerabilities.
|
|
- Use tools like `trivy`, `tfsec`, or `checkov` to scan your Terraform configurations for security issues.
|
|
|
|
## Modularity
|
|
|
|
- Use separate projects for each major component of the infrastructure; this:
|
|
- Reduces complexity.
|
|
- Makes it easier to manage and maintain configurations.
|
|
- Speeds up `plan` and `apply` operations.
|
|
- Allows for independent development and deployment of components.
|
|
- Reduces the risk of accidental changes to unrelated resources.
|
|
- Use modules to avoid duplication of configurations.
|
|
- Use modules to encapsulate related resources and configurations.
|
|
- Use modules to simplify complex configurations and improve readability.
|
|
- Avoid circular dependencies between modules.
|
|
- Avoid unnecessary layers of abstraction; use modules only when they add value.
|
|
- Avoid using modules for single resources; only use them for groups of related resources.
|
|
- Avoid excessive nesting of modules; keep the module hierarchy shallow.
|
|
- Use `output` blocks to expose important information about your infrastructure.
|
|
- Use outputs to provide information that is useful for other modules or for users of the configuration.
|
|
- Avoid exposing sensitive information in outputs; mark outputs as `sensitive = true` if they contain sensitive data.
|
|
|
|
## Maintainability
|
|
|
|
- Prioritize readability, clarity, and maintainability.
|
|
- Use comments to explain complex configurations and why certain design decisions were made.
|
|
- Write concise, efficient, and idiomatic configs that are easy to understand.
|
|
- Avoid using hard-coded values; use variables for configuration instead.
|
|
- Set default values for variables, where appropriate.
|
|
- Use data sources to retrieve information about existing resources instead of requiring manual configuration.
|
|
- This reduces the risk of errors, ensures that configurations are always up-to-date, and allows configurations to adapt to different environments.
|
|
- Avoid using data sources for resources that are created within the same configuration; use outputs instead.
|
|
- Avoid using data sources in reusable modules. Prefer explicit module parameters over data source lookups.
|
|
- Avoid, or remove, unnecessary data sources; they slow down `plan` and `apply` operations.
|
|
- Use `locals` for values that are used multiple times to ensure consistency.
|
|
|
|
## Style and Formatting
|
|
|
|
### General Style Guidelines
|
|
|
|
- Follow Terraform best practices for resource naming and organization.
|
|
- Use descriptive names for resources, variables, and outputs.
|
|
- Use consistent naming conventions across all configurations.
|
|
- Use snake_case for all variable names.
|
|
- Follow the **Terraform Style Guide** for formatting.
|
|
- Use consistent indentation (2 spaces for each level).
|
|
|
|
### Folder Layout and File Organization
|
|
|
|
- Structure Terraform configurations with logical file separation:
|
|
- Use `main.tf` for resources.
|
|
- Use `variables.tf` for inputs.
|
|
- Use `outputs.tf` for outputs.
|
|
- Use `terraform.tf` for provider configurations.
|
|
- Use `locals.tf` to abstract complex expressions and for better readability.
|
|
- Use a consistent folder structure for Terraform configurations.
|
|
- Use tfvars to modify environmental differences. In general, aim to keep environments similar.
|
|
- A **suggested** structure is:
|
|
|
|
```text
|
|
my-sap-bto-app/
|
|
├── infra/ # Terraform root module
|
|
│ ├── main.tf # Core resources
|
|
│ ├── variables.tf # Input variables
|
|
│ ├── outputs.tf # Outputs
|
|
│ ├── provider.tf # Provider configuration
|
|
│ ├── locals.tf # Local values
|
|
│ └── environments/ # Environment-specific configurations
|
|
│ ├── dev.tfvars # Development environment
|
|
│ ├── test.tfvars # Test environment
|
|
│ └── prod.tfvars # Production environment
|
|
├── .github/workflows/ # CI/CD pipelines (if using github)
|
|
└── README.md # Documentation
|
|
```
|
|
|
|
- Never change the folder structure without direct agreement with the user.
|
|
- If the `main.tf` or `variables.tf` files grow too large, split them into multiple files by resource type or function (e.g., `main.services.tf`, `main.rolecollectionassignments.tf` - move equivalent variables to `variables.services.tf`, etc.)
|
|
- **Antipatterns** are
|
|
- branch per environment
|
|
- repository per environment
|
|
- folder per environment
|
|
- or similar layouts that make it hard to test the root folder logic between environments.
|
|
|
|
### Resource Block Organization
|
|
|
|
- Place `depends_on` blocks at the very beginning of resource definitions to make dependency relationships clear.
|
|
- Use `depends_on` only when necessary to avoid circular dependencies.
|
|
- Place `for_each` and `count` blocks at the beginning of resource definitions to clarify the resource's instantiation logic.
|
|
- Use `count` for 0-1 resources.
|
|
- Use `for_each` for multiple resources.
|
|
- Use `for_each` for collections and `count` for numeric iterations.
|
|
- Prefer maps for stable resource addresses.
|
|
- Place them after `depends_on` blocks, if they are present.
|
|
- Place `lifecycle` blocks at the end of resource definitions.
|
|
- Group related attributes together within blocks.
|
|
- Place required attributes before optional ones, and comment each section accordingly.
|
|
- Separate attribute sections with blank lines to improve readability.
|
|
- Alphabetize attributes within each section for easier navigation.
|
|
- Use blank lines to separate logical sections of your configurations.
|
|
|
|
### Variables Style Standards
|
|
|
|
- All variables must have explicit type declarations.
|
|
- All variables must have a comprehensive descriptions.
|
|
- Avoid nullable defaults for collection values unless there's a specific need.
|
|
|
|
### Locals Style Standards
|
|
|
|
- Use locals for computed values and complex expressions.
|
|
- Improve readability by extracting repeated expressions.
|
|
- Combine related values into structured locals.
|
|
|
|
### Outputs Style Standards
|
|
|
|
- Avoid unnecessary outputs, only use these to expose information needed by other configurations.
|
|
- Use `sensitive = true` for outputs containing secrets.
|
|
- Provide clear descriptions for all outputs.
|
|
|
|
### Formatting and Linting
|
|
|
|
- Use `terraform fmt -recursive` to format your configurations automatically.
|
|
- Use `tflint` to check for style violations and ensure configurations follow best practices.
|
|
- Run `tflint` regularly to catch style issues early in the development process.
|
|
|
|
## Documentation
|
|
|
|
- Always include `description` and `type` attributes for variables and outputs.
|
|
- Use clear and concise descriptions to explain the purpose of each variable and output.
|
|
- Use appropriate types for variables (e.g., `string`, `number`, `bool`, `list`, `map`).
|
|
- Document your Terraform configurations using comments, where appropriate.
|
|
- Use comments to explain the purpose of resources and variables.
|
|
- Use comments to explain complex configurations or decisions.
|
|
- Avoid redundant comments; comments should add value and clarity.
|
|
- Include a `README.md` file in each project to provide an overview of the project and its structure.
|
|
- Include instructions for setting up and using the configurations.
|
|
- Use `terraform-docs` to generate documentation for your configurations automatically.
|
|
|
|
## State Management
|
|
|
|
- Use remote backend with state locking
|
|
- Do not use SAP BTP Object Store as a backend for Terraform state as your access is too limited to ensure a proper state management
|
|
- Never commit state files to source control
|
|
- Enable encryption at rest and in transit
|
|
|
|
## Validation
|
|
|
|
- Run `terraform validate` to check syntax
|
|
- Ask before running `terraform plan`. Terraform plan will require authentication information and the global account subdomain. The authentication information should be sourced from environment parameters or via tfvars, but **MUST NOT** be coded in the provider block.
|
|
- Test configurations in non-production environments first
|
|
- Ensure idempotency (multiple applies produce same result)
|
|
|
|
## Testing
|
|
|
|
- Write tests to validate the functionality of your Terraform configurations.
|
|
- Use the `.tftest.hcl` extension for test files.
|
|
- Write tests to cover both positive and negative scenarios.
|
|
- Ensure tests are idempotent and can be run multiple times without side effects.
|
|
|
|
## SAP BTP Provider Specifics
|
|
|
|
When provisioning SAP BTP resources, follow these additional best practices:
|
|
|
|
- Use the data source `btp_subaccount_service_plan` to retrieve service plan information when provisioning the resource `btp_subaccount_service_instance`
|
|
- Sample code should look like this:
|
|
|
|
```terraform
|
|
data "btp_subaccount_service_plan" "example" {
|
|
subaccount_id = var.subaccount_id
|
|
service_name = "your_service_name"
|
|
plan_name = "your_plan_name"
|
|
}
|
|
|
|
resource "btp_subaccount_service_instance" "example" {
|
|
subaccount_id = var.subaccount_id
|
|
serviceplan_id = data.btp_subaccount_service_plan.example.id
|
|
name = "my-example-instance-new"
|
|
}
|
|
```
|
|
|
|
- Use **explicit dependencies** between `btp_subaccount_entitlement` and the data source `btp_subaccount_service_plan` to ensure correct provisioning order for the resource `btp_subaccount_service_instance` that implictly dpeends on the data source.
|
|
- The connection between the resource `btp_subaccount_entitlement` and the data source `btp_subaccount_service_plan` is not automatically inferred by the provider.
|
|
- The data source `btp_subaccount_service_plan` depends on the `btp_subaccount_entitlement`.
|
|
- You can derive the dependency by comparing the `service_name` and `plan_name` of the resource and data source.
|
|
- Sample code should look like this:
|
|
|
|
```terraform
|
|
resource "btp_subaccount_entitlement" "example" {
|
|
subaccount_id = var.subaccount_id
|
|
service_name = "your_service_name"
|
|
plan_name = "your_plan_name"
|
|
}
|
|
|
|
data "btp_subaccount_service_plan" "example" {
|
|
subaccount_id = var.subaccount_id
|
|
service_name = "your_service_name"
|
|
plan_name = "your_plan_name"
|
|
|
|
depends_on = [btp_subaccount_entitlement.example]
|
|
}
|
|
```
|
|
|
|
- Use **explicit dependencies** between `btp_subaccount_entitlement` and `btp_subaccount_subscription` resources to ensure correct provisioning order.
|
|
- The connection between `btp_subaccount_entitlement` and `btp_subaccount_subscription` is not automatically inferred by the provider.
|
|
- The resource `btp_subaccount_subscription` depends on the `btp_subaccount_entitlement`.
|
|
- You can derive the dependency by comparing the `app_name` with the `service_name` and the attributes `plan_name`.
|
|
-
|
|
|
|
## Tool Integration
|
|
|
|
### Use Available Tools
|
|
|
|
- **Terraform MCP Serer**: Use the Terraform MCP server for provider specific guidance https://github.com/mcp/hashicorp/terraform-mcp-server
|
|
|
|
### Terraform Registry
|
|
|
|
- **Schema Information**: Use the Terraform Provider for SAP BTP at https://registry.terraform.io/providers/SAP/btp/latest/docs for SAP BTP resources available via the MCP server
|
|
|
|
## Anti-Patterns to Avoid
|
|
|
|
**Configuration:**
|
|
|
|
- MUST NOT hardcode values that should be parameterized
|
|
- MUST NOT use `terraform import` as a regular workflow pattern
|
|
- SHOULD avoid complex conditional logic that makes code hard to understand
|
|
- MUST NOT use `local-exec` provisioners unless absolutely necessary
|
|
- SHOULD NOT combine the Terraform provider for SAP BTP with the Terraform prpovider for Cloud Foundry in the same configuration
|
|
|
|
**Security:**
|
|
|
|
- MUST NEVER store secrets in Terraform files or state
|
|
- MUST NOT disable security features for convenience
|
|
- MUST NOT use default passwords or keys in resource configurations
|
|
|
|
**Operational:**
|
|
|
|
- MUST NOT apply Terraform changes directly to production without testing
|
|
- MUST avoid making manual changes to Terraform-managed resources
|
|
- MUST NOT ignore Terraform state file corruption or inconsistencies
|
|
- MUST NOT run Terraform from local machines for production
|
|
- SHOULD NOT use a Terraform state file (`**/*.tfstate`) for read operations
|
|
- MUST NOT use a Terraform state file (`**/*.tfstate`) for read operations all changes must be made via Terraform CLI or HCL.
|