diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 90eb569..fedee6e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,30 +20,22 @@ jobs: - name: Install terraform-docs run: | WORK_DIR=$(mktemp -d) - curl -Lo ${WORK_DIR}/terraform-docs.tar.gz https://github.com/terraform-docs/terraform-docs/releases/download/v0.16.0/terraform-docs-v0.16.0-$(uname)-amd64.tar.gz + curl -Lo ${WORK_DIR}/terraform-docs.tar.gz https://github.com/terraform-docs/terraform-docs/releases/download/v0.18.0/terraform-docs-v0.18.0-$(uname)-amd64.tar.gz cd ${WORK_DIR} tar -xzf terraform-docs.tar.gz chmod +x terraform-docs mv terraform-docs /usr/local/bin/terraform-docs - - name: Generate docs run: make docs - name: Check git diff is clean (all files generated should be committed) run: git diff --exit-code - - 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 - - uses: terraform-linters/setup-tflint@v4 with: - tflint_version: v0.49.0 - - - name: Lint + tflint_version: v0.51.1 + - name: Terraform Lint run: make lint + + - name: Terraform Validate + run: make validate diff --git a/.tflint.hcl b/.tflint.hcl index 8188d5b..eb1d742 100644 --- a/.tflint.hcl +++ b/.tflint.hcl @@ -5,6 +5,6 @@ plugin "terraform" { plugin "azurerm" { enabled = true - version = "0.25.1" + version = "0.26.0" source = "github.com/terraform-linters/tflint-ruleset-azurerm" } diff --git a/Makefile b/Makefile index d090076..7d3b704 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,18 @@ TF_DIRS = $(patsubst %/main.tf, %, $(shell find . -type d -name .terraform -prune -o -name 'main.tf' -print)) VALIDATE_TF_DIRS = $(addprefix validate-,$(TF_DIRS)) LINT_TF_DIRS = $(addprefix lint-,$(TF_DIRS)) +DOCS_TF_DIRS = $(addprefix docs-,$(TF_DIRS)) + +# Generate docs for a terraform directories +$(DOCS_TF_DIRS): docs-%: + @echo "Docs $*" + terraform-docs --config docs/.terraform-docs.yaml $* + terraform-docs --config docs/.terraform-docs-example.yaml $* # Generate docs .PHONY: docs -docs: - terraform-docs --lockfile=false ./modules/base - terraform-docs --config docs/.terraform-docs.yaml . - terraform-docs --config docs/.terraform-docs-example.yaml . - terraform-docs --config docs/.terraform-docs.yaml ./examples/with-backstage - terraform-docs --config docs/.terraform-docs-example.yaml ./examples/with-backstage +docs: $(DOCS_TF_DIRS) + @echo "All docs generated" # Format all terraform files fmt: @@ -39,5 +42,5 @@ lint-init: tflint --init # Lint all terraform directories -lint: lint-init $(LINT_TF_DIRS) +lint: lint-init $(LINT_TF_DIRS) fmt-check @echo "All linted" diff --git a/README.md b/README.md index c340ba4..ca982c2 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ This plane is where the actual infrastructure exists including clusters, databas ## How to spin up your Humanitec Azure Reference Architecture -This repo contains an implementation of part of the Humanitec Reference Architecture for an Internal Developer Platform. +This repo contains an implementation of part of the Humanitec Reference Architecture for an Internal Developer Platform, including Backstage as optional Portal solution. This repo covers the base layer of the implementation for Azure. @@ -98,13 +98,13 @@ This reference architecture implementation uses Terraform. You will need to do t For example: - ``` + ```shell export HUMANITEC_TOKEN="my-humanitec-api-token" ``` 5. Run terraform: - ``` + ```shell terraform init terraform plan terraform apply @@ -112,7 +112,7 @@ This reference architecture implementation uses Terraform. You will need to do t `terraform plan` and `apply` might output this message: - ``` + ```shell │ Warning: Argument is deprecated │ │ with module.base.module.azure_aks.azurerm_kubernetes_cluster.main, @@ -136,13 +136,13 @@ Check for the existence of key elements of the reference architecture. This is a 1. Set the `HUMANITEC_ORG` environment variable to the ID of your Humanitec Organization (must be all lowercase): - ``` + ```shell export HUMANITEC_ORG="my-humanitec-org" ``` 2. Verify the existence of the Resource Definition for the AKS cluster in your Humanitec Organization: - ``` + ```shell curl -s https://api.humanitec.io/orgs/${HUMANITEC_ORG}/resources/defs/ref-arch \ --header "Authorization: Bearer ${HUMANITEC_TOKEN}" \ | jq .id,.type @@ -150,20 +150,20 @@ Check for the existence of key elements of the reference architecture. This is a This should output: - ``` + ```shell "ref-arch" "k8s-cluster" ``` 3. Verify the existence of the newly created AKS cluster: - ``` + ```shell az aks list --subscription ``` This should output: - ``` + ```shell [ { ... various properties ... @@ -173,17 +173,56 @@ Check for the existence of key elements of the reference architecture. This is a ] ``` +### Enable a portal (optional) + +#### Portal Prerequisites + +Backstage requires a GitHub connection, which in turn needs: + +* 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. + + ```shell + export GITHUB_TOKEN="my-github-token" + ``` + +* Set the `GITHUB_ORG_ID` environment variable to your GitHub organization ID. + + ```shell + export GITHUB_ORG_ID="my-github-org-id" + ``` + +* Install the GitHub App for Backstage into your GitHub organization + * Run `docker run --rm -it -e GITHUB_ORG_ID -v $(pwd):/pwd -p 127.0.0.1:3000:3000 ghcr.io/humanitec-architecture/create-gh-app` ([image source](https://github.com/humanitec-architecture/create-gh-app/)) and follow the instructions: + * “All repositories” ~> Install + * “Okay, […] was installed on the […] account.” ~> You can close the window and server. + +#### Portal Usage + +* Enable `with_backstage` inside your `terraform.tfvars` and configure the additional variables that a required for Backstage. +* Perform another `terraform apply` + +#### Verify portal setup + +* [Fetch the DNS entry](https://developer.humanitec.com/score/getting-started/get-dns/) of the Humanitec Application `backstage`, Environment `development`. +* Open the host in your browser. +* Click the "Create" button and scaffold your first application. + ### 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. Ensure you are (still) logged in with `az`. +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 the Portal, if you used one, but not the `backstage` app itself. +2. Ensure you are (still) logged in with `az`. -2. Ensure you still have the `HUMANITEC_TOKEN` environment variable set to an appropriate Humanitec API token with the `Administrator` role on the Humanitec Organization. +3. Ensure you still have the `HUMANITEC_TOKEN` environment variable set to an appropriate Humanitec API token with the `Administrator` role on the Humanitec Organization. -3. Run terraform: +4. Run terraform: - ``` + ```shell terraform destroy ``` @@ -198,24 +237,44 @@ Once you are finished with the reference architecture, you can remove all provis | azapi | ~> 1.11 | | azuread | ~> 2.47 | | azurerm | ~> 3.87 | +| github | ~> 5.38 | | helm | ~> 2.12 | | humanitec | ~> 1.0 | | kubernetes | ~> 2.25 | +| random | ~> 3.5 | + +### Providers + +| Name | Version | +|------|---------| +| humanitec | ~> 1.0 | ### Modules | Name | Source | Version | |------|--------|---------| | base | ./modules/base | n/a | +| github | ./modules/github | n/a | +| github\_app | github.com/humanitec-architecture/shared-terraform-modules | v2024-06-12//modules/github-app | +| portal\_backstage | ./modules/portal-backstage | n/a | + +### Resources + +| Name | Type | +|------|------| +| [humanitec_service_user_token.deployer](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/service_user_token) | resource | +| [humanitec_user.deployer](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/user) | resource | ### Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| humanitec\_org\_id | Humanitec Organization ID | `string` | n/a | yes | | location | Azure region to deploy into | `string` | n/a | yes | | subscription\_id | Azure Subscription (ID) to use | `string` | n/a | yes | +| github\_org\_id | GitHub org id (required for Backstage) | `string` | `null` | no | +| humanitec\_org\_id | Humanitec Organization ID (required for Backstage) | `string` | `null` | no | | vm\_size | The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS | `string` | `"Standard_D2_v2"` | no | +| with\_backstage | Deploy Backstage | `bool` | `false` | no | ### Outputs diff --git a/examples/with-backstage/README.md b/examples/with-backstage/README.md deleted file mode 100644 index 5d586d2..0000000 --- a/examples/with-backstage/README.md +++ /dev/null @@ -1,161 +0,0 @@ -# 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 | -| azapi | ~> 1.11 | -| azuread | ~> 2.47 | -| azurerm | ~> 3.87 | -| github | ~> 5.38 | -| helm | ~> 2.12 | -| humanitec | ~> 1.0 | -| kubernetes | ~> 2.25 | -| random | ~> 3.5 | - -### Providers - -| Name | Version | -|------|---------| -| azurerm | ~> 3.87 | -| github | ~> 5.38 | -| humanitec | ~> 1.0 | -| random | ~> 3.5 | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| backstage\_mysql | git::https://github.com/humanitec-architecture/resource-packs-in-cluster.git//humanitec-resource-defs/mysql/basic | main | -| backstage\_postgres | git::https://github.com/humanitec-architecture/resource-packs-in-cluster.git//humanitec-resource-defs/postgres/basic | main | -| base | ../../modules/base | n/a | - -### Resources - -| Name | Type | -|------|------| -| [azurerm_federated_identity_credential.github_oidc_identity](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/federated_identity_credential) | resource | -| [azurerm_role_assignment.github_oidc_identity_acr](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | -| [azurerm_user_assigned_identity.github_oidc_identity](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity) | 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_acr_name](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | -| [github_actions_organization_variable.backstage_azure_client_id](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | -| [github_actions_organization_variable.backstage_azure_subscription_id](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | -| [github_actions_organization_variable.backstage_azure_tenant_id](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_actions_repository_oidc_subject_claim_customization_template.backstage](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_repository_oidc_subject_claim_customization_template) | 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_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_value.app_config_backend_auth_keys](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 | -| [random_bytes.backstage_service_to_service_auth_key](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/bytes) | resource | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| 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 | -| location | Azure region to deploy into | `string` | n/a | yes | -| subscription\_id | Azure Subscription (ID) to use | `string` | n/a | yes | -| vm\_size | The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS | `string` | `"Standard_D2_v2"` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| aks\_cluster\_issuer\_url | Issuer URL for the OpenID Connect discovery endpoint | - diff --git a/examples/with-backstage/backstage-github.tf b/examples/with-backstage/backstage-github.tf deleted file mode 100644 index 8d6001a..0000000 --- a/examples/with-backstage/backstage-github.tf +++ /dev/null @@ -1,86 +0,0 @@ -# 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 deleted file mode 100644 index 54ac2d4..0000000 --- a/examples/with-backstage/backstage-humanitec.tf +++ /dev/null @@ -1,132 +0,0 @@ -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 -} - -resource "random_bytes" "backstage_service_to_service_auth_key" { - length = 24 -} - -resource "humanitec_value" "app_config_backend_auth_keys" { - app_id = humanitec_application.backstage.id - key = "APP_CONFIG_backend_auth_keys" - description = "Backstage service-to-service-auth keys" - value = jsonencode([{ - secret = random_bytes.backstage_service_to_service_auth_key.base64 - }]) - is_secret = true -} - -# Configure required resources for backstage - -locals { - res_def_prefix = "backstage-" -} - -# in-cluster postgres - -module "backstage_postgres" { - # Not pinned as we don't have a release yet - # tflint-ignore: terraform_module_pinned_source - source = "git::https://github.com/humanitec-architecture/resource-packs-in-cluster.git//humanitec-resource-defs/postgres/basic?ref=main" - - 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" { - # Not pinned as we don't have a release yet - # tflint-ignore: terraform_module_pinned_source - source = "git::https://github.com/humanitec-architecture/resource-packs-in-cluster.git//humanitec-resource-defs/mysql/basic?ref=main" - - 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 deleted file mode 100644 index f7faadc..0000000 --- a/examples/with-backstage/create-gh-app/index.js +++ /dev/null @@ -1,154 +0,0 @@ -// 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 deleted file mode 100644 index ea933c2..0000000 --- a/examples/with-backstage/main.tf +++ /dev/null @@ -1,9 +0,0 @@ -# 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/outputs.tf b/examples/with-backstage/outputs.tf deleted file mode 100644 index 68cdfce..0000000 --- a/examples/with-backstage/outputs.tf +++ /dev/null @@ -1,4 +0,0 @@ -output "aks_cluster_issuer_url" { - description = "Issuer URL for the OpenID Connect discovery endpoint" - value = module.base.aks_oidc_issuer_url -} diff --git a/examples/with-backstage/terraform.tfvars.example b/examples/with-backstage/terraform.tfvars.example deleted file mode 100644 index e1a20a3..0000000 --- a/examples/with-backstage/terraform.tfvars.example +++ /dev/null @@ -1,18 +0,0 @@ - -# GitHub org id -github_org_id = "" - -# Humanitec CI Service User Token -humanitec_ci_service_user_token = "" - -# Humanitec Organization ID -humanitec_org_id = "" - -# Azure region to deploy into -location = "" - -# Azure Subscription (ID) to use -subscription_id = "" - -# The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS -vm_size = "Standard_D2_v2" \ No newline at end of file diff --git a/examples/with-backstage/variables.tf b/examples/with-backstage/variables.tf deleted file mode 100644 index e4c9835..0000000 --- a/examples/with-backstage/variables.tf +++ /dev/null @@ -1,31 +0,0 @@ -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 "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/main.tf b/main.tf index 8f6a97b..8fecc36 100644 --- a/main.tf +++ b/main.tf @@ -1,81 +1,78 @@ +# Azure reference architecture -terraform { - required_providers { - azapi = { - source = "Azure/azapi" - version = "~> 1.11" - } - azuread = { - source = "hashicorp/azuread" - version = "~> 2.47" - } - azurerm = { - source = "hashicorp/azurerm" - version = "~> 3.87" - } - helm = { - source = "hashicorp/helm" - version = "~> 2.12" - } - humanitec = { - source = "humanitec/humanitec" - version = "~> 1.0" - } - kubernetes = { - source = "hashicorp/kubernetes" - version = "~> 2.25" - } - } - required_version = ">= 1.3.0" -} +module "base" { + source = "./modules/base" -provider "humanitec" { - org_id = var.humanitec_org_id + subscription_id = var.subscription_id + location = var.location + vm_size = var.vm_size } -provider "azapi" { - subscription_id = var.subscription_id +# User used for scaffolding and deploying apps + +resource "humanitec_user" "deployer" { + count = var.with_backstage ? 1 : 0 + + name = "deployer" + role = "administrator" + type = "service" } -provider "azurerm" { - features {} +resource "humanitec_service_user_token" "deployer" { + count = var.with_backstage ? 1 : 0 - subscription_id = var.subscription_id + id = "deployer" + user_id = humanitec_user.deployer[0].id + description = "Used by scaffolding and deploying" } -provider "azuread" { +module "github" { + count = var.with_backstage ? 1 : 0 + + source = "./modules/github" + + humanitec_org_id = var.humanitec_org_id + humanitec_ci_service_user_token = humanitec_service_user_token.deployer[0].token + subscription_id = var.subscription_id + az_resource_group_location = module.base.az_resource_group_location + az_resource_group_name = module.base.az_resource_group_name + az_container_registry_id = module.base.az_container_registry_id + az_container_registry_name = module.base.az_container_registry_name + github_org_id = var.github_org_id + + depends_on = [module.base] } -module "base" { - source = "./modules/base" +# Configure GitHub variables & secrets for Backstage itself and for all scaffolded apps - subscription_id = var.subscription_id - location = var.location - vm_size = var.vm_size +locals { + github_app_credentials_file = "github-app-credentials.json" } -provider "kubernetes" { - host = module.base.aks_host +module "github_app" { + count = var.with_backstage ? 1 : 0 - cluster_ca_certificate = base64decode(module.base.aks_cluster_ca_certificate) + source = "github.com/humanitec-architecture/shared-terraform-modules?ref=v2024-06-12//modules/github-app" - 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"] - } + credentials_file = "${path.module}/${local.github_app_credentials_file}" } -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"] - } - } +# Deploy Backstage as Portal + +module "portal_backstage" { + count = var.with_backstage ? 1 : 0 + + source = "./modules/portal-backstage" + + humanitec_org_id = var.humanitec_org_id + humanitec_ci_service_user_token = humanitec_service_user_token.deployer[0].token + + github_org_id = var.github_org_id + github_app_client_id = module.github_app[0].client_id + github_app_client_secret = module.github_app[0].client_secret + github_app_id = module.github_app[0].app_id + github_app_private_key = module.github_app[0].private_key + github_webhook_secret = module.github_app[0].webhook_secret + + depends_on = [module.github] } diff --git a/modules/base/README.md b/modules/base/README.md index a125663..8e57735 100644 --- a/modules/base/README.md +++ b/modules/base/README.md @@ -3,35 +3,37 @@ Module that provides the reference architecture. -## Requirements +### Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.0 | -| [azapi](#requirement\_azapi) | ~> 1.11 | -| [azuread](#requirement\_azuread) | ~> 2.47 | -| [azurerm](#requirement\_azurerm) | ~> 3.87 | -| [helm](#requirement\_helm) | ~> 2.12 | -| [humanitec](#requirement\_humanitec) | ~> 1.0 | -| [random](#requirement\_random) | ~> 3.5 | +| terraform | >= 1.3.0 | +| azapi | ~> 1.11 | +| azuread | ~> 2.47 | +| azurerm | ~> 3.87 | +| helm | ~> 2.12 | +| humanitec | ~> 1.0 | +| random | ~> 3.5 | -## Providers +### Providers | Name | Version | |------|---------| -| [azuread](#provider\_azuread) | ~> 2.47 | -| [azurerm](#provider\_azurerm) | ~> 3.87 | -| [helm](#provider\_helm) | ~> 2.12 | -| [humanitec](#provider\_humanitec) | ~> 1.0 | -| [random](#provider\_random) | ~> 3.5 | +| azuread | ~> 2.47 | +| azurerm | ~> 3.87 | +| helm | ~> 2.12 | +| humanitec | ~> 1.0 | +| random | ~> 3.5 | -## Modules +### Modules | Name | Source | Version | |------|--------|---------| -| [azure\_aks](#module\_azure\_aks) | Azure/aks/azurerm | ~> 7 | +| azure\_aks | Azure/aks/azurerm | ~> 7 | +| default\_mysql | github.com/humanitec-architecture/resource-packs-in-cluster | v2024-06-05//humanitec-resource-defs/mysql/basic | +| default\_postgres | github.com/humanitec-architecture/resource-packs-in-cluster | v2024-06-05//humanitec-resource-defs/postgres/basic | -## Resources +### Resources | Name | Type | |------|------| @@ -49,38 +51,40 @@ Module that provides the reference architecture. | [humanitec_resource_account.cluster_account](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_account) | resource | | [humanitec_resource_definition.k8s_cluster_driver](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition) | resource | | [humanitec_resource_definition.k8s_namespace](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition) | resource | +| [humanitec_resource_definition_criteria.default_mysql](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | +| [humanitec_resource_definition_criteria.default_postgres](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | 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 | -## Inputs +### Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [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 | -| [resource\_group\_name](#input\_resource\_group\_name) | Name of the resource group to create | `string` | `"ref-arch"` | no | -| [vm\_size](#input\_vm\_size) | The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS | `string` | `"Standard_D2_v2"` | no | +| location | Azure region to deploy into | `string` | n/a | yes | +| subscription\_id | Azure Subscription (ID) to use | `string` | n/a | yes | +| cluster\_name | Name for the AKS cluster | `string` | `"ref-arch"` | no | +| container\_registry\_name\_prefix | Name for Azure Container Registry | `string` | `"humrefarch"` | no | +| environment | Name of the environment to be deployed into | `string` | `"development"` | no | +| ingress\_nginx\_min\_unavailable | Number of allowed unavaiable replicas for the ingress-nginx controller | `number` | `1` | no | +| ingress\_nginx\_replica\_count | Number of replicas for the ingress-nginx controller | `number` | `2` | no | +| resource\_group\_name | Name of the resource group to create | `string` | `"ref-arch"` | no | +| vm\_size | The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS | `string` | `"Standard_D2_v2"` | no | -## Outputs +### Outputs | Name | Description | |------|-------------| -| [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\_oidc\_issuer\_url](#output\_aks\_oidc\_issuer\_url) | Issuer URL for the OpenID Connect discovery endpoint | -| [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 | +| aks\_cluster\_ca\_certificate | Base64 encoded certificate data required to communicate with the cluster | +| aks\_host | Endpoint for your Kubernetes API server | +| aks\_oidc\_issuer\_url | Issuer URL for the OpenID Connect discovery endpoint | +| aks\_server\_app\_id | Azure Kubernetes Service AAD Server | +| az\_container\_registry\_id | ID of the created azure container registry | +| az\_container\_registry\_name | Name of the created azure container registry | +| az\_resource\_group\_location | Location of the created azure resource group | +| az\_resource\_group\_name | Name of the created azure resource group | +| environment | Name of the environment to be deployed into | +| ingress\_nginx\_external\_ip | External IP address for the Nginx ingress controller | diff --git a/modules/base/humanitec.tf b/modules/base/humanitec.tf index 056a4ac..ed4dd66 100644 --- a/modules/base/humanitec.tf +++ b/modules/base/humanitec.tf @@ -52,3 +52,27 @@ resource "humanitec_resource_definition" "k8s_namespace" { resource "humanitec_resource_definition_criteria" "k8s_namespace" { resource_definition_id = humanitec_resource_definition.k8s_namespace.id } + +# in-cluster postgres + +module "default_postgres" { + source = "github.com/humanitec-architecture/resource-packs-in-cluster?ref=v2024-06-05//humanitec-resource-defs/postgres/basic" + + prefix = "default-" +} + +resource "humanitec_resource_definition_criteria" "default_postgres" { + resource_definition_id = module.default_postgres.id + env_type = var.environment +} + +module "default_mysql" { + source = "github.com/humanitec-architecture/resource-packs-in-cluster?ref=v2024-06-05//humanitec-resource-defs/mysql/basic" + + prefix = "default-" +} + +resource "humanitec_resource_definition_criteria" "default_mysql" { + resource_definition_id = module.default_mysql.id + env_type = var.environment +} diff --git a/modules/base/terraform.tfvars.example b/modules/base/terraform.tfvars.example new file mode 100644 index 0000000..7618d8f --- /dev/null +++ b/modules/base/terraform.tfvars.example @@ -0,0 +1,27 @@ + +# Name for the AKS cluster +cluster_name = "ref-arch" + +# Name for Azure Container Registry +container_registry_name_prefix = "humrefarch" + +# Name of the environment to be deployed into +environment = "development" + +# Number of allowed unavaiable replicas for the ingress-nginx controller +ingress_nginx_min_unavailable = 1 + +# Number of replicas for the ingress-nginx controller +ingress_nginx_replica_count = 2 + +# Azure region to deploy into +location = "" + +# Name of the resource group to create +resource_group_name = "ref-arch" + +# Azure Subscription (ID) to use +subscription_id = "" + +# The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS +vm_size = "Standard_D2_v2" \ No newline at end of file diff --git a/modules/github/README.md b/modules/github/README.md new file mode 100644 index 0000000..d921c38 --- /dev/null +++ b/modules/github/README.md @@ -0,0 +1,44 @@ + +### Requirements + +| Name | Version | +|------|---------| +| terraform | >= 1.3.0 | +| azurerm | ~> 3.87 | +| github | ~> 5.38 | + +### Providers + +| Name | Version | +|------|---------| +| azurerm | ~> 3.87 | +| github | ~> 5.38 | + +### Resources + +| Name | Type | +|------|------| +| [azurerm_federated_identity_credential.github_oidc_identity](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/federated_identity_credential) | resource | +| [azurerm_role_assignment.github_oidc_identity_acr](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource | +| [azurerm_user_assigned_identity.github_oidc_identity](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity) | 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_acr_name](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_azure_client_id](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_azure_subscription_id](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_variable) | resource | +| [github_actions_organization_variable.backstage_azure_tenant_id](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 | + +### Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| az\_container\_registry\_id | ID of the created azure container registry | `string` | n/a | yes | +| az\_container\_registry\_name | Name of the created azure container registry | `string` | n/a | yes | +| az\_resource\_group\_location | Azure region to deploy into | `string` | n/a | yes | +| az\_resource\_group\_name | Name of the resource group | `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 | +| subscription\_id | Azure Subscription (ID) to use | `string` | n/a | yes | + \ No newline at end of file diff --git a/examples/with-backstage/azure-github.tf b/modules/github/azure.tf similarity index 80% rename from examples/with-backstage/azure-github.tf rename to modules/github/azure.tf index 419a7ab..781ed20 100644 --- a/examples/with-backstage/azure-github.tf +++ b/modules/github/azure.tf @@ -1,6 +1,5 @@ locals { name = "gha-acr-push" - cloud_provider = "azure" github_issuer_url = "https://token.actions.githubusercontent.com" } @@ -9,13 +8,13 @@ locals { 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 + location = var.az_resource_group_location + resource_group_name = var.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 + resource_group_name = var.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 @@ -23,7 +22,7 @@ resource "azurerm_federated_identity_credential" "github_oidc_identity" { } resource "azurerm_role_assignment" "github_oidc_identity_acr" { - scope = module.base.az_container_registry_id + scope = var.az_container_registry_id role_definition_name = "AcrPush" principal_id = azurerm_user_assigned_identity.github_oidc_identity.principal_id } diff --git a/modules/github/main.tf b/modules/github/main.tf new file mode 100644 index 0000000..5d8eb47 --- /dev/null +++ b/modules/github/main.tf @@ -0,0 +1,47 @@ +locals { + cloud_provider = "azure" +} + +# Configure GitHub variables & secrets for all scaffolded apps + +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 = var.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 +} diff --git a/modules/github/providers.tf b/modules/github/providers.tf new file mode 100644 index 0000000..bae5e1d --- /dev/null +++ b/modules/github/providers.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.87" + } + github = { + source = "integrations/github" + version = "~> 5.38" + } + } + required_version = ">= 1.3.0" +} diff --git a/modules/github/terraform.tfvars.example b/modules/github/terraform.tfvars.example new file mode 100644 index 0000000..49e736f --- /dev/null +++ b/modules/github/terraform.tfvars.example @@ -0,0 +1,24 @@ + +# ID of the created azure container registry +az_container_registry_id = "" + +# Name of the created azure container registry +az_container_registry_name = "" + +# Azure region to deploy into +az_resource_group_location = "" + +# Name of the resource group +az_resource_group_name = "" + +# GitHub org id +github_org_id = "" + +# Humanitec CI Service User Token +humanitec_ci_service_user_token = "" + +# Humanitec Organization ID +humanitec_org_id = "" + +# Azure Subscription (ID) to use +subscription_id = "" \ No newline at end of file diff --git a/modules/github/variables.tf b/modules/github/variables.tf new file mode 100644 index 0000000..7abbc05 --- /dev/null +++ b/modules/github/variables.tf @@ -0,0 +1,55 @@ +variable "subscription_id" { + description = "Azure Subscription (ID) to use" + type = string +} + +variable "az_resource_group_location" { + description = "Azure region to deploy into" + type = string +} + +variable "az_resource_group_name" { + description = "Name of the resource group" + type = string +} + +variable "az_container_registry_id" { + description = "ID of the created azure container registry" + type = string +} + +variable "az_container_registry_name" { + description = "Name of the created azure container registry" + type = string +} + +variable "humanitec_org_id" { + description = "Humanitec Organization ID" + type = string + + validation { + condition = var.humanitec_org_id != null + error_message = "Humanitec Organization ID must not be empty" + } +} + +variable "humanitec_ci_service_user_token" { + description = "Humanitec CI Service User Token" + type = string + sensitive = true + + validation { + condition = var.humanitec_ci_service_user_token != null + error_message = "Humanitec CI Service User Token must not be empty" + } +} + +variable "github_org_id" { + description = "GitHub org id" + type = string + + validation { + condition = var.github_org_id != null + error_message = "GitHub org id must not be empty" + } +} diff --git a/modules/portal-backstage/README.md b/modules/portal-backstage/README.md new file mode 100644 index 0000000..dc153bd --- /dev/null +++ b/modules/portal-backstage/README.md @@ -0,0 +1,41 @@ + +### Requirements + +| Name | Version | +|------|---------| +| terraform | >= 1.3.0 | +| humanitec | ~> 1.0 | + +### Providers + +| Name | Version | +|------|---------| +| humanitec | ~> 1.0 | + +### Modules + +| Name | Source | Version | +|------|--------|---------| +| backstage\_postgres | github.com/humanitec-architecture/resource-packs-in-cluster | v2024-06-05//humanitec-resource-defs/postgres/basic | +| portal\_backstage | github.com/humanitec-architecture/shared-terraform-modules | v2024-06-12//modules/portal-backstage | + +### Resources + +| Name | Type | +|------|------| +| [humanitec_application.backstage](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/application) | resource | +| [humanitec_resource_definition_criteria.backstage_postgres](https://registry.terraform.io/providers/humanitec/humanitec/latest/docs/resources/resource_definition_criteria) | resource | + +### Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| github\_app\_client\_id | GitHub App Client ID | `string` | n/a | yes | +| github\_app\_client\_secret | GitHub App Client Secret | `string` | n/a | yes | +| github\_app\_id | GitHub App ID | `string` | n/a | yes | +| github\_app\_private\_key | GitHub App Private Key | `string` | n/a | yes | +| github\_org\_id | GitHub org id | `string` | n/a | yes | +| github\_webhook\_secret | GitHub Webhook Secret | `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 | + \ No newline at end of file diff --git a/modules/portal-backstage/main.tf b/modules/portal-backstage/main.tf new file mode 100644 index 0000000..4026198 --- /dev/null +++ b/modules/portal-backstage/main.tf @@ -0,0 +1,62 @@ +resource "humanitec_application" "backstage" { + id = "backstage" + name = "backstage" +} + +locals { + secrets = { + humanitec-token = var.humanitec_ci_service_user_token + github-app-client-id = var.github_app_client_id + github-app-client-secret = var.github_app_client_secret + github-app-private-key = indent(2, var.github_app_private_key) + github-webhook-secret = var.github_webhook_secret + } + + secret_refs = { + for key, value in local.secrets : key => { + value = value + } + } +} + +module "portal_backstage" { + source = "github.com/humanitec-architecture/shared-terraform-modules?ref=v2024-06-12//modules/portal-backstage" + + cloud_provider = "azure" + + humanitec_org_id = var.humanitec_org_id + humanitec_app_id = humanitec_application.backstage.id + humanitec_ci_service_user_token_ref = local.secret_refs["humanitec-token"] + + github_org_id = var.github_org_id + github_app_client_id_ref = local.secret_refs["github-app-client-id"] + github_app_client_secret_ref = local.secret_refs["github-app-client-secret"] + github_app_id = var.github_app_id + github_app_private_key_ref = local.secret_refs["github-app-private-key"] + github_webhook_secret_ref = local.secret_refs["github-webhook-secret"] + + # 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 + backstage_repo_custom_claim_keys = ["repository_owner"] +} + +# Configure required resources for backstage + +locals { + res_def_prefix = "backstage-" +} + +# in-cluster postgres + +module "backstage_postgres" { + source = "github.com/humanitec-architecture/resource-packs-in-cluster?ref=v2024-06-05//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 +} diff --git a/modules/portal-backstage/providers.tf b/modules/portal-backstage/providers.tf new file mode 100644 index 0000000..59d13ba --- /dev/null +++ b/modules/portal-backstage/providers.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + humanitec = { + source = "humanitec/humanitec" + version = "~> 1.0" + } + } + required_version = ">= 1.3.0" +} diff --git a/modules/portal-backstage/terraform.tfvars.example b/modules/portal-backstage/terraform.tfvars.example new file mode 100644 index 0000000..8b207b4 --- /dev/null +++ b/modules/portal-backstage/terraform.tfvars.example @@ -0,0 +1,24 @@ + +# GitHub App Client ID +github_app_client_id = "" + +# GitHub App Client Secret +github_app_client_secret = "" + +# GitHub App ID +github_app_id = "" + +# GitHub App Private Key +github_app_private_key = "" + +# GitHub org id +github_org_id = "" + +# GitHub Webhook Secret +github_webhook_secret = "" + +# Humanitec CI Service User Token +humanitec_ci_service_user_token = "" + +# Humanitec Organization ID +humanitec_org_id = "" \ No newline at end of file diff --git a/modules/portal-backstage/variables.tf b/modules/portal-backstage/variables.tf new file mode 100644 index 0000000..4437cf6 --- /dev/null +++ b/modules/portal-backstage/variables.tf @@ -0,0 +1,40 @@ +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 "github_org_id" { + description = "GitHub org id" + type = string +} + +variable "github_app_client_id" { + description = "GitHub App Client ID" + type = string +} + +variable "github_app_client_secret" { + description = "GitHub App Client Secret" + type = string +} + +variable "github_app_id" { + description = "GitHub App ID" + type = string +} + +variable "github_webhook_secret" { + description = "GitHub Webhook Secret" + type = string +} + +variable "github_app_private_key" { + description = "GitHub App Private Key" + type = string +} diff --git a/examples/with-backstage/provider.tf b/providers.tf similarity index 100% rename from examples/with-backstage/provider.tf rename to providers.tf diff --git a/terraform.tfvars.example b/terraform.tfvars.example index f75eddf..2ebfdbb 100644 --- a/terraform.tfvars.example +++ b/terraform.tfvars.example @@ -1,5 +1,8 @@ -# Humanitec Organization ID +# GitHub org id (required for Backstage) +github_org_id = "" + +# Humanitec Organization ID (required for Backstage) humanitec_org_id = "" # Azure region to deploy into @@ -9,4 +12,7 @@ location = "" subscription_id = "" # The Azure VM instances type to use as "Agents" (aka Kubernetes Nodes) in AKS -vm_size = "Standard_D2_v2" \ No newline at end of file +vm_size = "Standard_D2_v2" + +# Deploy Backstage +with_backstage = false \ No newline at end of file diff --git a/variables.tf b/variables.tf index 7ea1e08..aba8325 100644 --- a/variables.tf +++ b/variables.tf @@ -1,4 +1,3 @@ - variable "subscription_id" { description = "Azure Subscription (ID) to use" type = string @@ -9,13 +8,26 @@ variable "location" { type = string } -variable "humanitec_org_id" { - description = "Humanitec Organization ID" - type = string -} - variable "vm_size" { description = "The Azure VM instances type to use as \"Agents\" (aka Kubernetes Nodes) in AKS" type = string default = "Standard_D2_v2" } + +variable "with_backstage" { + description = "Deploy Backstage" + type = bool + default = false +} + +variable "github_org_id" { + description = "GitHub org id (required for Backstage)" + type = string + default = null +} + +variable "humanitec_org_id" { + description = "Humanitec Organization ID (required for Backstage)" + type = string + default = null +}