diff --git a/.github/workflows/_containerTemplate.yml b/.github/workflows/_containerTemplate.yml index d81d5f3..e657927 100644 --- a/.github/workflows/_containerTemplate.yml +++ b/.github/workflows/_containerTemplate.yml @@ -74,7 +74,7 @@ jobs: - name: Login Container Registry id: registry_login uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 - if: github.event_name != 'pull_request' + # if: github.event_name != 'pull_request' with: registry: ${{ inputs.registry_uri }} username: ${{ secrets.USER_NAME }} @@ -101,7 +101,7 @@ jobs: with: context: ${{ inputs.working_directory }} file: ${{ inputs.working_directory }}/Dockerfile - push: ${{ github.event_name != 'pull_request' }} + push: true # ${{ github.event_name != 'pull_request' }} tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} cache-from: type=gha diff --git a/.github/workflows/container-ado.yml b/.github/workflows/container-ado.yml new file mode 100644 index 0000000..5007150 --- /dev/null +++ b/.github/workflows/container-ado.yml @@ -0,0 +1,30 @@ +name: Container ADO +on: + push: + branches: + - main + paths: + - "code/container-ado/**" + + pull_request: + branches: + - main + paths: + - "code/container-ado/**" + + schedule: + - cron: '0 0 * * *' + +jobs: + build_and_publish: + uses: ./.github/workflows/_containerTemplate.yml + name: "Build & Publish" + with: + environment: "dev" + working_directory: "./code/container-ado" + registry_uri: "ghcr.io" + image_namespace_name: "PerfectThymeTech" + image_name: "AzureDevOpsAgentAzure" + secrets: + USER_NAME: ${{ github.actor }} + PASSWORD: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/terraform-ado.yml b/.github/workflows/terraform-ado.yml new file mode 100644 index 0000000..423bc6a --- /dev/null +++ b/.github/workflows/terraform-ado.yml @@ -0,0 +1,32 @@ +name: Infrastructure Deployment - ADO +on: + push: + branches: + - main + paths: + - "code/infra-ado/**" + - ".github/workflows/terraform-ado.yml" + + pull_request: + branches: + - main + paths: + - "code/infra-ado/**" + - ".github/workflows/terraform-ado.yml" + +jobs: + terraform_dev: + uses: ./.github/workflows/_terraformEnvironmentTemplate.yml + name: "Dev" + with: + environment: "dev" + config: "PerfectThymeTech-ado" + terraform_version: "1.14.4" + node_version: 24 + working_directory: "./code/infra-ado" + tenant_id: "37963dd4-f4e6-40f8-a7d6-24b97919e452" + subscription_id: "e82c5267-9dc4-4f45-ac13-abdd5e130d27" + secrets: + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/code/container-ado/Dockerfile b/code/container-ado/Dockerfile new file mode 100644 index 0000000..ac4ca7f --- /dev/null +++ b/code/container-ado/Dockerfile @@ -0,0 +1,33 @@ +FROM myoung34/github-runner-base:ubuntu-noble +LABEL maintainer="info@perfectthymetech.com" + +# Can be 'linux-x64', 'linux-arm64', 'linux-arm', 'rhel.6-x64'. +ENV TARGETARCH=linux-x64 +ENV AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache +RUN mkdir -p /opt/hostedtoolcache + +ARG AZURE_CLI_VERSION="2.72.0" +ARG PWSH_VERSION="7.5.3" + +RUN DEBIAN_FRONTEND=noninteractive apt-get update +RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y + +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \ + apt-transport-https \ + apt-utils \ + ca-certificates \ + curl \ + git \ + iputils-ping \ + jq \ + lsb-release \ + software-properties-common + +WORKDIR /azp +COPY install_dependencies.sh start.sh /azp/ + +RUN chmod +x /azp/start.sh /azp/install_dependencies.sh \ + && /azp/install_dependencies.sh ${AZURE_CLI_VERSION} ${PWSH_VERSION} \ + && rm /azp/install_dependencies.sh + +ENTRYPOINT [ "./start.sh" ] diff --git a/code/container-ado/install_dependencies.sh b/code/container-ado/install_dependencies.sh new file mode 100644 index 0000000..5b598d5 --- /dev/null +++ b/code/container-ado/install_dependencies.sh @@ -0,0 +1,25 @@ +#!/bin/bash -ex +AZURE_CLI_VERSION=$1 +PWSH_VERSION=$2 + +# Install Azure CLI +apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release \ + && mkdir -p /etc/apt/keyrings \ + && curl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | tee /etc/apt/keyrings/microsoft.gpg > /dev/null \ + && chmod go+r /etc/apt/keyrings/microsoft.gpg \ + && AZ_DIST=$(lsb_release -cs) \ + && echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ $AZ_DIST main" | tee /etc/apt/sources.list.d/azure-cli.list \ + && apt-get update \ + && AZ_DIST=$(lsb_release -cs) \ + && apt-get install -y azure-cli=$AZURE_CLI_VERSION-1~$AZ_DIST + +# Install Azure CLI - AKS +az aks install-cli + +# Install Powershell +apt-get install -y wget \ + && wget https://github.com/PowerShell/PowerShell/releases/download/v$PWSH_VERSION/powershell_$PWSH_VERSION-1.deb_amd64.deb \ + && dpkg -i powershell_$PWSH_VERSION-1.deb_amd64.deb \ + && apt-get install -fy \ + && rm powershell_$PWSH_VERSION-1.deb_amd64.deb \ + && pwsh -Command "Install-Module -Name Az -Repository PSGallery -Force" diff --git a/code/container-ado/start.sh b/code/container-ado/start.sh new file mode 100644 index 0000000..4909108 --- /dev/null +++ b/code/container-ado/start.sh @@ -0,0 +1,109 @@ +#!/bin/bash +set -e + +if [ -z "$AZP_URL" ]; then + echo 1>&2 "error: missing AZP_URL environment variable" + exit 1 +fi + +if [ -z "$AZP_TOKEN_FILE" ]; then + if [ -z "$AZP_TOKEN" ]; then + echo 1>&2 "error: missing AZP_TOKEN environment variable" + exit 1 + fi + + AZP_TOKEN_FILE=/azp/.token + echo -n $AZP_TOKEN > "$AZP_TOKEN_FILE" +fi + +unset AZP_TOKEN + +if [ -n "$AZP_WORK" ]; then + mkdir -p "$AZP_WORK" +fi + +export AGENT_ALLOW_RUNASROOT="1" + +cleanup() { + # If $AZP_PLACEHOLDER is set, skip cleanup + if [ -n "$AZP_PLACEHOLDER" ]; then + echo 'Running in placeholder mode, skipping cleanup' + return + fi + if [ -e config.sh ]; then + print_header "Cleanup. Removing Azure Pipelines agent..." + + # If the agent has some running jobs, the configuration removal process will fail. + # So, give it some time to finish the job. + while true; do + ./config.sh remove --unattended --auth PAT --token $(cat "$AZP_TOKEN_FILE") && break + + echo "Retrying in 30 seconds..." + sleep 30 + done + fi +} + +print_header() { + lightcyan='\033[1;36m' + nocolor='\033[0m' + echo -e "${lightcyan}$1${nocolor}" +} + +# Let the agent ignore the token env variables +export VSO_AGENT_IGNORE=AZP_TOKEN,AZP_TOKEN_FILE + +print_header "1. Determining matching Azure Pipelines agent..." + +AZP_AGENT_PACKAGES=$(curl -LsS \ + -u user:$(cat "$AZP_TOKEN_FILE") \ + -H 'Accept:application/json;' \ + "$AZP_URL/_apis/distributedtask/packages/agent?platform=$TARGETARCH&top=1") + +AZP_AGENT_PACKAGE_LATEST_URL=$(echo "$AZP_AGENT_PACKAGES" | jq -r '.value[0].downloadUrl') + +if [ -z "$AZP_AGENT_PACKAGE_LATEST_URL" -o "$AZP_AGENT_PACKAGE_LATEST_URL" == "null" ]; then + echo 1>&2 "error: could not determine a matching Azure Pipelines agent" + echo 1>&2 "check that account '$AZP_URL' is correct and the token is valid for that account" + exit 1 +fi + +print_header "2. Downloading and extracting Azure Pipelines agent..." +echo "Agent package URL: $AZP_AGENT_PACKAGE_LATEST_URL" +curl -LsS $AZP_AGENT_PACKAGE_LATEST_URL | tar -xz & wait $! + +source ./env.sh + +trap 'cleanup; exit 0' EXIT +trap 'cleanup; exit 130' INT +trap 'cleanup; exit 143' TERM + +print_header "3. Configuring Azure Pipelines agent..." + +./config.sh --unattended \ + --agent "${AZP_AGENT_NAME:-$(hostname)}" \ + --url "$AZP_URL" \ + --auth PAT \ + --token $(cat "$AZP_TOKEN_FILE") \ + --pool "${AZP_POOL:-Default}" \ + --work "${AZP_WORK:-_work}" \ + --replace \ + --acceptTeeEula & wait $! + +print_header "4. Running Azure Pipelines agent..." + +trap 'cleanup; exit 0' EXIT +trap 'cleanup; exit 130' INT +trap 'cleanup; exit 143' TERM + +chmod +x ./run.sh + + +# If $AZP_PLACEHOLDER is set, skipping running the agent +if [ -n "$AZP_PLACEHOLDER" ]; then + echo 'Running in placeholder mode, skipping running the agent' +else + # To be aware of TERM and INT signals call run.sh + # Running it with the --once flag at the end will shut down the agent after the build is executed + ./run.sh --once & wait $! +fi diff --git a/code/infra-ado/agentpool.tf b/code/infra-ado/agentpool.tf new file mode 100644 index 0000000..651ca8f --- /dev/null +++ b/code/infra-ado/agentpool.tf @@ -0,0 +1,7 @@ +resource "azuredevops_agent_pool" "agent_pool" { + name = "${local.prefix}-pool001" + + auto_provision = false + pool_type = "automation" + auto_update = false +} diff --git a/code/infra-ado/applicationinsights.tf b/code/infra-ado/applicationinsights.tf new file mode 100644 index 0000000..5a525bf --- /dev/null +++ b/code/infra-ado/applicationinsights.tf @@ -0,0 +1,14 @@ +module "application_insights" { + source = "github.com/PerfectThymeTech/terraform-azurerm-modules//modules/applicationinsights?ref=main" + providers = { + azurerm = azurerm + } + + location = var.location + resource_group_name = azurerm_resource_group.resource_group_container_app.name + tags = var.tags + application_insights_name = "${local.prefix}-appi001" + application_insights_application_type = "web" + application_insights_log_analytics_workspace_id = var.log_analytics_workspace_id + diagnostics_configurations = local.diagnostics_configurations +} diff --git a/code/infra-ado/containerapps.tf b/code/infra-ado/containerapps.tf new file mode 100644 index 0000000..099e8a2 --- /dev/null +++ b/code/infra-ado/containerapps.tf @@ -0,0 +1,175 @@ +resource "azapi_resource" "container_apps_environment" { + type = "Microsoft.App/managedEnvironments@2024-03-01" + parent_id = azurerm_resource_group.resource_group_container_app.id + name = "${local.prefix}-cae001" + location = var.location + tags = var.tags + + body = { + properties = { + # appInsightsConfiguration = { # Can only be set when DaprAIConnectionString is set to null + # connectionString = module.application_insights.application_insights_connection_string + # } + appLogsConfiguration = { + destination = "azure-monitor" + } + daprAIConnectionString = module.application_insights.application_insights_connection_string + daprAIInstrumentationKey = module.application_insights.application_insights_instrumentation_key + daprConfiguration = {} + infrastructureResourceGroup = "${local.prefix}-cae001-rg" + kedaConfiguration = {} + vnetConfiguration = { + infrastructureSubnetId = azapi_resource.subnet_container_app.id + internal = true + } + workloadProfiles = [ + { + name = "Consumption" + workloadProfileType = "Consumption" + } + ] + zoneRedundant = false + } + } +} + +resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_container_app_environment" { + for_each = { for index, value in local.diagnostics_configurations : + index => { + log_analytics_workspace_id = value.log_analytics_workspace_id, + storage_account_id = value.storage_account_id + } + } + name = "applicationLogs-${each.key}" + target_resource_id = azapi_resource.container_apps_environment.id + log_analytics_workspace_id = each.value.log_analytics_workspace_id == "" ? null : each.value.log_analytics_workspace_id + storage_account_id = each.value.storage_account_id == "" ? null : each.value.storage_account_id + + dynamic "enabled_log" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_container_app_environment.log_category_groups + content { + category_group = entry.value + } + } + + dynamic "enabled_metric" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_container_app_environment.metrics + content { + category = entry.value + } + } +} + +resource "azapi_resource" "container_apps_job" { + type = "Microsoft.App/jobs@2024-03-01" + parent_id = azurerm_resource_group.resource_group_container_app.id + name = "${local.prefix}-caj001" + location = var.location + tags = var.tags + identity { + type = "UserAssigned" + identity_ids = [ + module.user_assigned_identity.user_assigned_identity_id + ] + } + + body = { + properties = { + configuration = { + replicaRetryLimit = 1 + replicaTimeout = 1800 + triggerType = "Event" + eventTriggerConfig = { + parallelism = 1 + replicaCompletionCount = 1 + scale = { + minExecutions = 0 + maxExecutions = 10 + pollingInterval = 30 + rules = [ + { + name = "azure-pipelines" + type = "azure-pipelines" + auth = [ + { + triggerParameter = "organizationURL" + secretRef = "organization-url" + }, + { + triggerParameter = "personalAccessToken" + secretRef = "personal-access-token" + } + ] + metadata = { + # poolName = azuredevops_agent_pool.agent_pool.name + poolID = azuredevops_agent_pool.agent_pool.id + organizationURLFromEnv = "AZP_URL" + personalAccessTokenFromEnv = "AZP_TOKEN" + targetPipelinesQueueLength = 1 + activationTargetPipelinesQueueLength = 0 + requireAllDemands = false + requireAllDemandsAndIgnoreOthers = false + jobsToFetch = 250 + fetchUnfinishedJobsOnly = false + caseInsensitiveDemandsProcessing = false + # parent = var.azure_devops_parent_agent_name + # demands = var.azure_devops_demands + } + } + ] + } + } + secrets = [ + { + identity = module.user_assigned_identity.user_assigned_identity_id + keyVaultUrl = azurerm_key_vault_secret.key_vault_secret_azure_devops_organization_url.versionless_id + name = "organization-url" + value = var.azure_devops_organization_url + }, + { + identity = module.user_assigned_identity.user_assigned_identity_id + keyVaultUrl = azurerm_key_vault_secret.key_vault_secret_azure_devops_pat.versionless_id + name = "personal-access-token" + value = var.azure_devops_pat + } + ] + } + environmentId = azapi_resource.container_apps_environment.id + template = { + containers = [ + { + name = "github-runner" + # args = [] + # command = [] + env = [ + { + name = "AZP_TOKEN" + secretRef = "personal-access-token" + }, + { + name = "AZP_URL" + secretRef = "organization-url" + }, + { + name = "AZP_POOL" + value = azuredevops_agent_pool.agent_pool.name + } + ] + image = var.container_image_reference + # probes = [] + resources = { + cpu = 1.5 + memory = "3.0Gi" + } + volumeMounts = null + } + ] + initContainers = null + volumes = null + } + workloadProfileName = "Consumption" + } + } +} diff --git a/code/infra-ado/data.tf b/code/infra-ado/data.tf new file mode 100644 index 0000000..9456254 --- /dev/null +++ b/code/infra-ado/data.tf @@ -0,0 +1,25 @@ +data "azurerm_client_config" "current" {} + +data "azurerm_virtual_network" "virtual_network" { + name = local.virtual_network.name + resource_group_name = local.virtual_network.resource_group_name +} + +data "azurerm_network_security_group" "network_security_group" { + name = local.network_security_group.name + resource_group_name = local.network_security_group.resource_group_name +} + +data "azurerm_route_table" "route_table" { + name = local.route_table.name + resource_group_name = local.route_table.resource_group_name +} + +data "azurerm_log_analytics_workspace" "log_analytics_workspace" { + name = local.log_analytics_workspace.name + resource_group_name = local.log_analytics_workspace.resource_group_name +} + +data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_container_app_environment" { + resource_id = azapi_resource.container_apps_environment.id +} diff --git a/code/infra-ado/keyvault.tf b/code/infra-ado/keyvault.tf new file mode 100644 index 0000000..c7159dd --- /dev/null +++ b/code/infra-ado/keyvault.tf @@ -0,0 +1,44 @@ +module "key_vault" { + source = "github.com/PerfectThymeTech/terraform-azurerm-modules//modules/keyvault?ref=main" + providers = { + azurerm = azurerm + time = time + } + + location = var.location + resource_group_name = azurerm_resource_group.resource_group_container_app.name + tags = var.tags + key_vault_name = "${local.prefix}-kv001" + key_vault_sku_name = "standard" + key_vault_soft_delete_retention_days = 7 + diagnostics_configurations = local.diagnostics_configurations + subnet_id = azapi_resource.subnet_private_endpoints.id + connectivity_delay_in_seconds = var.connectivity_delay_in_seconds + private_dns_zone_id_vault = var.private_dns_zone_id_vault +} + +resource "azurerm_key_vault_secret" "key_vault_secret_azure_devops_organization_url" { + name = "organization-url" + key_vault_id = module.key_vault.key_vault_id + + content_type = "text/plain" + value = var.azure_devops_organization_url + + depends_on = [ + azurerm_role_assignment.current_role_assignment_key_vault_secrets_officer, + module.key_vault.key_vault_setup_completed, + ] +} + +resource "azurerm_key_vault_secret" "key_vault_secret_azure_devops_pat" { + name = "personal-access-token" + key_vault_id = module.key_vault.key_vault_id + + content_type = "text/plain" + value = var.azure_devops_pat + + depends_on = [ + azurerm_role_assignment.current_role_assignment_key_vault_secrets_officer, + module.key_vault.key_vault_setup_completed, + ] +} diff --git a/code/infra-ado/locals.tf b/code/infra-ado/locals.tf new file mode 100644 index 0000000..a1342be --- /dev/null +++ b/code/infra-ado/locals.tf @@ -0,0 +1,43 @@ +locals { + # General locals + prefix = "${lower(var.prefix)}-${var.environment}" + github_labels = "aca" + resource_providers_to_register = [ + "Microsoft.Authorization", + "Microsoft.App", + "Microsoft.Insights", + "Microsoft.KeyVault", + "Microsoft.ManagedIdentity", + "Microsoft.Network", + "Microsoft.Resources", + ] + + # Resource locals + virtual_network = { + resource_group_name = split("/", var.vnet_id)[4] + name = split("/", var.vnet_id)[8] + } + network_security_group = { + resource_group_name = split("/", var.nsg_id)[4] + name = split("/", var.nsg_id)[8] + } + route_table = { + resource_group_name = split("/", var.route_table_id)[4] + name = split("/", var.route_table_id)[8] + } + log_analytics_workspace = { + resource_group_name = split("/", var.log_analytics_workspace_id)[4] + name = split("/", var.log_analytics_workspace_id)[8] + } + + # Logging locals + diagnostics_configurations = [ + { + log_analytics_workspace_id = var.log_analytics_workspace_id + storage_account_id = "" + } + ] + + # CMK locals + customer_managed_key = null +} diff --git a/code/infra-ado/main.tf b/code/infra-ado/main.tf new file mode 100644 index 0000000..184af31 --- /dev/null +++ b/code/infra-ado/main.tf @@ -0,0 +1,5 @@ +resource "azurerm_resource_group" "resource_group_container_app" { + name = "${local.prefix}-container-rg" + location = var.location + tags = var.tags +} diff --git a/code/infra-ado/network.tf b/code/infra-ado/network.tf new file mode 100644 index 0000000..065e387 --- /dev/null +++ b/code/infra-ado/network.tf @@ -0,0 +1,58 @@ +resource "azapi_resource" "subnet_container_app" { + type = "Microsoft.Network/virtualNetworks/subnets@2024-01-01" + name = "ConAppEnvironmentAdoSubnet" + parent_id = data.azurerm_virtual_network.virtual_network.id + + body = { + properties = { + addressPrefix = var.subnet_cidr_container_app + delegations = [ + { + name = "ContainerAppDelegation" + properties = { + serviceName = "Microsoft.App/environments" + } + } + ] + ipAllocations = [] + networkSecurityGroup = { + id = data.azurerm_network_security_group.network_security_group.id + } + privateEndpointNetworkPolicies = "Enabled" + privateLinkServiceNetworkPolicies = "Enabled" + routeTable = { + id = data.azurerm_route_table.route_table.id + } + serviceEndpointPolicies = [] + serviceEndpoints = [] + } + } +} + +resource "azapi_resource" "subnet_private_endpoints" { + type = "Microsoft.Network/virtualNetworks/subnets@2024-01-01" + name = "ConAppPrivateEndpointAdoSubnet" + parent_id = data.azurerm_virtual_network.virtual_network.id + + body = { + properties = { + addressPrefix = var.subnet_cidr_private_endpoints + delegations = [] + ipAllocations = [] + networkSecurityGroup = { + id = data.azurerm_network_security_group.network_security_group.id + } + privateEndpointNetworkPolicies = "Enabled" + privateLinkServiceNetworkPolicies = "Enabled" + routeTable = { + id = data.azurerm_route_table.route_table.id + } + serviceEndpointPolicies = [] + serviceEndpoints = [] + } + } + + depends_on = [ + azapi_resource.subnet_container_app + ] +} diff --git a/code/infra-ado/providers.tf b/code/infra-ado/providers.tf new file mode 100644 index 0000000..3c24cb0 --- /dev/null +++ b/code/infra-ado/providers.tf @@ -0,0 +1,34 @@ +provider "azurerm" { + disable_correlation_request_id = false + environment = "public" + resource_provider_registrations = "none" + resource_providers_to_register = local.resource_providers_to_register + storage_use_azuread = true + # use_oidc = true + + features { + key_vault { + recover_soft_deleted_key_vaults = true + recover_soft_deleted_certificates = true + recover_soft_deleted_keys = true + recover_soft_deleted_secrets = true + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + +provider "azapi" { + default_location = var.location + default_tags = var.tags + disable_correlation_request_id = false + environment = "public" + skip_provider_registration = false + # use_oidc = true +} + +provider "azuredevops" { + org_service_url = var.azure_devops_organization_url + # use_oidc = true +} diff --git a/code/infra-ado/roleassignments.tf b/code/infra-ado/roleassignments.tf new file mode 100644 index 0000000..9fe8f7b --- /dev/null +++ b/code/infra-ado/roleassignments.tf @@ -0,0 +1,12 @@ +resource "azurerm_role_assignment" "current_role_assignment_key_vault_secrets_officer" { + scope = module.key_vault.key_vault_id + role_definition_name = "Key Vault Secrets Officer" + principal_id = data.azurerm_client_config.current.object_id +} + +# User Assigned Identity +resource "azurerm_role_assignment" "uai_role_assignment_key_vault_secrets_user" { + scope = module.key_vault.key_vault_id + role_definition_name = "Key Vault Secrets User" + principal_id = module.user_assigned_identity.user_assigned_identity_principal_id +} diff --git a/code/infra-ado/terraform.tf b/code/infra-ado/terraform.tf new file mode 100644 index 0000000..335d694 --- /dev/null +++ b/code/infra-ado/terraform.tf @@ -0,0 +1,31 @@ +terraform { + required_version = ">=0.12" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "4.58.0" + } + azapi = { + source = "azure/azapi" + version = "2.8.0" + } + azuredevops = { + source = "microsoft/azuredevops" + version = "1.13.0" + } + time = { + source = "hashicorp/time" + version = "0.13.1" + } + } + + backend "azurerm" { + environment = "public" + resource_group_name = "" + storage_account_name = "" + container_name = "" + key = "" + use_azuread_auth = true + } +} diff --git a/code/infra-ado/userassignedidentity.tf b/code/infra-ado/userassignedidentity.tf new file mode 100644 index 0000000..1914968 --- /dev/null +++ b/code/infra-ado/userassignedidentity.tf @@ -0,0 +1,12 @@ +module "user_assigned_identity" { + source = "github.com/PerfectThymeTech/terraform-azurerm-modules//modules/userassignedidentity?ref=main" + providers = { + azurerm = azurerm + } + + location = var.location + resource_group_name = azurerm_resource_group.resource_group_container_app.name + tags = var.tags + user_assigned_identity_name = "${local.prefix}-uai001" + user_assigned_identity_federated_identity_credentials = {} +} diff --git a/code/infra-ado/variables.tf b/code/infra-ado/variables.tf new file mode 100644 index 0000000..b80b7cd --- /dev/null +++ b/code/infra-ado/variables.tf @@ -0,0 +1,151 @@ +# General variables +variable "location" { + description = "Specifies the location for all Azure resources." + type = string + sensitive = false +} + +variable "environment" { + description = "Specifies the environment of the deployment." + type = string + sensitive = false + default = "dev" + validation { + condition = contains(["int", "dev", "tst", "qa", "uat", "prd"], var.environment) + error_message = "Please use an allowed value: \"int\", \"dev\", \"tst\", \"qa\", \"uat\" or \"prd\"." + } +} + +variable "prefix" { + description = "Specifies the prefix for all resources created in this deployment." + type = string + sensitive = false + validation { + condition = length(var.prefix) >= 2 && length(var.prefix) <= 10 + error_message = "Please specify a prefix with more than two and less than 10 characters." + } +} + +variable "tags" { + description = "Specifies the tags that you want to apply to all resources." + type = map(string) + sensitive = false + default = {} +} + +# Github variables +variable "azure_devops_organization_url" { + description = "Specifies the organization url of the Azure DevOps org." + type = string + sensitive = false + validation { + condition = length(var.azure_devops_organization_url) > 2 + error_message = "Please specify a valid name." + } +} + +variable "azure_devops_pat" { + description = "Specifies the PAT for the Azure DevOps Org." + type = string + sensitive = false + validation { + condition = length(var.azure_devops_pat) > 2 + error_message = "Please specify a valid app ID." + } +} + +# Container variables +variable "container_image_reference" { + description = "Specifies the container image reference used in Azure Container Jobs." + type = string + sensitive = true + validation { + condition = length(var.container_image_reference) > 2 + error_message = "Please specify a valid container reference." + } +} + +# Logging variables +variable "log_analytics_workspace_id" { + description = "Specifies the resource ID of the log analytics workspace used for collecting logs." + type = string + sensitive = false + validation { + condition = length(split("/", var.log_analytics_workspace_id)) == 9 + error_message = "Please specify a valid resource ID." + } +} + +# Network variables +variable "connectivity_delay_in_seconds" { + description = "Specifies the delay in seconds after the private endpoint deployment (required for the DNS automation via Policies)." + type = number + sensitive = false + nullable = false + default = 120 + validation { + condition = var.connectivity_delay_in_seconds >= 0 + error_message = "Please specify a valid non-negative number." + } +} + +variable "vnet_id" { + description = "Specifies the resource ID of the Vnet used for the Azure Function." + type = string + sensitive = false + validation { + condition = length(split("/", var.vnet_id)) == 9 + error_message = "Please specify a valid resource ID." + } +} + +variable "nsg_id" { + description = "Specifies the resource ID of the default network security group for the Azure Function." + type = string + sensitive = false + validation { + condition = length(split("/", var.nsg_id)) == 9 + error_message = "Please specify a valid resource ID." + } +} + +variable "route_table_id" { + description = "Specifies the resource ID of the default route table for the Azure Function." + type = string + sensitive = false + validation { + condition = length(split("/", var.route_table_id)) == 9 + error_message = "Please specify a valid resource ID." + } +} + +variable "subnet_cidr_container_app" { + description = "Specifies the subnet cidr range for teh container app subnet." + type = string + sensitive = false + validation { + condition = length(split("/", var.subnet_cidr_container_app)) == 2 + error_message = "Please specify a valid subnet cidr range." + } +} + +variable "subnet_cidr_private_endpoints" { + description = "Specifies the subnet cidr range for private endpoints." + type = string + sensitive = false + validation { + condition = length(split("/", var.subnet_cidr_private_endpoints)) == 2 + error_message = "Please specify a valid subnet cidr range." + } +} + +variable "private_dns_zone_id_vault" { + description = "Specifies the resource ID of the private DNS zone for Azure Key Vault. Not required if DNS A-records get created via Azure Policy." + type = string + sensitive = false + default = "" + validation { + condition = var.private_dns_zone_id_vault == "" || (length(split("/", var.private_dns_zone_id_vault)) == 9 && endswith(var.private_dns_zone_id_vault, "privatelink.vaultcore.azure.net")) + error_message = "Please specify a valid resource ID for the private DNS Zone." + } +} diff --git a/config/PerfectThymeTech-ado/azurerm.tfbackend b/config/PerfectThymeTech-ado/azurerm.tfbackend new file mode 100644 index 0000000..e70833e --- /dev/null +++ b/config/PerfectThymeTech-ado/azurerm.tfbackend @@ -0,0 +1,7 @@ +environment = "public" +subscription_id = "e82c5267-9dc4-4f45-ac13-abdd5e130d27" +resource_group_name = "rg-terraform" +storage_account_name = "terraformststg001" +container_name = "azuredevops-agent" +key = "terraform.tfstate" +use_azuread_auth = true diff --git a/config/PerfectThymeTech-ado/vars.tfvars b/config/PerfectThymeTech-ado/vars.tfvars new file mode 100644 index 0000000..879414e --- /dev/null +++ b/config/PerfectThymeTech-ado/vars.tfvars @@ -0,0 +1,22 @@ +# General variables +location = "northeurope" +environment = "prd" +prefix = "ador" +tags = { + "workload" = "azuredevops-action-runners" +} +log_analytics_workspace_id = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-logging-rg/providers/Microsoft.OperationalInsights/workspaces/ptt-dev-log001" + +# Github variables +azure_devops_organization_url = "https://dev.azure.com/marvinbuss-eval" + +# Container variables +container_image_reference = "ghcr.io/perfectthymetech/azuredevopsagentazure:pr-232" + +# Network variables +vnet_id = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-hub-northeurope-rg/providers/Microsoft.Network/virtualNetworks/ptt-dev-vnet001" +nsg_id = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-hub-northeurope-rg/providers/Microsoft.Network/networkSecurityGroups/ptt-dev-default-nsg001" +route_table_id = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-hub-northeurope-rg/providers/Microsoft.Network/routeTables/ptt-dev-default-rt001" +subnet_cidr_container_app = "10.0.3.64/26" +subnet_cidr_private_endpoints = "10.0.3.0/26" +private_dns_zone_id_vault = "/subscriptions/e82c5267-9dc4-4f45-ac13-abdd5e130d27/resourceGroups/ptt-dev-privatedns-rg/providers/Microsoft.Network/privateDnsZones/privatelink.vaultcore.azure.net"