diff --git a/.github/gitcliff.toml b/.github/gitcliff.toml new file mode 100644 index 0000000..902a6c4 --- /dev/null +++ b/.github/gitcliff.toml @@ -0,0 +1,25 @@ +# git-cliff configuration for release changelog +# Docs: https://github.com/orhun/git-cliff + +[changelog] +header = "" +body = "{{#each groups}}{{#if commits}}\n### {{title}}\n{{#each commits}}- {{this.message | trim}} ({{this.sha}})\n{{/each}}{{/if}}{{/each}}\n" +footer = "" +trim = true + +[git] +conventional_commits = true +filter_unconventional = true + +commit_parsers = [ + { message = "feat\(.*\):", group = "Features" }, + { message = "fix\(.*\):", group = "Bug Fixes" }, + { message = "perf\(.*\):", group = "Performance" }, + { message = "refactor\(.*\):", group = "Refactors" }, + { message = "docs\(.*\):", group = "Documentation" }, + { message = "chore\(.*\):", group = "Chores" }, + { message = "test\(.*\):", group = "Tests" }, + { message = "ci\(.*\):", group = "CI" } +] + +protect_breaking_changes = true diff --git a/.github/workflows/terraform-module.yml b/.github/workflows/terraform-module.yml new file mode 100644 index 0000000..cda0f38 --- /dev/null +++ b/.github/workflows/terraform-module.yml @@ -0,0 +1,85 @@ +name: quix-aks module CI/CD + +on: + push: + branches: [ main, dev ] + paths: + - 'modules/quix-aks/**' + - '.github/workflows/terraform-module.yml' + workflow_dispatch: + inputs: + bump: + description: 'Version bump (patch, minor, major)' + required: true + default: 'minor' + type: choice + options: [patch, minor, major] + +permissions: + contents: write + +jobs: + validate: + name: Validate Terraform + runs-on: ubuntu-latest + defaults: + run: + working-directory: modules/quix-aks + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.6.6 + + - name: Terraform fmt + run: terraform fmt -check -recursive + + - name: Terraform init (no backend) + run: terraform init -backend=false + + - name: Terraform validate + run: terraform validate + + - name: Generate terraform-docs (inject & commit) + uses: terraform-docs/gh-actions@v1.3.0 + with: + working-dir: modules/quix-aks + output-file: README.md + output-method: inject + config-file: '' + git-push: true + + release: + name: Tag release + runs-on: ubuntu-latest + needs: validate + # Only release on manual dispatch with bump==minor + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.bump == 'minor' }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Create tag (SemVer bump) + id: tag + uses: anothrNick/github-tag-action@1.67.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEFAULT_BUMP: ${{ github.event.inputs.bump }} + TAG_PREFIX: 'v' + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + if: steps.tag.outputs.new_tag != '' + with: + tag_name: ${{ steps.tag.outputs.new_tag }} + name: ${{ steps.tag.outputs.new_tag }} + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index fc41f6e..d6c5065 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Terraform Modules (Azure) -Repository of production-ready Terraform modules. The primary module is `azure/modules/quix-aks` (full AKS with networking, NAT, RBAC, and optional Bastion/jumpbox). +Repository of production-ready Terraform modules for installing quix-platform. ## Structure @@ -58,3 +58,53 @@ Access a private AKS: see `BASTION_ACCESS.md`. ## Module versioning Publish SemVer tags and reference the module with `?ref=vX.Y.Z` when consuming from git. + +### Using this module from another repo with a Git tag + +HTTPS example: + +```hcl +module "quix_aks" { + source = "git::https://github.com/quixio/terraform-quixplatform-azure.git//modules/quix-aks?ref=0.0.2" + + name = "my-aks" + location = "westeurope" + resource_group_name = "rg-my-aks" + create_resource_group = true + + vnet_name = "vnet-my-aks" + vnet_address_space = ["10.240.0.0/16"] + nodes_subnet_name = "Subnet-Nodes" + nodes_subnet_cidr = "10.240.0.0/22" + + nat_identity_name = "my-nat-id" + public_ip_name = "my-nat-ip" + nat_gateway_name = "my-nat" + availability_zone = "1" + + kubernetes_version = "1.32.4" + network_profile = { + network_plugin_mode = "vnet" + service_cidr = "172.22.0.0/16" + dns_service_ip = "172.22.0.10" + } + + node_pools = { + default = { + name = "default" + type = "system" + node_count = 1 + vm_size = "Standard_D4ds_v5" + } + } +} +``` + +SSH example: + +```hcl +module "quix_aks" { + source = "git::ssh://git@github.com/quixio/terraform-quixplatform-azure.git//modules/quix-aks?ref=0.0.2" + # ...same inputs as above +} +``` diff --git a/examples/private-quix-infr/main.tf b/examples/private-quix-infr/main.tf index f309eea..ce15f0d 100644 --- a/examples/private-quix-infr/main.tf +++ b/examples/private-quix-infr/main.tf @@ -20,12 +20,12 @@ resource "azurerm_resource_group" "this" { module "aks" { source = "../../modules/quix-aks" - name = "quix-aks-private" - location = "westeurope" - resource_group_name = "rg-quix-private" - create_resource_group = false - kubernetes_version = "1.32.4" - sku_tier = "Standard" + name = "quix-aks-private" + location = "westeurope" + resource_group_name = "rg-quix-private" + create_resource_group = false + kubernetes_version = "1.32.4" + sku_tier = "Standard" private_cluster_enabled = true vnet_name = "vnet-quix-private" @@ -40,21 +40,21 @@ module "aks" { enable_credentials_fetch = true node_pools = { - default = { - name = "default" - type = "system" - node_count = 2 - vm_size = "Standard_D4ds_v5" - } - quix_controller = { - name = "quixcontroller" - type = "user" - node_count = 1 - vm_size = "Standard_D4ds_v5" - taints = ["dedicated=controller:NoSchedule"] - labels = { role = "controller" } - } + default = { + name = "default" + type = "system" + node_count = 2 + vm_size = "Standard_D4ds_v5" } + quix_controller = { + name = "quixcontroller" + type = "user" + node_count = 1 + vm_size = "Standard_D4ds_v5" + taints = ["dedicated=controller:NoSchedule"] + labels = { role = "controller" } + } + } network_profile = { network_plugin_mode = "overlay" diff --git a/examples/public-quix-infr/main.tf b/examples/public-quix-infr/main.tf index d3ddff6..f044ec7 100644 --- a/examples/public-quix-infr/main.tf +++ b/examples/public-quix-infr/main.tf @@ -14,12 +14,12 @@ provider "azurerm" { module "aks" { source = "../../modules/quix-aks" - name = "quix-aks-public" - location = "westeurope" - resource_group_name = "rg-quix-public" - create_resource_group = true - kubernetes_version = "1.32.4" - sku_tier = "Standard" + name = "quix-aks-public" + location = "westeurope" + resource_group_name = "rg-quix-public" + create_resource_group = true + kubernetes_version = "1.32.4" + sku_tier = "Standard" private_cluster_enabled = false vnet_name = "vnet-quix-public" @@ -35,28 +35,28 @@ module "aks" { enable_credentials_fetch = true node_pools = { - default = { - name = "default" - type = "system" - node_count = 1 - vm_size = "Standard_D4ds_v5" - }, - quix_controller = { - name = "quixcontroller" - type = "user" - node_count = 1 - vm_size = "Standard_D4ds_v5" - taints = ["dedicated=controller:NoSchedule"] - labels = { role = "controller" } - } - quix_deployments = { - name = "quixdeployment" - type = "user" - node_count = 1 - vm_size = "Standard_D4ds_v5" - taints = ["dedicated=controller:NoSchedule"] - labels = { role = "controller" } - } + default = { + name = "default" + type = "system" + node_count = 1 + vm_size = "Standard_D4ds_v5" + }, + quix_controller = { + name = "quixcontroller" + type = "user" + node_count = 1 + vm_size = "Standard_D4ds_v5" + taints = ["dedicated=controller:NoSchedule"] + labels = { role = "controller" } + } + quix_deployments = { + name = "quixdeployment" + type = "user" + node_count = 1 + vm_size = "Standard_D4ds_v5" + taints = ["dedicated=controller:NoSchedule"] + labels = { role = "controller" } + } } network_profile = { diff --git a/modules/quix-aks/README.md b/modules/quix-aks/README.md index b0ea410..b0bdfcc 100644 --- a/modules/quix-aks/README.md +++ b/modules/quix-aks/README.md @@ -10,8 +10,8 @@ | Name | Version | |------|---------| -| [azurerm](#provider\_azurerm) | >= 3.112.0, < 4.0.0 | -| [null](#provider\_null) | n/a | +| [azurerm](#provider\_azurerm) | 3.117.1 | +| [null](#provider\_null) | 3.2.4 | ## Modules @@ -65,8 +65,8 @@ No modules. | [name](#input\_name) | Name of the AKS cluster | `string` | n/a | yes | | [nat\_gateway\_name](#input\_nat\_gateway\_name) | Name of the NAT Gateway | `string` | n/a | yes | | [nat\_identity\_name](#input\_nat\_identity\_name) | Name of the managed identity for NAT | `string` | n/a | yes | -| [network\_profile](#input\_network\_profile) | AKS network profile |
object({
network_plugin_mode = string # "overlay" or "vnet"
service_cidr = string
dns_service_ip = string
pod_cidr = optional(string)
network_policy = optional(string, "calico")
outbound_type = optional(string, "userAssignedNATGateway")
}) | n/a | yes |
-| [node\_pools](#input\_node\_pools) | Map of additional node pools (include a 'system' pool to override default) | map(object({
name = string
type = string # system | user
node_count = number
vm_size = string
max_pods = optional(number)
taints = optional(list(string))
labels = optional(map(string))
mode = optional(string) # system | user (overrides type)
})) | `{}` | no |
+| [network\_profile](#input\_network\_profile) | AKS network profile | object({
network_plugin_mode = string # "overlay" or "vnet"
service_cidr = string
dns_service_ip = string
pod_cidr = optional(string)
network_policy = optional(string, "calico")
outbound_type = optional(string, "userAssignedNATGateway")
}) | n/a | yes |
+| [node\_pools](#input\_node\_pools) | Map of additional node pools (include a 'system' pool to override default) | map(object({
name = string
type = string # system | user
node_count = number
vm_size = string
max_pods = optional(number)
taints = optional(list(string))
labels = optional(map(string))
mode = optional(string) # system | user (overrides type)
})) | `{}` | no |
| [nodes\_subnet\_cidr](#input\_nodes\_subnet\_cidr) | CIDR for the AKS nodes subnet | `string` | n/a | yes |
| [nodes\_subnet\_name](#input\_nodes\_subnet\_name) | Name of the AKS nodes subnet | `string` | n/a | yes |
| [oidc\_issuer\_enabled](#input\_oidc\_issuer\_enabled) | Enable OIDC issuer | `bool` | `true` | no |
diff --git a/modules/quix-aks/aks.tf b/modules/quix-aks/aks.tf
index 01bdd65..e9fbc78 100644
--- a/modules/quix-aks/aks.tf
+++ b/modules/quix-aks/aks.tf
@@ -3,12 +3,12 @@
################################################################################
resource "azurerm_kubernetes_cluster" "this" {
- name = var.name
- location = local.rg_location
- resource_group_name = local.rg_name_effective
- dns_prefix = "${var.name}-dns"
- kubernetes_version = var.kubernetes_version
- sku_tier = var.sku_tier
+ name = var.name
+ location = local.rg_location
+ resource_group_name = local.rg_name_effective
+ dns_prefix = "${var.name}-dns"
+ kubernetes_version = var.kubernetes_version
+ sku_tier = var.sku_tier
private_cluster_enabled = var.private_cluster_enabled
oidc_issuer_enabled = var.oidc_issuer_enabled
@@ -53,9 +53,9 @@ resource "azurerm_kubernetes_cluster" "this" {
}
resource "azurerm_kubernetes_cluster_node_pool" "additional" {
- for_each = { for k, p in var.node_pools : k => p if k != (length(local.system_pool_keys) > 0 ? local.system_pool_keys[0] : "__none__") }
+ for_each = { for k, p in var.node_pools : k => p if k != (length(local.system_pool_keys) > 0 ? local.system_pool_keys[0] : "__none__") }
# Sanitize name: lowercase, alphanum only, start with letter, max 12
- name = substr(
+ name = substr(
(
can(regex("^[a-z]", (replace(lower(each.value.name), "[^a-z0-9]", "") != "" ? replace(lower(each.value.name), "[^a-z0-9]", "") : "pool")))
? (replace(lower(each.value.name), "[^a-z0-9]", "") != "" ? replace(lower(each.value.name), "[^a-z0-9]", "") : "pool")
@@ -77,7 +77,7 @@ resource "azurerm_kubernetes_cluster_node_pool" "additional" {
drain_timeout_in_minutes = 0
node_soak_duration_in_minutes = 0
}
- tags = var.tags
+ tags = var.tags
}
################################################################################
diff --git a/modules/quix-aks/main.tf b/modules/quix-aks/main.tf
index d641579..9bc31c7 100644
--- a/modules/quix-aks/main.tf
+++ b/modules/quix-aks/main.tf
@@ -3,17 +3,17 @@
################################################################################
locals {
- is_overlay = var.network_profile.network_plugin_mode == "overlay"
+ is_overlay = var.network_profile.network_plugin_mode == "overlay"
system_pool_keys = [for k, p in var.node_pools : k if lower(coalesce(p.mode, p.type)) == "system"]
system_pool = length(local.system_pool_keys) > 0 ? var.node_pools[local.system_pool_keys[0]] : null
# Sanitize system pool name: lowercase, alphanum only, start with letter, max 12
- system_pool_name_base = replace(lower(local.system_pool.name), "[^a-z0-9]", "")
- system_pool_name_nonempty = local.system_pool_name_base != "" ? local.system_pool_name_base : "system"
- system_pool_name_prefixed = can(regex("^[a-z]", local.system_pool_name_nonempty)) ? local.system_pool_name_nonempty : "p${local.system_pool_name_nonempty}"
- system_pool_name = substr(local.system_pool_name_prefixed, 0, 12)
- rg_id = var.create_resource_group ? azurerm_resource_group.this[0].id : data.azurerm_resource_group.existing[0].id
- rg_name_effective = var.resource_group_name
- rg_location = var.create_resource_group ? azurerm_resource_group.this[0].location : data.azurerm_resource_group.existing[0].location
+ system_pool_name_base = replace(lower(local.system_pool.name), "[^a-z0-9]", "")
+ system_pool_name_nonempty = local.system_pool_name_base != "" ? local.system_pool_name_base : "system"
+ system_pool_name_prefixed = can(regex("^[a-z]", local.system_pool_name_nonempty)) ? local.system_pool_name_nonempty : "p${local.system_pool_name_nonempty}"
+ system_pool_name = substr(local.system_pool_name_prefixed, 0, 12)
+ rg_id = var.create_resource_group ? azurerm_resource_group.this[0].id : data.azurerm_resource_group.existing[0].id
+ rg_name_effective = var.resource_group_name
+ rg_location = var.create_resource_group ? azurerm_resource_group.this[0].location : data.azurerm_resource_group.existing[0].location
}
################################################################################
diff --git a/modules/quix-aks/variables.tf b/modules/quix-aks/variables.tf
index 21dd654..8d355a1 100644
--- a/modules/quix-aks/variables.tf
+++ b/modules/quix-aks/variables.tf
@@ -80,14 +80,14 @@ variable "nodes_subnet_cidr" {
variable "node_pools" {
description = "Map of additional node pools (include a 'system' pool to override default)"
type = map(object({
- name = string
- type = string # system | user
- node_count = number
- vm_size = string
- max_pods = optional(number)
- taints = optional(list(string))
- labels = optional(map(string))
- mode = optional(string) # system | user (overrides type)
+ name = string
+ type = string # system | user
+ node_count = number
+ vm_size = string
+ max_pods = optional(number)
+ taints = optional(list(string))
+ labels = optional(map(string))
+ mode = optional(string) # system | user (overrides type)
}))
default = {}
validation {
@@ -103,7 +103,7 @@ variable "node_pools" {
variable "network_profile" {
description = "AKS network profile"
type = object({
- network_plugin_mode = string # "overlay" or "vnet"
+ network_plugin_mode = string # "overlay" or "vnet"
service_cidr = string
dns_service_ip = string
pod_cidr = optional(string)
@@ -111,9 +111,9 @@ variable "network_profile" {
outbound_type = optional(string, "userAssignedNATGateway")
})
validation {
- condition = contains(["overlay", "vnet"], var.network_profile.network_plugin_mode)
- error_message = "network_profile.network_plugin_mode must be 'overlay' or 'vnet'."
-}
+ condition = contains(["overlay", "vnet"], var.network_profile.network_plugin_mode)
+ error_message = "network_profile.network_plugin_mode must be 'overlay' or 'vnet'."
+ }
}