From b9613acf167aec2e68c6f0b8530c3d2cce16593e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20W=C3=BCrbach?= Date: Tue, 20 Feb 2024 21:13:52 +0100 Subject: [PATCH] feat: backstage example --- .github/workflows/ci.yaml | 3 + examples/with-backstage/README.md | 156 ++++++++++++++++++ examples/with-backstage/azure-github.tf | 29 ++++ examples/with-backstage/backstage-github.tf | 86 ++++++++++ .../with-backstage/backstage-humanitec.tf | 114 +++++++++++++ .../with-backstage/create-gh-app/index.js | 154 +++++++++++++++++ examples/with-backstage/main.tf | 9 + examples/with-backstage/provider.tf | 79 +++++++++ examples/with-backstage/variables.tf | 37 +++++ modules/base/README.md | 9 + modules/base/acr.tf | 13 ++ modules/base/main.tf | 4 + modules/base/outputs.tf | 20 +++ modules/base/providers.tf | 4 + modules/base/variables.tf | 6 + 15 files changed, 723 insertions(+) create mode 100644 examples/with-backstage/README.md create mode 100644 examples/with-backstage/azure-github.tf create mode 100644 examples/with-backstage/backstage-github.tf create mode 100644 examples/with-backstage/backstage-humanitec.tf create mode 100644 examples/with-backstage/create-gh-app/index.js create mode 100644 examples/with-backstage/main.tf create mode 100644 examples/with-backstage/provider.tf create mode 100644 examples/with-backstage/variables.tf create mode 100644 modules/base/acr.tf diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5159c06..90eb569 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -35,6 +35,9 @@ jobs: - name: Terraform Format Check run: make fmt-check + - name: Stub GitHub App credentials (required for validation) + run: cd ./examples/with-backstage && STUB_FILE=1 node create-gh-app/index.js + - name: Terraform Validate run: make validate diff --git a/examples/with-backstage/README.md b/examples/with-backstage/README.md new file mode 100644 index 0000000..844d793 --- /dev/null +++ b/examples/with-backstage/README.md @@ -0,0 +1,156 @@ +# Azure reference architecture with Backstage + +Provisions the Azure reference architecture connected to Humanitec and installs Backstage. + +## Prerequisites + +* The same prerequisites as the [base reference architecture](../../README.md#prerequisites), plus the following items. +* A GitHub organization and permission to create new repositories in it. Go to to create a new org (the "Free" option is fine). Note: is has to be an organization, a free account is not sufficient. +* Create a classic github personal access token with `repo`, `workflow`, `delete_repo` and `admin:org` scope [here](https://github.com/settings/tokens). +* Set the `GITHUB_TOKEN` environment variable to your token. + + ``` + export GITHUB_TOKEN="my-github-token" + ``` + +* Set the `GITHUB_ORG_ID` environment variable to your GitHub organization ID. + + ``` + export GITHUB_ORG_ID="my-github-org-id" + ``` + +* [Node.js](https://nodejs.org) installed locally. +* Install the GitHub App for Backstage into your GitHub organization using `node create-gh-app/index.js`. Follow the instructions. + * “All repositories” ~> Install + * “Okay, […] was installed on the […] account.” ~> You can close the window and server. + +## Usage + +Follow the same steps as for the [base layer](../../README.md#usage), applying these modifications: + +* Execute `cd ./examples/with-backstage` after cloning the repo. Execute all subsequent commands in this directory. +* In particular, use the `./examples/with-backstage/terraform.tfvars.example` file as the basis for your `terraform.tfvars` file. It defines additional variables needed to setup and configure Backstage. + +## Verify your result + +Check for the existence of key elements of the backstage module. This is a subset of all elements only. For a complete list of what was installed, review the Terraform code. + +1. Perform the [verification steps of the base installation](../../README.md) if you have not already done so. +2. Verify the existence of the Backstage Application in your Humanitec Organization: + + ``` + curl -s https://api.humanitec.io/orgs/${HUMANITEC_ORG}/apps/backstage \ + --header "Authorization: Bearer ${HUMANITEC_TOKEN}" + ``` + + This should output a JSON formatted representation of the Application like so: + + ``` + {"id":"backstage","name":"backstage","created_at":"2023-10-02T13:44:27Z","created_by":"s-d3e94a0e-8b53-29f9-b666-27548b7e06e0","envs":[{"id":"development","name":"Development","type":"development"}]} + ``` + + You can also check for the Application in the [Humanitec Platform Orchestrator UI](https://app.humanitec.io). + +3. Connect to your EKS cluster via `kubectl`. See the [Azure documentation](https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-cli#connect-to-the-cluster) or use this command: + + ``` + az aks get-credentials --resource-group ref-arch --name ref-arch-aks + ``` + +4. Get the elements in the newly created Kubernetes namespace: + + ``` + kubectl get all -n backstage-development + ``` + + You should see + * a `deployment`, `replicaset`, running `pod`, and `service` for Backstage + * a `statefulset`, running `pod`, and `service` for PostgreSQL database used by Backstage. + + Note: it may take up to ten minutes after the `terraform apply` completed until you actually see those resources. The Backstage application needs to built and deployed via a GitHub action out of the newly created repository in your GitHub organization. + +## Cleaning up + +Once you are finished with the reference architecture, you can remove all provisioned infrastructure and the resource definitions created in Humanitec with the following: + +1. Delete all Humanitec applications scaffolded using Backstage, but not the `backstage` app itself. + +2. Follow the [base reference architecture cleanup instructions](../../README.md#cleaning-up). + +## Terraform docs + + +### Requirements + +| Name | Version | +|------|---------| +| terraform | >= 1.3.0 | +| Azure | ~> 5.17 | +| github | ~> 5.38 | +| humanitec | ~> 0.13 | + +### Providers + +| Name | Version | +|------|---------| +| Azure | ~> 5.17 | +| github | ~> 5.38 | +| humanitec | ~> 0.13 | + +### Modules + +| Name | Source | Version | +|------|--------|---------| +| backstage\_ecr | terraform-Azure-modules/ecr/Azure | ~> 1.6 | +| backstage\_iam\_policy\_ecr\_create\_repository | git:: | n/a | +| backstage\_iam\_role\_service\_account | git:: | n/a | +| backstage\_k8s\_service\_account | git:: | n/a | +| backstage\_mysql | git:: | n/a | +| backstage\_postgres | git:: | n/a | +| backstage\_workload | git:: | n/a | +| base | ../../modules/base | n/a | +| iam\_github\_oidc\_provider | terraform-Azure-modules/iam/Azure//modules/iam-github-oidc-provider | ~> 5.30 | +| iam\_github\_oidc\_role | terraform-Azure-modules/iam/Azure//modules/iam-github-oidc-role | ~> 5.30 | + +### Resources + +| Name | Type | +|------|------| +| [Azure_iam_policy.ecr_push_policy](https://registry.terraform.io/providers/hashicorp/Azure/latest/docs/resources/iam_policy) | resource | +| [github_actions_organization_secret.backstage_humanitec_token](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_secret) | resource | +| [github_actions_organization_variable.backstage_Azure_region](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_Azure_role_arn](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_cloud_provider](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_humanitec_org_id](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_repository.backstage](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository) | resource | +| [humanitec_application.backstage](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/application) | resource | +| [humanitec_resource_definition_criteria.backstage_iam_policy_ecr_create_repository](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | +| [humanitec_resource_definition_criteria.backstage_iam_role_service_account](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | +| [humanitec_resource_definition_criteria.backstage_k8s_service_account](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | +| [humanitec_resource_definition_criteria.backstage_mysql](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | +| [humanitec_resource_definition_criteria.backstage_postgres](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | +| [humanitec_resource_definition_criteria.backstage_workload](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | +| [humanitec_value.Azure_default_region](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | +| [humanitec_value.backstage_cloud_provider](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | +| [humanitec_value.backstage_github_app_client_id](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | +| [humanitec_value.backstage_github_app_client_secret](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | +| [humanitec_value.backstage_github_app_id](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | +| [humanitec_value.backstage_github_app_private_key](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | +| [humanitec_value.backstage_github_app_webhook_secret](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | +| [humanitec_value.backstage_github_org_id](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | +| [humanitec_value.backstage_humanitec_org](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | +| [humanitec_value.backstage_humanitec_token](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/value) | resource | + +### Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| Azure\_account\_id | Azure Account (ID) to use | `string` | n/a | yes | +| Azure\_region | Azure region | `string` | n/a | yes | +| github\_org\_id | GitHub org id | `string` | n/a | yes | +| humanitec\_ci\_service\_user\_token | Humanitec CI Service User Token | `string` | n/a | yes | +| humanitec\_org\_id | Humanitec Organization ID | `string` | n/a | yes | +| disk\_size | Disk size in GB to use for EKS nodes | `number` | `20` | no | +| instance\_types | List of EC2 instances types to use for EKS nodes | `list(string)` |
[
"t3.large"
]
| no | +| resource\_packs\_Azure\_rev | Revision of the resource-packs-Azure repository to use | `string` | `"refs/heads/main"` | no | + diff --git a/examples/with-backstage/azure-github.tf b/examples/with-backstage/azure-github.tf new file mode 100644 index 0000000..419a7ab --- /dev/null +++ b/examples/with-backstage/azure-github.tf @@ -0,0 +1,29 @@ +locals { + name = "gha-acr-push" + cloud_provider = "azure" + github_issuer_url = "https://token.actions.githubusercontent.com" +} + +# User for GHA allowed to push images using OpenID Connect (OIDC) so we don't need to store credentials in GitHub +# Reference https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-azure + +resource "azurerm_user_assigned_identity" "github_oidc_identity" { + name = local.name + location = module.base.az_resource_group_location + resource_group_name = module.base.az_resource_group_name +} + +resource "azurerm_federated_identity_credential" "github_oidc_identity" { + name = "github-action-identity" + resource_group_name = module.base.az_resource_group_name + audience = ["api://AzureADTokenExchange"] + issuer = local.github_issuer_url + subject = "repository_owner:${var.github_org_id}" # configured in github_actions_organization_oidc_subject_claim_customization_template + parent_id = azurerm_user_assigned_identity.github_oidc_identity.id +} + +resource "azurerm_role_assignment" "github_oidc_identity_acr" { + scope = module.base.az_container_registry_id + role_definition_name = "AcrPush" + principal_id = azurerm_user_assigned_identity.github_oidc_identity.principal_id +} diff --git a/examples/with-backstage/backstage-github.tf b/examples/with-backstage/backstage-github.tf new file mode 100644 index 0000000..8d6001a --- /dev/null +++ b/examples/with-backstage/backstage-github.tf @@ -0,0 +1,86 @@ +# Configure GitHub variables & secrets for Backstage itself and for all scaffolded apps + +locals { + github_app_credentials_file = "github-app-credentials.json" + github_app_credentials = jsondecode(file("${path.module}/${local.github_app_credentials_file}")) + github_app_id = local.github_app_credentials["appId"] + github_app_client_id = local.github_app_credentials["clientId"] + github_app_client_secret = local.github_app_credentials["clientSecret"] + github_app_private_key = local.github_app_credentials["privateKey"] + github_webhook_secret = local.github_app_credentials["webhookSecret"] +} + +locals { + backstage_repo = "backstage" +} + +resource "github_actions_organization_variable" "backstage_cloud_provider" { + variable_name = "CLOUD_PROVIDER" + visibility = "all" + value = local.cloud_provider +} + +resource "github_actions_organization_variable" "backstage_azure_client_id" { + variable_name = "AZURE_CLIENT_ID" + visibility = "all" + value = azurerm_user_assigned_identity.github_oidc_identity.client_id +} + +resource "github_actions_organization_variable" "backstage_azure_tenant_id" { + variable_name = "AZURE_TENANT_ID" + visibility = "all" + value = azurerm_user_assigned_identity.github_oidc_identity.tenant_id +} + +resource "github_actions_organization_variable" "backstage_azure_subscription_id" { + variable_name = "AZURE_SUBSCRIPTION_ID" + visibility = "all" + value = var.subscription_id +} + +resource "github_actions_organization_variable" "backstage_azure_acr_name" { + variable_name = "AZURE_ACR_NAME" + visibility = "all" + value = module.base.az_container_registry_name +} + +resource "github_actions_organization_variable" "backstage_humanitec_org_id" { + variable_name = "HUMANITEC_ORG_ID" + visibility = "all" + value = var.humanitec_org_id +} + +resource "github_actions_organization_secret" "backstage_humanitec_token" { + secret_name = "HUMANITEC_TOKEN" + visibility = "all" + plaintext_value = var.humanitec_ci_service_user_token +} + +# Backstage repository itself + +resource "github_repository" "backstage" { + name = local.backstage_repo + description = "Backstage" + + visibility = "public" + + template { + owner = "humanitec-architecture" + repository = "backstage" + } + + depends_on = [ + module.base, + humanitec_application.backstage, + humanitec_resource_definition_criteria.backstage_postgres, + github_actions_organization_secret.backstage_humanitec_token, + ] +} + +# Required as Azure doesn't support wildcards in scopes https://github.com/Azure/azure-workload-identity/issues/373 +# More details in https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-token-claims +resource "github_actions_repository_oidc_subject_claim_customization_template" "backstage" { + repository = github_repository.backstage.name + use_default = false + include_claim_keys = ["repository_owner"] +} diff --git a/examples/with-backstage/backstage-humanitec.tf b/examples/with-backstage/backstage-humanitec.tf new file mode 100644 index 0000000..387a7ac --- /dev/null +++ b/examples/with-backstage/backstage-humanitec.tf @@ -0,0 +1,114 @@ +resource "humanitec_application" "backstage" { + id = "backstage" + name = "backstage" +} + +# Configure required values for backstage + +resource "humanitec_value" "backstage_github_org_id" { + app_id = humanitec_application.backstage.id + key = "GITHUB_ORG_ID" + description = "" + value = var.github_org_id + is_secret = false +} + +resource "humanitec_value" "backstage_github_app_id" { + app_id = humanitec_application.backstage.id + key = "GITHUB_APP_ID" + description = "" + value = local.github_app_id + is_secret = false +} + +resource "humanitec_value" "backstage_github_app_client_id" { + app_id = humanitec_application.backstage.id + key = "GITHUB_APP_CLIENT_ID" + description = "" + value = local.github_app_client_id + is_secret = true +} + +resource "humanitec_value" "backstage_github_app_client_secret" { + app_id = humanitec_application.backstage.id + key = "GITHUB_APP_CLIENT_SECRET" + description = "" + value = local.github_app_client_secret + is_secret = true +} + +resource "humanitec_value" "backstage_github_app_private_key" { + app_id = humanitec_application.backstage.id + key = "GITHUB_APP_PRIVATE_KEY" + description = "" + value = indent(2, local.github_app_private_key) + is_secret = true +} + +resource "humanitec_value" "backstage_github_app_webhook_secret" { + app_id = humanitec_application.backstage.id + key = "GITHUB_APP_WEBHOOK_SECRET" + description = "" + value = local.github_webhook_secret + is_secret = true +} + +resource "humanitec_value" "backstage_humanitec_org" { + app_id = humanitec_application.backstage.id + key = "HUMANITEC_ORG_ID" + description = "" + value = var.humanitec_org_id + is_secret = false +} + +resource "humanitec_value" "backstage_humanitec_token" { + app_id = humanitec_application.backstage.id + key = "HUMANITEC_TOKEN" + description = "" + value = var.humanitec_ci_service_user_token + is_secret = true +} + +resource "humanitec_value" "backstage_cloud_provider" { + app_id = humanitec_application.backstage.id + key = "CLOUD_PROVIDER" + description = "" + value = local.cloud_provider + is_secret = false +} + +# Configure required resources for backstage + +locals { + res_def_prefix = "backstage-" +} + +# in-cluster postgres + +module "backstage_postgres" { + source = "git::https://github.com/humanitec-architecture/resource-packs-in-cluster.git//humanitec-resource-defs/postgres/basic" + + prefix = local.res_def_prefix +} + +resource "humanitec_resource_definition_criteria" "backstage_postgres" { + resource_definition_id = module.backstage_postgres.id + app_id = humanitec_application.backstage.id + + force_delete = true +} + +# Configure required resources for scaffolded apps + +# in-cluster mysql + +module "backstage_mysql" { + source = "git::https://github.com/humanitec-architecture/resource-packs-in-cluster.git//humanitec-resource-defs/mysql/basic" + + prefix = local.res_def_prefix +} + +resource "humanitec_resource_definition_criteria" "backstage_mysql" { + resource_definition_id = module.backstage_mysql.id + env_type = module.base.environment +} diff --git a/examples/with-backstage/create-gh-app/index.js b/examples/with-backstage/create-gh-app/index.js new file mode 100644 index 0000000..f7faadc --- /dev/null +++ b/examples/with-backstage/create-gh-app/index.js @@ -0,0 +1,154 @@ +// Small CLI tool to create a GitHub App for Backstage +// +// Heavily inspired by https://github.com/backstage/backstage/blob/master/packages/cli/src/commands/create-github-app/ + +const http = require('http'); +const crypto = require('crypto'); +const fs = require('fs/promises') + +const hostname = '127.0.0.1'; +const port = 3000; + +const FORM_PAGE = ` + + +
+ + +
+ + + +`; + + +let baseUrl; + + +const webhookId = crypto +.randomBytes(15) +.toString('base64') +.replace(/[\+\/]/g, ''); + +const webhookUrl = `https://smee.io/${webhookId}`; + +const handleIndex = (req, res, GITHUB_ORG_ID) => { + const encodedOrg = encodeURIComponent(GITHUB_ORG_ID); + const actionUrl = `https://github.com/organizations/${encodedOrg}/settings/apps/new`; + + + res.statusCode = 200; + const manifest = { + default_events: ['create', 'delete', 'push', 'repository'], + default_permissions: { + actions: 'write', // Required to configure repository oidc customization + members: 'read', + administration: 'write', + contents: 'write', + metadata: 'read', + pull_requests: 'write', + issues: 'write', + workflows: 'write', + checks: 'read', + actions_variables: 'write', + secrets: 'write', + environments: 'write', + }, + name: `backstage-${GITHUB_ORG_ID}`, + url: 'https://backstage.io', + description: 'GitHub App for Backstage', + public: false, + redirect_url: `${baseUrl}/callback`, + hook_attributes: { + url: webhookUrl, + active: false, + }, + }; + + const manifestJson = JSON.stringify(manifest).replace(/\"/g, '"'); + + let body = FORM_PAGE; + body = body.replace('MANIFEST_JSON', manifestJson); + body = body.replace('ACTION_URL', actionUrl); + + res.setHeader('content-type', 'text/html'); + res.end(body); +} + + +const writeConfigFile = async (data, webhookUrl) => { + const fileName = `github-app-credentials.json`; + const content = JSON.stringify({ + name: data.name, + slug: data.slug, + appId: data.id, + webhookUrl: webhookUrl, + clientId: data.client_id, + clientSecret: data.client_secret, + webhookSecret: data.webhook_secret, + privateKey: data.pem, + }, null, 2) + + await fs.writeFile(fileName, content); + + console.log(`Created ${fileName}, you can close the server now.`) +} + +const handleCallback = async (req, res) => { + const url = new URL(req.url, `http://${req.headers.host}`); + const conversionRes = await fetch(`https://api.github.com/app-manifests/${encodeURIComponent(url.searchParams.get('code'))}/conversions`, { + method: 'POST', + }); + + if (conversionRes.status !== 201) { + const body = await conversionRes.text(); + res.statusCode = conversionRes.status; + res.end(body); + } + + const data = await conversionRes.json(); + + await writeConfigFile(data, webhookUrl); + + res.writeHead(302, { Location: `${data.html_url}/installations/new` }); + res.end(); +} + +if (process.env.STUB_FILE === '1') { + writeConfigFile({ + name: 'stub', + slug: 'stub', + id: 'stub', + client_id: 'stub', + client_secret: 'stub', + webhook_secret: 'stub', + pem: 'stub', + }, 'https://smee.io/stub'); + + return; +} + +const GITHUB_ORG_ID = process.env.GITHUB_ORG_ID; +if (!GITHUB_ORG_ID) { + console.error('Please export GITHUB_ORG_ID'); + process.exit(1); +} + +const server = http.createServer((req, res) => { + if (req.url === '/') { + handleIndex(req, res, GITHUB_ORG_ID); + } else if (req.url.startsWith('/callback?')) { + handleCallback(req, res); + } else { + res.statusCode = 404; + res.end('Not found, url: ' + req.url); + } +}); + +server.listen(port, hostname, () => { + baseUrl = `http://${hostname}:${port}`; + + console.log(`Open ${baseUrl}`); +}); diff --git a/examples/with-backstage/main.tf b/examples/with-backstage/main.tf new file mode 100644 index 0000000..ea933c2 --- /dev/null +++ b/examples/with-backstage/main.tf @@ -0,0 +1,9 @@ +# Azure reference architecture + +module "base" { + source = "../../modules/base" + + subscription_id = var.subscription_id + location = var.location + vm_size = var.vm_size +} diff --git a/examples/with-backstage/provider.tf b/examples/with-backstage/provider.tf new file mode 100644 index 0000000..49646ee --- /dev/null +++ b/examples/with-backstage/provider.tf @@ -0,0 +1,79 @@ +terraform { + required_providers { + azapi = { + source = "Azure/azapi" + version = "~> 1.11" + } + azuread = { + source = "hashicorp/azuread" + version = "~> 2.47" + } + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.87" + } + github = { + source = "integrations/github" + version = "~> 5.38" + } + helm = { + source = "hashicorp/helm" + version = "~> 2.12" + } + humanitec = { + source = "humanitec/humanitec" + version = "~> 0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.25" + } + } + required_version = ">= 1.3.0" +} + +provider "humanitec" { + org_id = var.humanitec_org_id +} + +provider "github" { + owner = var.github_org_id +} + +provider "azapi" { + subscription_id = var.subscription_id +} + +provider "azurerm" { + features {} + + subscription_id = var.subscription_id +} + +provider "azuread" {} + +provider "kubernetes" { + host = module.base.aks_host + + cluster_ca_certificate = base64decode(module.base.aks_cluster_ca_certificate) + + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "kubelogin" + # This requires the kubelogin to be installed locally where Terraform is executed + args = ["get-token", "--server-id", module.base.aks_server_app_id, "--login", "azurecli"] + } +} + +provider "helm" { + kubernetes { + host = module.base.aks_host + cluster_ca_certificate = base64decode(module.base.aks_cluster_ca_certificate) + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "kubelogin" + # This requires the kubelogin to be installed locally where Terraform is executed + args = ["get-token", "--server-id", module.base.aks_server_app_id, "--login", "azurecli"] + } + } +} diff --git a/examples/with-backstage/variables.tf b/examples/with-backstage/variables.tf new file mode 100644 index 0000000..2daf542 --- /dev/null +++ b/examples/with-backstage/variables.tf @@ -0,0 +1,37 @@ +variable "subscription_id" { + description = "Azure Subscription (ID) to use" + type = string +} + +variable "location" { + description = "Azure region to deploy into" + type = string +} + +variable "github_org_id" { + description = "GitHub org id" + type = string +} + +variable "humanitec_org_id" { + description = "Humanitec Organization ID" + type = string +} + +variable "humanitec_ci_service_user_token" { + description = "Humanitec CI Service User Token" + type = string + sensitive = true +} + +variable "resource_packs_aws_rev" { + description = "Revision of the resource-packs-aws repository to use" + type = string + default = "refs/heads/main" +} + +variable "vm_size" { + description = "The Azure VM instances type to use as \"Agents\" (aka Kubernetes Nodes) in AKS" + type = string + default = "Standard_D2_v2" +} diff --git a/modules/base/README.md b/modules/base/README.md index b4a76b1..f1c0375 100644 --- a/modules/base/README.md +++ b/modules/base/README.md @@ -13,6 +13,7 @@ Module that provides the reference architecture. | [azurerm](#requirement\_azurerm) | ~> 3.87 | | [helm](#requirement\_helm) | ~> 2.12 | | [humanitec](#requirement\_humanitec) | ~> 0 | +| [random](#requirement\_random) | ~> 3.5 | ## Providers @@ -22,6 +23,7 @@ Module that provides the reference architecture. | [azurerm](#provider\_azurerm) | ~> 3.87 | | [helm](#provider\_helm) | ~> 2.12 | | [humanitec](#provider\_humanitec) | ~> 0 | +| [random](#provider\_random) | ~> 3.5 | ## Modules @@ -38,6 +40,7 @@ Module that provides the reference architecture. | [azuread_group_member.cluster_admins](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group_member) | resource | | [azuread_service_principal.humanitec](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/service_principal) | resource | | [azuread_service_principal_password.humanitec](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/service_principal_password) | resource | +| [azurerm_container_registry.acr](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/container_registry) | resource | | [azurerm_public_ip.ingress](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | | [azurerm_resource_group.main](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | | [azurerm_role_assignment.humanitec_cluster_admin_permissions](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | @@ -47,6 +50,7 @@ Module that provides the reference architecture. | [humanitec_resource_definition.k8s_namespace](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition) | resource | | [humanitec_resource_definition_criteria.k8s_cluster_driver](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | | [humanitec_resource_definition_criteria.k8s_namespace](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | +| [random_string.name_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource | | [azuread_client_config.current](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/client_config) | data source | | [azuread_service_principal.aks](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/service_principal) | data source | @@ -57,6 +61,7 @@ Module that provides the reference architecture. | [location](#input\_location) | Azure region to deploy into | `string` | n/a | yes | | [subscription\_id](#input\_subscription\_id) | Azure Subscription (ID) to use | `string` | n/a | yes | | [cluster\_name](#input\_cluster\_name) | Name for the AKS cluster | `string` | `"ref-arch"` | no | +| [container\_registry\_name\_prefix](#input\_container\_registry\_name\_prefix) | Name for Azure Container Registry | `string` | `"humrefarch"` | no | | [environment](#input\_environment) | Name of the environment to be deployed into | `string` | `"development"` | no | | [ingress\_nginx\_min\_unavailable](#input\_ingress\_nginx\_min\_unavailable) | Number of allowed unavaiable replicas for the ingress-nginx controller | `number` | `1` | no | | [ingress\_nginx\_replica\_count](#input\_ingress\_nginx\_replica\_count) | Number of replicas for the ingress-nginx controller | `number` | `2` | no | @@ -70,6 +75,10 @@ Module that provides the reference architecture. | [aks\_cluster\_ca\_certificate](#output\_aks\_cluster\_ca\_certificate) | Base64 encoded certificate data required to communicate with the cluster | | [aks\_host](#output\_aks\_host) | Endpoint for your Kubernetes API server | | [aks\_server\_app\_id](#output\_aks\_server\_app\_id) | Azure Kubernetes Service AAD Server | +| [az\_container\_registry\_id](#output\_az\_container\_registry\_id) | ID of the created azure container registry | +| [az\_container\_registry\_name](#output\_az\_container\_registry\_name) | Name of the created azure container registry | +| [az\_resource\_group\_location](#output\_az\_resource\_group\_location) | Location of the created azure resource group | +| [az\_resource\_group\_name](#output\_az\_resource\_group\_name) | Name of the created azure resource group | | [environment](#output\_environment) | Name of the environment to be deployed into | | [ingress\_nginx\_external\_ip](#output\_ingress\_nginx\_external\_ip) | External IP address for the Nginx ingress controller | diff --git a/modules/base/acr.tf b/modules/base/acr.tf new file mode 100644 index 0000000..0a1b832 --- /dev/null +++ b/modules/base/acr.tf @@ -0,0 +1,13 @@ +resource "random_string" "name_suffix" { + length = 6 + special = false + upper = false +} + +resource "azurerm_container_registry" "acr" { + name = "${var.container_registry_name_prefix}${random_string.name_suffix.result}" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + sku = "Premium" + admin_enabled = false +} diff --git a/modules/base/main.tf b/modules/base/main.tf index 733f2f4..3105869 100644 --- a/modules/base/main.tf +++ b/modules/base/main.tf @@ -54,6 +54,10 @@ module "azure_aks" { agents_size = var.vm_size node_os_channel_upgrade = "NodeImage" + + attached_acr_id_map = { + main = azurerm_container_registry.acr.id + } } # Service Principal used by Humanitec to access the AKS cluster diff --git a/modules/base/outputs.tf b/modules/base/outputs.tf index a24c0c8..c3269f1 100644 --- a/modules/base/outputs.tf +++ b/modules/base/outputs.tf @@ -5,6 +5,26 @@ output "environment" { value = var.environment } +output "az_resource_group_name" { + description = "Name of the created azure resource group" + value = azurerm_resource_group.main.name +} + +output "az_resource_group_location" { + description = "Location of the created azure resource group" + value = azurerm_resource_group.main.location +} + +output "az_container_registry_name" { + description = "Name of the created azure container registry" + value = azurerm_container_registry.acr.name +} + +output "az_container_registry_id" { + description = "ID of the created azure container registry" + value = azurerm_container_registry.acr.id +} + # AKS outputs output "aks_host" { diff --git a/modules/base/providers.tf b/modules/base/providers.tf index 7c17168..48e3d1d 100644 --- a/modules/base/providers.tf +++ b/modules/base/providers.tf @@ -20,6 +20,10 @@ terraform { source = "humanitec/humanitec" version = "~> 0" } + random = { + source = "hashicorp/random" + version = "~> 3.5" + } } required_version = ">= 1.3.0" } diff --git a/modules/base/variables.tf b/modules/base/variables.tf index 4969ca4..a2e1279 100644 --- a/modules/base/variables.tf +++ b/modules/base/variables.tf @@ -21,6 +21,12 @@ variable "cluster_name" { default = "ref-arch" } +variable "container_registry_name_prefix" { + description = "Name for Azure Container Registry" + type = string + default = "humrefarch" +} + variable "vm_size" { description = "The Azure VM instances type to use as \"Agents\" (aka Kubernetes Nodes) in AKS" type = string