Skip to content
Merged
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
207 changes: 207 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Terraform Style Guide

This file defines the Terraform style conventions for this repository, based on the [HashiCorp Terraform Style Guide](https://developer.hashicorp.com/terraform/language/style). All Terraform code in this workspace MUST conform to these rules. When reviewing or generating Terraform code, enforce every rule below.

---

## Code Formatting

- Indent **two spaces** per nesting level.
- Align `=` signs when multiple single-line arguments appear on consecutive lines at the same nesting level:
```hcl
ami = "abc123"
instance_type = "t2.micro"
```
- Place all arguments at the **top** of a block, then nested blocks **below**, separated by one blank line.
- Use **empty lines** to separate logical groups of arguments within a block.
- **Meta-arguments first**: list meta-arguments (`count`, `for_each`) at the top of a resource block, separated from other arguments by one blank line.
- **Meta-argument blocks last**: place `lifecycle`, `depends_on` blocks at the bottom, separated from other blocks by one blank line.
```hcl
resource "aws_instance" "example" {
# meta-argument first
count = 2

ami = "abc123"
instance_type = "t2.micro"

network_interface {
# ...
}

# meta-argument block last
lifecycle {
create_before_destroy = true
}
}
```
- Separate **top-level blocks** with exactly one blank line.
- Separate **nested blocks** with blank lines, except when grouping related blocks of the same type.
- Run `terraform fmt` before committing. Use `-recursive` to format subdirectories.

## Code Validation

- Run `terraform validate` before committing to check syntax and internal consistency.

## Comments

- Use `#` for **all** comments (single-line and multi-line). Do NOT use `//` or `/* */`.
- Write self-explanatory code; only add comments when necessary to clarify complexity.

## Resource Naming

- Use a **descriptive noun** for every resource name.
- Separate words with **underscores** (`_`), not hyphens or camelCase.
- Do NOT include the resource type in the resource name (the address already contains it).
- Wrap both resource type and name in **double quotes**.

**Bad:**
```hcl
resource aws_instance webAPI-aws-instance {...}
```
**Good:**
```hcl
resource "aws_instance" "web_api" {...}
```

## Resource Order

- Define **data sources before** the resources that reference them so code "builds on itself".
- Within a resource block, order parameters as follows:
1. `count` or `for_each` meta-argument
2. Resource-specific non-block parameters
3. Resource-specific block parameters
4. `lifecycle` block (if required)
5. `depends_on` (if required)

## Variables

- Every variable MUST have a `type` and a `description`.
- Provide a `default` for optional variables.
- Set `sensitive = true` for passwords, private keys, and other secrets.
- Use `validation` blocks only when values have uniquely restrictive requirements.
- Order variable parameters:
1. `type`
2. `description`
3. `default` (optional)
4. `sensitive` (optional)
5. `validation` blocks

```hcl
variable "db_disk_size" {
type = number
description = "Disk size for the API database"
default = 100
}

variable "db_password" {
type = string
description = "Database password"
sensitive = true
}
```

## Outputs

- Every output MUST have a `description`.
- Order output parameters:
1. `description`
2. `value`
3. `sensitive` (optional)

```hcl
output "web_public_ip" {
description = "Public IP of the web instance"
value = aws_instance.web.public_ip
}
```

## Local Values

- Use local values **sparingly**; overuse makes code harder to understand.
- If referenced in multiple files, define locals in a `locals.tf` file.
- If specific to one file, define locals at the **top** of that file.
- Use descriptive nouns with underscores for local value names.

## Provider Configuration

- Always include a **default provider configuration** (without `alias`).
- Define **all providers** in the same file.
- If multiple instances of a provider exist, define the **default first**.
- For non-default providers, the `alias` parameter must be the **first** parameter in the block.

## Dynamic Resource Count (`count` / `for_each`)

- Use `count` and `for_each` **sparingly**; they add complexity.
- Use `count` when resources are almost identical.
- Use `for_each` when arguments need distinct values not derivable from an integer.
- A common pattern for conditional resources: `count = var.condition ? 1 : 0`.
- If the effect of a meta-argument is not immediately obvious, add a comment.

## File Naming Conventions

- `main.tf` — resource and data source blocks (or split by logical group as the codebase grows).
- `variables.tf` — all variable blocks, in **alphabetical order**.
- `outputs.tf` — all output blocks, in **alphabetical order**.
- `providers.tf` — all `provider` blocks and configuration.
- `terraform.tf` — single `terraform` block with `required_version` and `required_providers`.
- `backend.tf` — backend configuration.
- `locals.tf` — local values (if shared across files).
- `override.tf` — override definitions (use sparingly, comment the original resource).
- When the codebase grows, split resources into logically named files (e.g., `network.tf`, `storage.tf`, `compute.tf`). It should be immediately clear where to find any resource.

## Version Pinning

- Pin **provider versions** in `required_providers`.
- Pin **module versions** to a specific major and minor version.
- Set a minimum `required_version` for the Terraform binary in the `terraform` block.
```hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.34.0"
}
}
required_version = ">= 1.7"
}
```
- For registry modules, use the `version` parameter in the `module` block.

## Module Structure

- Group logically related resources into modules.
- Store local (child) modules in `./modules/<module_name>`.
- Follow the [standard module structure](https://developer.hashicorp.com/terraform/language/modules/develop/structure).
- Name module repositories `terraform-<PROVIDER>-<NAME>` if publishing to a registry.

## .gitignore

Do NOT commit:
- `terraform.tfstate` and `terraform.tfstate.*` backup files.
- `.terraform.tfstate.lock.info`.
- `.terraform/` directory.
- Saved plan files (from `terraform plan -out`).
- `.tfvars` files containing sensitive information.

Always commit:
- All `.tf` code files.
- `.terraform.lock.hcl` dependency lock file.
- `.gitignore`.
- `README.md`.

## Secrets Management

- Never store secrets in plain-text Terraform files.
- Use provider-specific environment variables for credentials.
- Use a secrets manager (e.g., HashiCorp Vault) where possible.
- Mark sensitive variables with `sensitive = true`.

## Testing

- Write tests for Terraform modules.
- Run tests as pre-merge checks in pull requests or as CI/CD pipeline steps.

## Linting

- Use a linter such as [TFLint](https://github.com/terraform-linters/tflint) to enforce coding standards.
- Run `terraform fmt` and `terraform validate` before every commit.
3 changes: 3 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* david.wenzel@stackit.cloud @mahauber
docs/* @lweberru
scripts/* @lweberru
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,29 @@ Contributions are welcome! Please feel free to submit a Pull Request.

## 📄 License

This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
<!-- BEGIN_TF_DOCS -->
### Requirements

No requirements.

### Providers

No providers.

### Modules

No modules.

### Resources

No resources.

### Inputs

No inputs.

### Outputs

No outputs.
<!-- END_TF_DOCS -->
52 changes: 52 additions & 0 deletions examples/01-standalone/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.10 |
| <a name="requirement_stackit"></a> [stackit](#requirement\_stackit) | 0.88.0 |

## Providers

No providers.

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_devops"></a> [devops](#module\_devops) | ../../modules/devops | n/a |
| <a name="module_governance"></a> [governance](#module\_governance) | ../../modules/governance | n/a |
| <a name="module_landing_zone"></a> [landing\_zone](#module\_landing\_zone) | ../../modules/landing-zone | n/a |
| <a name="module_management"></a> [management](#module\_management) | ../../modules/management | n/a |
| <a name="module_sandboxes"></a> [sandboxes](#module\_sandboxes) | ../../modules/sandboxes | n/a |

## Resources

No resources.

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_company_code"></a> [company\_code](#input\_company\_code) | Company code used in resource naming conventions. | `string` | n/a | yes |
| <a name="input_company_name"></a> [company\_name](#input\_company\_name) | Name of the company. | `string` | n/a | yes |
| <a name="input_labels"></a> [labels](#input\_labels) | Additional labels to apply to all resources. | `map(string)` | `{}` | no |
| <a name="input_landing_zones"></a> [landing\_zones](#input\_landing\_zones) | Map of landing zones to create (public, without network area). | <pre>map(object({<br/> project_name = string<br/> project_code = string<br/> owner_email = string<br/> env = optional(string, "dev")<br/> role_assignments = optional(list(object({<br/> role = string<br/> subject = string<br/> })), [])<br/> network_prefix_length = optional(number, null)<br/> custom_roles = optional(list(object({<br/> name = string<br/> description = string<br/> permissions = list(string)<br/> })), [])<br/> }))</pre> | `{}` | no |
| <a name="input_organization_auditors"></a> [organization\_auditors](#input\_organization\_auditors) | List of organization auditors. | `list(string)` | `[]` | no |
| <a name="input_organization_id"></a> [organization\_id](#input\_organization\_id) | Container ID of the root organization. | `string` | n/a | yes |
| <a name="input_organization_owners"></a> [organization\_owners](#input\_organization\_owners) | List of organization owners. | `list(string)` | `[]` | no |
| <a name="input_owner_email"></a> [owner\_email](#input\_owner\_email) | Email address of the owner. Required for STACKIT resource manager. | `string` | n/a | yes |
| <a name="input_platform_admins"></a> [platform\_admins](#input\_platform\_admins) | List of platform administrators. | `list(string)` | `[]` | no |
| <a name="input_region"></a> [region](#input\_region) | STACKIT region for regional resources. | `string` | `"eu01"` | no |
| <a name="input_sandboxes"></a> [sandboxes](#input\_sandboxes) | List of sandboxes to create. | <pre>list(object({<br/> project_name = string<br/> owner_emails = optional(list(string))<br/> project_owner_email = string<br/> }))</pre> | `[]` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_devops_project_id"></a> [devops\_project\_id](#output\_devops\_project\_id) | The project ID of the DevOps project. |
| <a name="output_governance_folder_ids"></a> [governance\_folder\_ids](#output\_governance\_folder\_ids) | Map of governance folder names to their container IDs. |
| <a name="output_landing_zone_projects"></a> [landing\_zone\_projects](#output\_landing\_zone\_projects) | Map of landing zone project IDs. |
| <a name="output_management_project_id"></a> [management\_project\_id](#output\_management\_project\_id) | The project ID of the Management project. |
| <a name="output_sandbox_projects"></a> [sandbox\_projects](#output\_sandbox\_projects) | The created sandbox projects. |
<!-- END_TF_DOCS -->
Loading
Loading