diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5ebd5f4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "terraform" + assignees: ["frasermolyneux"] + directory: "/terraform" + schedule: + interval: "daily" + + - package-ecosystem: "github-actions" + assignees: ["frasermolyneux"] + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml new file mode 100644 index 0000000..28c4e7b --- /dev/null +++ b/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,22 @@ +name: Dependabot Auto-Merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v1.6.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Enable auto-merge for Dependabot PRs + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/destroy-development.yml b/.github/workflows/destroy-development.yml new file mode 100644 index 0000000..8f4e580 --- /dev/null +++ b/.github/workflows/destroy-development.yml @@ -0,0 +1,28 @@ +name: Destroy Development + +on: + workflow_dispatch: + +permissions: + id-token: write # This is required for Az CLI Login + contents: read # This is required for actions/checkout + +jobs: + terraform-destroy-dev: + environment: Development + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.repository }}-dev + + steps: + - uses: actions/checkout@v4 + + - uses: frasermolyneux/actions/terraform-destroy@main + with: + terraform-folder: "terraform" + terraform-var-file: "tfvars/dev.tfvars" + terraform-backend-file: "backends/dev.backend.hcl" + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} diff --git a/.github/workflows/feature-development.yml b/.github/workflows/feature-development.yml new file mode 100644 index 0000000..b59b0cb --- /dev/null +++ b/.github/workflows/feature-development.yml @@ -0,0 +1,32 @@ +name: Feature Development + +on: + workflow_dispatch: + push: + branches: + - "feature/*" + +permissions: + id-token: write # This is required for Az CLI Login + contents: read # This is required for actions/checkout + +jobs: + terraform-plan-and-apply-dev: + environment: Development + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.repository }}-dev + + steps: + - uses: actions/checkout@v4 + + - uses: frasermolyneux/actions/terraform-plan-and-apply@main + with: + terraform-folder: "terraform" + terraform-var-file: "tfvars/dev.tfvars" + terraform-backend-file: "backends/dev.backend.hcl" + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + diff --git a/.github/workflows/pull-request-validation.yml b/.github/workflows/pull-request-validation.yml new file mode 100644 index 0000000..56b25cc --- /dev/null +++ b/.github/workflows/pull-request-validation.yml @@ -0,0 +1,61 @@ +name: Pull Request Validation + +on: + workflow_dispatch: + pull_request: + branches: + - main + +permissions: + id-token: write # This is required for Az CLI Login + contents: read # This is required for actions/checkout + +jobs: + dependency-review: + runs-on: ubuntu-latest + + steps: + - name: "Checkout Repository" + uses: actions/checkout@v4 + + - name: "Dependency Review" + uses: actions/dependency-review-action@v3 + + terraform-plan-and-apply-dev: + environment: Development + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.repository }}-dev + + steps: + - uses: actions/checkout@v4 + + - uses: frasermolyneux/actions/terraform-plan-and-apply@main + with: + terraform-folder: "terraform" + terraform-var-file: "tfvars/dev.tfvars" + terraform-backend-file: "backends/dev.backend.hcl" + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + terraform-plan-prd: + if: github.actor != 'dependabot[bot]' # dependabot context has no permissions to prod so skip this check + environment: Production + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.repository }}-prd + + steps: + - uses: actions/checkout@v4 + + - uses: frasermolyneux/actions/terraform-plan@main + with: + terraform-folder: "terraform" + terraform-var-file: "tfvars/prd.tfvars" + terraform-backend-file: "backends/prd.backend.hcl" + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} diff --git a/.github/workflows/release-to-production.yml b/.github/workflows/release-to-production.yml new file mode 100644 index 0000000..809d213 --- /dev/null +++ b/.github/workflows/release-to-production.yml @@ -0,0 +1,55 @@ +name: Release to Production + +on: + workflow_dispatch: + push: + branches: + - main + schedule: + - cron: "0 3 * * 4" # Every Thursday at 3am + +permissions: + id-token: write # This is required for Az CLI Login + contents: read # This is required for actions/checkout + +concurrency: + group: ${{ github.workflow }} + +jobs: + terraform-plan-and-apply-dev: + environment: Development + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.repository }}-dev + + steps: + - uses: actions/checkout@v4 + + - uses: frasermolyneux/actions/terraform-plan-and-apply@main + with: + terraform-folder: "terraform" + terraform-var-file: "tfvars/dev.tfvars" + terraform-backend-file: "backends/dev.backend.hcl" + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + terraform-plan-and-apply-prd: + environment: Production + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.repository }}-prd + + steps: + - uses: actions/checkout@v4 + + - uses: frasermolyneux/actions/terraform-plan-and-apply@main + with: + terraform-folder: "terraform" + terraform-var-file: "tfvars/prd.tfvars" + terraform-backend-file: "backends/prd.backend.hcl" + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} diff --git a/terraform/app_insights.tf b/terraform/app_insights.tf new file mode 100644 index 0000000..b8c4b21 --- /dev/null +++ b/terraform/app_insights.tf @@ -0,0 +1,8 @@ +resource "azurerm_application_insights" "ai" { + name = local.app_insights_name + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + workspace_id = "/subscriptions/${var.log_analytics_subscription_id}/resourceGroups/${var.log_analytics_resource_group_name}/providers/Microsoft.OperationalInsights/workspaces/${var.log_analytics_workspace_name}" + + application_type = "web" +} diff --git a/terraform/backends/dev-azuread.backend.hcl b/terraform/backends/dev-azuread.backend.hcl new file mode 100644 index 0000000..fd0ff83 --- /dev/null +++ b/terraform/backends/dev-azuread.backend.hcl @@ -0,0 +1,6 @@ +storage_account_name = "saf39fd6adf871" +container_name = "tfstate" +key = "terraform.tfstate" +use_azuread_auth = true +subscription_id = "7760848c-794d-4a19-8cb2-52f71a21ac2b" +tenant_id = "e56a6947-bb9a-4a6e-846a-1f118d1c3a14" \ No newline at end of file diff --git a/terraform/backends/dev.backend.hcl b/terraform/backends/dev.backend.hcl new file mode 100644 index 0000000..290c892 --- /dev/null +++ b/terraform/backends/dev.backend.hcl @@ -0,0 +1,7 @@ +resource_group_name = "rg-tf-portal-core-dev-uksouth-01" +storage_account_name = "saf39fd6adf871" +container_name = "tfstate" +key = "terraform.tfstate" +use_oidc = true +subscription_id = "7760848c-794d-4a19-8cb2-52f71a21ac2b" +tenant_id = "e56a6947-bb9a-4a6e-846a-1f118d1c3a14" \ No newline at end of file diff --git a/terraform/backends/prd.backend.hcl b/terraform/backends/prd.backend.hcl new file mode 100644 index 0000000..dda43d3 --- /dev/null +++ b/terraform/backends/prd.backend.hcl @@ -0,0 +1,7 @@ +resource_group_name = "rg-tf-portal-core-prd-uksouth-01" +storage_account_name = "sa2e3e95eb7965" +container_name = "tfstate" +key = "terraform.tfstate" +use_oidc = true +subscription_id = "7760848c-794d-4a19-8cb2-52f71a21ac2b" +tenant_id = "e56a6947-bb9a-4a6e-846a-1f118d1c3a14" \ No newline at end of file diff --git a/terraform/common.tf b/terraform/common.tf new file mode 100644 index 0000000..7fbc6fb --- /dev/null +++ b/terraform/common.tf @@ -0,0 +1,6 @@ +resource "azurerm_resource_group" "rg" { + name = local.resource_group_name + location = var.location + + tags = var.tags +} diff --git a/terraform/locals.tf b/terraform/locals.tf new file mode 100644 index 0000000..4daf70c --- /dev/null +++ b/terraform/locals.tf @@ -0,0 +1,4 @@ +locals { + resource_group_name = "rg-portal-core-${var.environment}-${var.location}-${var.instance}" + app_insights_name = "ai-portal-core-${var.environment}-${var.location}-${var.instance}" +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..bb21ad5 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,35 @@ +terraform { + required_version = ">= 1.6.2" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.84.0" + } + } + + backend "azurerm" {} +} + +provider "azurerm" { + subscription_id = var.subscription_id + + features { + resource_group { + # Resource group is only used by workload, App Insights creates artifacts that need to be deleted + prevent_deletion_if_contains_resources = false + } + } +} + +data "azurerm_client_config" "current" {} + +data "azuread_client_config" "current" {} + +resource "random_id" "environment_id" { + byte_length = 6 +} + +resource "time_rotating" "thirty_days" { + rotation_days = 30 +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1 @@ + diff --git a/terraform/tfvars/dev.tfvars b/terraform/tfvars/dev.tfvars new file mode 100644 index 0000000..266ee7b --- /dev/null +++ b/terraform/tfvars/dev.tfvars @@ -0,0 +1,16 @@ +environment = "dev" +location = "uksouth" +instance = "01" + +subscription_id = "d68448b0-9947-46d7-8771-baa331a3063a" + +log_analytics_subscription_id = "d68448b0-9947-46d7-8771-baa331a3063a" +log_analytics_resource_group_name = "rg-platform-logging-prd-uksouth-01" +log_analytics_workspace_name = "log-platform-prd-uksouth-01" + +tags = { + Environment = "dev", + Workload = "portal", + DeployedBy = "GitHub-Terraform", + Git = "https://github.com/frasermolyneux/portal-core" +} diff --git a/terraform/tfvars/prd.tfvars b/terraform/tfvars/prd.tfvars new file mode 100644 index 0000000..2d596e1 --- /dev/null +++ b/terraform/tfvars/prd.tfvars @@ -0,0 +1,16 @@ +environment = "prd" +location = "uksouth" +instance = "01" + +subscription_id = "32444f38-32f4-409f-889c-8e8aa2b5b4d1" + +log_analytics_subscription_id = "d68448b0-9947-46d7-8771-baa331a3063a" +log_analytics_resource_group_name = "rg-platform-logging-prd-uksouth-01" +log_analytics_workspace_name = "log-platform-prd-uksouth-01" + +tags = { + Environment = "prd", + Workload = "portal", + DeployedBy = "GitHub-Terraform", + Git = "https://github.com/frasermolyneux/portal-core" +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..cf5f3b4 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,21 @@ +variable "environment" { + default = "dev" +} + +variable "location" { + default = "uksouth" +} + +variable "instance" { + default = "01" +} + +variable "subscription_id" {} + +variable "log_analytics_subscription_id" {} +variable "log_analytics_resource_group_name" {} +variable "log_analytics_workspace_name" {} + +variable "tags" { + default = {} +}