From 48401dad1ea917591e189920c681e48f0bbd44f8 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 9 Jun 2023 10:20:46 +0200 Subject: [PATCH 1/9] Add infrastructure baseline --- code/infra/data.tf | 16 +++ code/infra/function.tf | 133 +++++++++++++++++++ code/infra/keyvault.tf | 80 ++++++++++++ code/infra/locals.tf | 18 +++ code/infra/logging.tf | 103 +++++++++++++++ code/infra/main.tf | 11 ++ code/infra/network.tf | 54 ++++++++ code/infra/roleassignments.tf | 11 ++ code/infra/storage.tf | 232 ++++++++++++++++++++++++++++++++++ code/infra/terraform.tf | 56 ++++++++ code/infra/variables.tf | 123 ++++++++++++++++++ code/infra/vars.dev.tfvars | 13 ++ 12 files changed, 850 insertions(+) create mode 100644 code/infra/data.tf create mode 100644 code/infra/function.tf create mode 100644 code/infra/keyvault.tf create mode 100644 code/infra/locals.tf create mode 100644 code/infra/logging.tf create mode 100644 code/infra/main.tf create mode 100644 code/infra/network.tf create mode 100644 code/infra/roleassignments.tf create mode 100644 code/infra/storage.tf create mode 100644 code/infra/terraform.tf create mode 100644 code/infra/variables.tf create mode 100644 code/infra/vars.dev.tfvars diff --git a/code/infra/data.tf b/code/infra/data.tf new file mode 100644 index 0000000..5285bdf --- /dev/null +++ b/code/infra/data.tf @@ -0,0 +1,16 @@ +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 +} diff --git a/code/infra/function.tf b/code/infra/function.tf new file mode 100644 index 0000000..a0eeaa4 --- /dev/null +++ b/code/infra/function.tf @@ -0,0 +1,133 @@ +resource "azurerm_service_plan" "service_plan" { + name = "${local.prefix}-asp001" + location = var.location + resource_group_name = azurerm_resource_group.app_rg.name + tags = var.tags + + # maximum_elastic_worker_count = 20 + os_type = "Linux" + per_site_scaling_enabled = false + sku_name = "P1v3" + worker_count = 3 + zone_balancing_enabled = true +} + +resource "azapi_resource" "function" { + type = "Microsoft.Web/sites@2022-09-01" + parent_id = azurerm_resource_group.app_rg.id + name = "${local.prefix}-fctn001" + location = var.location + tags = var.tags + identity { + type = "SystemAssigned" + } + + body = jsonencode({ + kind = "functionapp,linux" + properties = { + clientAffinityEnabled = false + clientCertEnabled = false + clientCertMode = "Required" + enabled = true + hostNamesDisabled = false + httpsOnly = true + hyperV = false + isXenon = false + keyVaultReferenceIdentity = "SystemAssigned" + publicNetworkAccess = "Disabled" + redundancyMode = "None" + reserved = true + scmSiteAlsoStopped = false + serverFarmId = azurerm_service_plan.service_plan.id + storageAccountRequired = false + virtualNetworkSubnetId = azapi_resource.subnet_function.id + siteConfig = { + autoHealEnabled = false + acrUseManagedIdentityCreds = false + alwaysOn = true + appSettings = [ + { + name = "APPLICATIONINSIGHTS_CONNECTION_STRING" + value = azurerm_application_insights.application_insights.connection_string + }, + { + name = "APPINSIGHTS_INSTRUMENTATIONKEY" + value = azurerm_application_insights.application_insights.instrumentation_key + }, + { + name = "FUNCTIONS_EXTENSION_VERSION" + value = "~4" + }, + { + name = "FUNCTIONS_WORKER_RUNTIME" + value = "python" + }, + { + name = "WEBSITE_CONTENTOVERVNET" + value = "1" + }, + { + name = "AzureWebJobsStorage__accountName" + value = azurerm_storage_account.storage.name + } + ] + azureStorageAccounts = {} + detailedErrorLoggingEnabled = true + functionAppScaleLimit = 0 + functionsRuntimeScaleMonitoringEnabled = false + ftpsState = "FtpsOnly" + http20Enabled = false + ipSecurityRestrictionsDefaultAction = "Deny" + linuxFxVersion = "Python|3.10" + localMySqlEnabled = false + loadBalancing = "LeastRequests" + minTlsVersion = "1.2" + minimumElasticInstanceCount = 0 + numberOfWorkers = 1 + preWarmedInstanceCount = 0 + scmMinTlsVersion = "1.2" + scmIpSecurityRestrictionsUseMain = false + scmIpSecurityRestrictionsDefaultAction = "Deny" + use32BitWorkerProcess = true + vnetRouteAllEnabled = true + vnetPrivatePortsCount = 0 + webSocketsEnabled = false + } + } + }) +} + +data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_function" { + resource_id = azapi_resource.function.id +} + +resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_function" { + name = "logAnalytics" + target_resource_id = azapi_resource.function.id + log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id + + dynamic "enabled_log" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_function.log_category_groups + content { + category_group = entry.value + retention_policy { + enabled = true + days = 30 + } + } + } + + dynamic "metric" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_function.metrics + content { + category = entry.value + enabled = true + retention_policy { + enabled = true + days = 30 + } + } + } +} diff --git a/code/infra/keyvault.tf b/code/infra/keyvault.tf new file mode 100644 index 0000000..e0935dd --- /dev/null +++ b/code/infra/keyvault.tf @@ -0,0 +1,80 @@ +resource "azurerm_key_vault" "key_vault" { + name = "${local.prefix}-vault001" + location = var.location + resource_group_name = azurerm_resource_group.app_rg.name + tags = var.tags + + access_policy = [] + enable_rbac_authorization = true + enabled_for_deployment = false + enabled_for_disk_encryption = false + enabled_for_template_deployment = false + network_acls { + bypass = "AzureServices" + default_action = "Deny" + ip_rules = [] + virtual_network_subnet_ids = [] + } + public_network_access_enabled = false + purge_protection_enabled = true + sku_name = "standard" + soft_delete_retention_days = 7 + tenant_id = data.azurerm_client_config.current.tenant_id +} + +data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_key_vault" { + resource_id = azurerm_key_vault.key_vault.id +} + +resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_key_vault" { + name = "logAnalytics" + target_resource_id = azurerm_key_vault.key_vault.id + log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id + + dynamic "enabled_log" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_key_vault.log_category_groups + content { + category_group = entry.value + retention_policy { + enabled = true + days = 30 + } + } + } + + dynamic "metric" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_key_vault.metrics + content { + category = entry.value + enabled = true + retention_policy { + enabled = true + days = 30 + } + } + } +} + +resource "azurerm_private_endpoint" "key_vault_private_endpoint" { + name = "${azurerm_key_vault.key_vault.name}-pe" + location = var.location + resource_group_name = azurerm_key_vault.key_vault.resource_group_name + tags = var.tags + + custom_network_interface_name = "${azurerm_key_vault.key_vault.name}-nic" + private_service_connection { + name = "${azurerm_key_vault.key_vault.name}-pe" + is_manual_connection = false + private_connection_resource_id = azurerm_key_vault.key_vault.id + subresource_names = ["vault"] + } + subnet_id = azapi_resource.subnet_services.id + private_dns_zone_group { + name = "${azurerm_key_vault.key_vault.name}-arecord" + private_dns_zone_ids = [ + var.private_dns_zone_id_key_vault + ] + } +} diff --git a/code/infra/locals.tf b/code/infra/locals.tf new file mode 100644 index 0000000..09920e4 --- /dev/null +++ b/code/infra/locals.tf @@ -0,0 +1,18 @@ +locals { + prefix = "${lower(var.prefix)}-${var.environment}" + + 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] + } +} diff --git a/code/infra/logging.tf b/code/infra/logging.tf new file mode 100644 index 0000000..e2576d6 --- /dev/null +++ b/code/infra/logging.tf @@ -0,0 +1,103 @@ +resource "azurerm_application_insights" "application_insights" { + name = "${local.prefix}-appi001" + location = var.location + resource_group_name = azurerm_resource_group.logging_rg.name + tags = var.tags + + application_type = "other" + daily_data_cap_notifications_disabled = false + disable_ip_masking = false + force_customer_storage_for_profiler = false + internet_ingestion_enabled = true + internet_query_enabled = true + local_authentication_disabled = true + retention_in_days = 90 + sampling_percentage = 100 + workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id +} + +data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_application_insights" { + resource_id = azurerm_application_insights.application_insights.id +} + +resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_application_insights" { + name = "logAnalytics" + target_resource_id = azurerm_application_insights.application_insights.id + log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id + + dynamic "enabled_log" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_application_insights.log_category_groups + content { + category_group = entry.value + retention_policy { + enabled = true + days = 30 + } + } + } + + dynamic "metric" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_application_insights.metrics + content { + category = entry.value + enabled = true + retention_policy { + enabled = true + days = 30 + } + } + } +} + +resource "azurerm_log_analytics_workspace" "log_analytics_workspace" { + name = "${local.prefix}-log001" + location = var.location + resource_group_name = azurerm_resource_group.logging_rg.name + tags = var.tags + + allow_resource_only_permissions = true + cmk_for_query_forced = false + daily_quota_gb = -1 + internet_ingestion_enabled = true + internet_query_enabled = true + local_authentication_disabled = true + retention_in_days = 30 + sku = "PerGB2018" +} + +data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_log_analytics_workspace" { + resource_id = azurerm_log_analytics_workspace.log_analytics_workspace.id +} + +resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_log_analytics_workspace" { + name = "logAnalytics" + target_resource_id = azurerm_log_analytics_workspace.log_analytics_workspace.id + log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id + + dynamic "enabled_log" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_log_analytics_workspace.log_category_groups + content { + category_group = entry.value + retention_policy { + enabled = true + days = 30 + } + } + } + + dynamic "metric" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_log_analytics_workspace.metrics + content { + category = entry.value + enabled = true + retention_policy { + enabled = true + days = 30 + } + } + } +} diff --git a/code/infra/main.tf b/code/infra/main.tf new file mode 100644 index 0000000..823a2c6 --- /dev/null +++ b/code/infra/main.tf @@ -0,0 +1,11 @@ +resource "azurerm_resource_group" "app_rg" { + name = "${local.prefix}-app-rg" + location = var.location + tags = var.tags +} + +resource "azurerm_resource_group" "logging_rg" { + name = "${local.prefix}-logging-rg" + location = var.location + tags = var.tags +} diff --git a/code/infra/network.tf b/code/infra/network.tf new file mode 100644 index 0000000..427c00f --- /dev/null +++ b/code/infra/network.tf @@ -0,0 +1,54 @@ +resource "azapi_resource" "subnet_function" { + type = "Microsoft.Network/virtualNetworks/subnets@2022-07-01" + name = "FunctionSubnet" + parent_id = data.azurerm_virtual_network.virtual_network.id + + body = jsonencode({ + properties = { + addressPrefix = tostring(cidrsubnet(data.azurerm_virtual_network.virtual_network.address_space[0], 27 - tonumber(reverse(split("/", data.azurerm_virtual_network.virtual_network.address_space[0]))[0]), 2)) + delegations = [ + { + name = "FunctionDelegation" + properties = { + serviceName = "Microsoft.Web/serverFarms" + } + } + ] + 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_services" { + type = "Microsoft.Network/virtualNetworks/subnets@2022-07-01" + name = "PeSubnet" + parent_id = data.azurerm_virtual_network.virtual_network.id + + body = jsonencode({ + properties = { + addressPrefix = tostring(cidrsubnet(data.azurerm_virtual_network.virtual_network.address_space[0], 27 - tonumber(reverse(split("/", data.azurerm_virtual_network.virtual_network.address_space[0]))[0]), 3)) + 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 = [] + } + }) +} diff --git a/code/infra/roleassignments.tf b/code/infra/roleassignments.tf new file mode 100644 index 0000000..30b92b7 --- /dev/null +++ b/code/infra/roleassignments.tf @@ -0,0 +1,11 @@ +resource "azurerm_role_assignment" "role_assignment_key_vault_uai" { + scope = azurerm_key_vault.key_vault.id + role_definition_name = "Key Vault Crypto Service Encryption User" + principal_id = azurerm_user_assigned_identity.user_assigned_identity.principal_id +} + +resource "azurerm_role_assignment" "role_assignment_storage_function" { + scope = azurerm_storage_account.storage.id + role_definition_name = "Storage Blob Data Owner" + principal_id = azapi_resource.function.identity[0].principal_id +} diff --git a/code/infra/storage.tf b/code/infra/storage.tf new file mode 100644 index 0000000..84ee20b --- /dev/null +++ b/code/infra/storage.tf @@ -0,0 +1,232 @@ +resource "azurerm_storage_account" "storage" { + name = replace("${local.prefix}-stg001", "-", "") + location = var.location + resource_group_name = azurerm_resource_group.app_rg.name + tags = var.tags + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.user_assigned_identity.id + ] + } + + access_tier = "Hot" + account_kind = "StorageV2" + account_replication_type = "ZRS" + account_tier = "Standard" + allow_nested_items_to_be_public = false + allowed_copy_scope = "AAD" + blob_properties { + change_feed_enabled = false + container_delete_retention_policy { + days = 7 + } + delete_retention_policy { + days = 7 + } + default_service_version = "2020-06-12" + last_access_time_enabled = false + versioning_enabled = false + } + cross_tenant_replication_enabled = false + default_to_oauth_authentication = true + enable_https_traffic_only = true + infrastructure_encryption_enabled = true + is_hns_enabled = true + large_file_share_enabled = false + min_tls_version = "TLS1_2" + network_rules { + bypass = ["None"] + default_action = "Deny" + ip_rules = [] + virtual_network_subnet_ids = [] + } + nfsv3_enabled = false + public_network_access_enabled = false + queue_encryption_key_type = "Account" + table_encryption_key_type = "Account" + routing { + choice = "MicrosoftRouting" + publish_internet_endpoints = false + publish_microsoft_endpoints = false + } + sftp_enabled = false + shared_access_key_enabled = false + + depends_on = [ + azurerm_role_assignment.role_assignment_key_vault_uai + ] +} + +resource "azurerm_storage_management_policy" "storage_management_policy" { + storage_account_id = azurerm_storage_account.storage.id + + rule { + name = "default" + enabled = true + actions { + base_blob { + tier_to_cool_after_days_since_modification_greater_than = 360 + # delete_after_days_since_modification_greater_than = 720 + } + snapshot { + change_tier_to_cool_after_days_since_creation = 180 + delete_after_days_since_creation_greater_than = 360 + } + version { + change_tier_to_cool_after_days_since_creation = 180 + delete_after_days_since_creation = 360 + } + } + filters { + blob_types = ["blockBlob"] + prefix_match = [] + } + } +} + +resource "azapi_resource" "storage_file_share" { + type = "Microsoft.Storage/storageAccounts/fileServices/shares@2022-09-01" + name = "logicapp" + parent_id = "${azurerm_storage_account.storage.id}/fileServices/default" + + body = jsonencode({ + properties = { + accessTier = "TransactionOptimized" + enabledProtocols = "SMB" + shareQuota = 5120 + } + }) +} + +# resource "azurerm_storage_share" "storage_file_share" { +# name = "logicapp" +# storage_account_name = azurerm_storage_account.storage.name + +# access_tier = "TransactionOptimized" +# enabled_protocol = "SMB" +# quota = 5120 +# } + +data "azurerm_monitor_diagnostic_categories" "diagnostic_categories_storage" { + resource_id = azurerm_storage_account.storage.id +} + +resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_storage" { + name = "logAnalytics" + target_resource_id = azurerm_storage_account.storage.id + log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id + + dynamic "enabled_log" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_storage.log_category_groups + content { + category_group = entry.value + retention_policy { + enabled = true + days = 30 + } + } + } + + dynamic "metric" { + iterator = entry + for_each = data.azurerm_monitor_diagnostic_categories.diagnostic_categories_storage.metrics + content { + category = entry.value + enabled = true + retention_policy { + enabled = true + days = 30 + } + } + } +} + +resource "azurerm_private_endpoint" "storage_private_endpoint_blob" { + name = "${azurerm_storage_account.storage.name}-blob-pe" + location = var.location + resource_group_name = azurerm_storage_account.storage.resource_group_name + tags = var.tags + + custom_network_interface_name = "${azurerm_storage_account.storage.name}-blob-nic" + private_service_connection { + name = "${azurerm_storage_account.storage.name}-blob-pe" + is_manual_connection = false + private_connection_resource_id = azurerm_storage_account.storage.id + subresource_names = ["blob"] + } + subnet_id = azapi_resource.subnet_services.id + private_dns_zone_group { + name = "${azurerm_storage_account.storage.name}-arecord" + private_dns_zone_ids = [ + var.private_dns_zone_id_blob + ] + } +} + +resource "azurerm_private_endpoint" "storage_private_endpoint_file" { + name = "${azurerm_storage_account.storage.name}-file-pe" + location = var.location + resource_group_name = azurerm_storage_account.storage.resource_group_name + tags = var.tags + + custom_network_interface_name = "${azurerm_storage_account.storage.name}-file-nic" + private_service_connection { + name = "${azurerm_storage_account.storage.name}-file-pe" + is_manual_connection = false + private_connection_resource_id = azurerm_storage_account.storage.id + subresource_names = ["file"] + } + subnet_id = azapi_resource.subnet_services.id + private_dns_zone_group { + name = "${azurerm_storage_account.storage.name}-arecord" + private_dns_zone_ids = [ + var.private_dns_zone_id_file + ] + } +} + +resource "azurerm_private_endpoint" "storage_private_endpoint_queue" { + name = "${azurerm_storage_account.storage.name}-queue-pe" + location = var.location + resource_group_name = azurerm_storage_account.storage.resource_group_name + tags = var.tags + + custom_network_interface_name = "${azurerm_storage_account.storage.name}-queue-nic" + private_service_connection { + name = "${azurerm_storage_account.storage.name}-queue-pe" + is_manual_connection = false + private_connection_resource_id = azurerm_storage_account.storage.id + subresource_names = ["queue"] + } + subnet_id = azapi_resource.subnet_services.id + private_dns_zone_group { + name = "${azurerm_storage_account.storage.name}-arecord" + private_dns_zone_ids = [ + var.private_dns_zone_id_queue + ] + } +} + +resource "azurerm_private_endpoint" "storage_private_endpoint_table" { + name = "${azurerm_storage_account.storage.name}-table-pe" + location = var.location + resource_group_name = azurerm_storage_account.storage.resource_group_name + tags = var.tags + + custom_network_interface_name = "${azurerm_storage_account.storage.name}-table-nic" + private_service_connection { + name = "${azurerm_storage_account.storage.name}-table-pe" + is_manual_connection = false + private_connection_resource_id = azurerm_storage_account.storage.id + subresource_names = ["table"] + } + subnet_id = azapi_resource.subnet_services.id + private_dns_zone_group { + name = "${azurerm_storage_account.storage.name}-arecord" + private_dns_zone_ids = [ + var.private_dns_zone_id_table + ] + } +} diff --git a/code/infra/terraform.tf b/code/infra/terraform.tf new file mode 100644 index 0000000..7f1afcc --- /dev/null +++ b/code/infra/terraform.tf @@ -0,0 +1,56 @@ +terraform { + required_version = ">=0.12" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "3.53.0" + } + azapi = { + source = "azure/azapi" + version = "1.5.0" + } + } + + backend "azurerm" { + environment = "public" + subscription_id = "176a0de9-d99e-4faa-90cc-89780e1186fe" + resource_group_name = "workload000-cicd" + storage_account_name = "workload000stg001" + container_name = "terraform" + key = "terraform-function20.tfstate" + use_oidc = true + } +} + +provider "azurerm" { + disable_correlation_request_id = false + environment = "public" + skip_provider_registration = false + 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 + } + network { + relaxed_locking = true + } + resource_group { + prevent_deletion_if_contains_resources = true + } + } +} + +provider "azapi" { + default_location = var.location + default_tags = var.tags + disable_correlation_request_id = false + environment = "public" + skip_provider_registration = false + use_oidc = true +} diff --git a/code/infra/variables.tf b/code/infra/variables.tf new file mode 100644 index 0000000..3227511 --- /dev/null +++ b/code/infra/variables.tf @@ -0,0 +1,123 @@ +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(["dev", "tst", "prd"], var.environment) + error_message = "Please use an allowed value: \"dev\", \"tst\" 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(any) + sensitive = false +} + +variable "timestamp_expiry" { + description = "Specifies the initial key vault key expiry." + type = number + sensitive = false +} + +variable "vnet_id" { + description = "Specifies the resource ID of the Vnet used for the Data Landing Zone" + 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 Data Landing Zone" + 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 Data Landing Zone" + type = string + sensitive = false + validation { + condition = length(split("/", var.route_table_id)) == 9 + error_message = "Please specify a valid resource ID." + } +} + +variable "private_dns_zone_id_blob" { + description = "Specifies the resource ID of the private DNS zone for Azure Storage blob endpoints. Not required if DNS A-records get created via Azue Policy." + type = string + sensitive = false + default = "" + validation { + condition = var.private_dns_zone_id_blob == "" || (length(split("/", var.private_dns_zone_id_blob)) == 9 && endswith(var.private_dns_zone_id_blob, "privatelink.blob.core.windows.net")) + error_message = "Please specify a valid resource ID for the private DNS Zone." + } +} + +variable "private_dns_zone_id_queue" { + description = "Specifies the resource ID of the private DNS zone for Azure Storage queue endpoints. Not required if DNS A-records get created via Azue Policy." + type = string + sensitive = false + default = "" + validation { + condition = var.private_dns_zone_id_queue == "" || (length(split("/", var.private_dns_zone_id_queue)) == 9 && endswith(var.private_dns_zone_id_queue, "privatelink.queue.core.windows.net")) + error_message = "Please specify a valid resource ID for the private DNS Zone." + } +} + +variable "private_dns_zone_id_table" { + description = "Specifies the resource ID of the private DNS zone for Azure Storage table endpoints. Not required if DNS A-records get created via Azue Policy." + type = string + sensitive = false + default = "" + validation { + condition = var.private_dns_zone_id_table == "" || (length(split("/", var.private_dns_zone_id_table)) == 9 && endswith(var.private_dns_zone_id_table, "privatelink.table.core.windows.net")) + error_message = "Please specify a valid resource ID for the private DNS Zone." + } +} + +variable "private_dns_zone_id_file" { + description = "Specifies the resource ID of the private DNS zone for Azure Storage file endpoints. Not required if DNS A-records get created via Azue Policy." + type = string + sensitive = false + default = "" + validation { + condition = var.private_dns_zone_id_file == "" || (length(split("/", var.private_dns_zone_id_file)) == 9 && endswith(var.private_dns_zone_id_file, "privatelink.file.core.windows.net")) + error_message = "Please specify a valid resource ID for the private DNS Zone." + } +} + +variable "private_dns_zone_id_key_vault" { + description = "Specifies the resource ID of the private DNS zone for Azure Key Vault. Not required if DNS A-records get created via Azue Policy." + type = string + sensitive = false + default = "" + validation { + condition = var.private_dns_zone_id_key_vault == "" || (length(split("/", var.private_dns_zone_id_key_vault)) == 9 && endswith(var.private_dns_zone_id_key_vault, "privatelink.vaultcore.azure.net")) + error_message = "Please specify a valid resource ID for the private DNS Zone." + } +} diff --git a/code/infra/vars.dev.tfvars b/code/infra/vars.dev.tfvars new file mode 100644 index 0000000..11557eb --- /dev/null +++ b/code/infra/vars.dev.tfvars @@ -0,0 +1,13 @@ +location = "eastus" +environment = "dev" +prefix = "mb020" +tags = {} +timestamp_expiry = 1716628196 +vnet_id = "/subscriptions/6a439cba-0e4d-46ca-9eda-e3b7a8f60a49/resourceGroups/vnet-eastus-rg002/providers/Microsoft.Network/virtualNetworks/vnet-eastus002" +nsg_id = "/subscriptions/6a439cba-0e4d-46ca-9eda-e3b7a8f60a49/resourceGroups/vnet-eastus-rg002/providers/Microsoft.Network/networkSecurityGroups/nsg-eastus002" +route_table_id = "/subscriptions/6a439cba-0e4d-46ca-9eda-e3b7a8f60a49/resourceGroups/vnet-eastus-rg002/providers/Microsoft.Network/routeTables/rt-eastus002" +private_dns_zone_id_blob = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net" +private_dns_zone_id_queue = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.queue.core.windows.net" +private_dns_zone_id_table = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.table.core.windows.net" +private_dns_zone_id_file = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.file.core.windows.net" +private_dns_zone_id_key_vault = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.vaultcore.azure.net" From 98ba7d1722783dc50c3178e7bfe4a1cad2bd6e48 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 9 Jun 2023 10:24:45 +0200 Subject: [PATCH 2/9] Add pre-commit config --- .pre-commit-config.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..978da8d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +exclude: | + (?x)^( + README.md + )$ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-json + - id: check-yaml + - id: pretty-format-json + args: ["--indent", "2", "--autofix", "--no-sort-keys"] + - repo: local + hooks: + - id: terraform-fmt + name: terraform fmt + description: runs terraform fmt + entry: terraform fmt -recursive + language: system + pass_filenames: false From 491866714547c4f42ffab1b6fa06cbc71b126239 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 9 Jun 2023 10:24:52 +0200 Subject: [PATCH 3/9] Add function code folder --- code/function/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 code/function/.gitkeep diff --git a/code/function/.gitkeep b/code/function/.gitkeep new file mode 100644 index 0000000..e69de29 From 950c4f072655618440b26d9c6195f506449700a3 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 9 Jun 2023 12:54:58 +0200 Subject: [PATCH 4/9] Update terraform definition --- code/infra/terraform.tf | 20 ++++++++++---------- code/infra/variables.tf | 13 ++++--------- code/infra/vars.dev.tfvars | 25 ++++++++++++------------- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/code/infra/terraform.tf b/code/infra/terraform.tf index 7f1afcc..f871f75 100644 --- a/code/infra/terraform.tf +++ b/code/infra/terraform.tf @@ -4,22 +4,22 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "3.53.0" + version = "3.59.0" } azapi = { source = "azure/azapi" - version = "1.5.0" + version = "1.6.0" } } backend "azurerm" { environment = "public" - subscription_id = "176a0de9-d99e-4faa-90cc-89780e1186fe" - resource_group_name = "workload000-cicd" - storage_account_name = "workload000stg001" - container_name = "terraform" - key = "terraform-function20.tfstate" - use_oidc = true + subscription_id = "8f171ff9-2b5b-4f0f-aed5-7fa360a1d094" + resource_group_name = "mycrp-prd-cicd" + storage_account_name = "mycrpprdstg001" + container_name = "function" + key = "terraform.tfstate" + # use_oidc = true } } @@ -28,7 +28,7 @@ provider "azurerm" { environment = "public" skip_provider_registration = false storage_use_azuread = true - use_oidc = true + # use_oidc = true features { key_vault { @@ -52,5 +52,5 @@ provider "azapi" { disable_correlation_request_id = false environment = "public" skip_provider_registration = false - use_oidc = true + # use_oidc = true } diff --git a/code/infra/variables.tf b/code/infra/variables.tf index 3227511..57c3fb7 100644 --- a/code/infra/variables.tf +++ b/code/infra/variables.tf @@ -10,8 +10,8 @@ variable "environment" { sensitive = false default = "dev" validation { - condition = contains(["dev", "tst", "prd"], var.environment) - error_message = "Please use an allowed value: \"dev\", \"tst\" or \"prd\"." + condition = contains(["int", "dev", "tst", "qa", "uat", "prd"], var.environment) + error_message = "Please use an allowed value: \"int\", \"dev\", \"tst\", \"qa\", \"uat\" or \"prd\"." } } @@ -27,14 +27,9 @@ variable "prefix" { variable "tags" { description = "Specifies the tags that you want to apply to all resources." - type = map(any) - sensitive = false -} - -variable "timestamp_expiry" { - description = "Specifies the initial key vault key expiry." - type = number + type = map(string) sensitive = false + default = {} } variable "vnet_id" { diff --git a/code/infra/vars.dev.tfvars b/code/infra/vars.dev.tfvars index 11557eb..89fce27 100644 --- a/code/infra/vars.dev.tfvars +++ b/code/infra/vars.dev.tfvars @@ -1,13 +1,12 @@ -location = "eastus" -environment = "dev" -prefix = "mb020" -tags = {} -timestamp_expiry = 1716628196 -vnet_id = "/subscriptions/6a439cba-0e4d-46ca-9eda-e3b7a8f60a49/resourceGroups/vnet-eastus-rg002/providers/Microsoft.Network/virtualNetworks/vnet-eastus002" -nsg_id = "/subscriptions/6a439cba-0e4d-46ca-9eda-e3b7a8f60a49/resourceGroups/vnet-eastus-rg002/providers/Microsoft.Network/networkSecurityGroups/nsg-eastus002" -route_table_id = "/subscriptions/6a439cba-0e4d-46ca-9eda-e3b7a8f60a49/resourceGroups/vnet-eastus-rg002/providers/Microsoft.Network/routeTables/rt-eastus002" -private_dns_zone_id_blob = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net" -private_dns_zone_id_queue = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.queue.core.windows.net" -private_dns_zone_id_table = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.table.core.windows.net" -private_dns_zone_id_file = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.file.core.windows.net" -private_dns_zone_id_key_vault = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.vaultcore.azure.net" +location = "northeurope" +environment = "dev" +prefix = "myfunc" +tags = {} +vnet_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-function-network-rg/providers/Microsoft.Network/virtualNetworks/mycrp-prd-function-vnet001" +nsg_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-function-network-rg/providers/Microsoft.Network/networkSecurityGroups/mycrp-prd-function-nsg001" +route_table_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-function-network-rg/providers/Microsoft.Network/routeTables/mycrp-prd-function-rt001" +private_dns_zone_id_blob = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net" +private_dns_zone_id_queue = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.queue.core.windows.net" +private_dns_zone_id_table = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.table.core.windows.net" +private_dns_zone_id_file = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.file.core.windows.net" +private_dns_zone_id_key_vault = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.vaultcore.azure.net" From 6ac0b5b733bd234488c3b6b23361480ea42d25d5 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 9 Jun 2023 12:55:13 +0200 Subject: [PATCH 5/9] Add github baseline --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/BUG_REPORT.yml | 44 ++++++ .../ISSUE_TEMPLATE/DOCUMENTATION_ISSUE.yml | 24 ++++ .github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml | 33 +++++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/PULL_REQUEST_TEMPLATE.md | 2 + .github/dependabot.yml | 36 +++++ .github/workflows/_terraformApplyTemplate.yml | 82 ++++++++++++ .github/workflows/_terraformLintTemplate.yml | 63 +++++++++ .github/workflows/_terraformPlanTemplate.yml | 125 ++++++++++++++++++ .github/workflows/lint.yml | 44 ++++++ .github/workflows/newMajorVersion.yml | 32 +++++ .github/workflows/terraform.yml | 50 +++++++ 13 files changed, 537 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/BUG_REPORT.yml create mode 100644 .github/ISSUE_TEMPLATE/DOCUMENTATION_ISSUE.yml create mode 100644 .github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/_terraformApplyTemplate.yml create mode 100644 .github/workflows/_terraformLintTemplate.yml create mode 100644 .github/workflows/_terraformPlanTemplate.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/newMajorVersion.yml create mode 100644 .github/workflows/terraform.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..8a6256e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* PerfectThymeTech/csa-admins diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml new file mode 100644 index 0000000..341a020 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -0,0 +1,44 @@ +name: "Bug report 🐛" +description: Report errors or unexpected behavior +title: "Bug: " +labels: + - bug + - backlog +assignees: "marvinbuss" + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this report! + + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + placeholder: Please share with us the step(s) required to reproduce the bug. + validations: + required: true + + - type: textarea + id: error + attributes: + label: Error Message + description: If possible, please share the error message that you received. + placeholder: Error Messages + render: json + validations: + required: false + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If possible, please include screenshots and paste them into the markdown editor below. + placeholder: Screenshots + validations: + required: false + + - type: markdown + attributes: + value: "Thanks for completing this form!" diff --git a/.github/ISSUE_TEMPLATE/DOCUMENTATION_ISSUE.yml b/.github/ISSUE_TEMPLATE/DOCUMENTATION_ISSUE.yml new file mode 100644 index 0000000..e4e3d7f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/DOCUMENTATION_ISSUE.yml @@ -0,0 +1,24 @@ +name: "Documentation Issue 📚" +description: Report issues in our documentation +title: "Documentation: " +labels: + - documentation + - backlog + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this report! + + - type: textarea + id: description + attributes: + label: Documentation Issue + placeholder: Please share the name of the document along with supporting points on why it requires an amendment. + validations: + required: true + + - type: markdown + attributes: + value: "Thanks for completing this form!" diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml new file mode 100644 index 0000000..c0b8575 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml @@ -0,0 +1,33 @@ +name: "Feature Request & Ideas 🚀" +description: Suggest a new feature or improvement (this does not mean you have to implement it) +title: "Feature: " +labels: + - feature + - backlog +assignees: "marvinbuss" + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this report! + + - type: textarea + id: feature_description + attributes: + label: Feature or Idea - What? + placeholder: Briefly describe what the new feature, idea or enhancement entails. + validations: + required: true + + - type: textarea + id: feature_reason + attributes: + label: Feature or Idea - Why? + placeholder: Briefly describe why the new feature, idea or enhancement is required and what outcome you are trying to achieve. + validations: + required: true + + - type: markdown + attributes: + value: "Thanks for completing this form!" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0086358 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f6ae540 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,2 @@ +**Proposed changes**: +- ... diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e4a4227 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,36 @@ +version: 2 +# enable-beta-ecosystems: true +updates: + + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "sunday" + time: "10:00" + labels: + - "github-actions" + - "dependencies" + + # Maintain dependencies for pip + - package-ecosystem: "pip" + directory: "code/function/" + schedule: + interval: "weekly" + day: "sunday" + time: "10:00" + labels: + - "pip" + - "dependencies" + + # Maintain dependencies for Terraform + - package-ecosystem: "terraform" + directory: "/code/infra" + schedule: + interval: "weekly" + day: "sunday" + time: "10:00" + labels: + - "terraform" + - "dependencies" diff --git a/.github/workflows/_terraformApplyTemplate.yml b/.github/workflows/_terraformApplyTemplate.yml new file mode 100644 index 0000000..c7f188e --- /dev/null +++ b/.github/workflows/_terraformApplyTemplate.yml @@ -0,0 +1,82 @@ +name: Terraform Apply Template + +on: + workflow_call: + inputs: + environment: + required: true + type: string + default: "dev" + description: "Specifies the environment of the deployment." + terraform_version: + required: true + type: string + description: "Specifies the terraform version." + working_directory: + required: true + type: string + description: "Specifies the working directory." + secrets: + TENANT_ID: + required: true + description: "Specifies the tenant id of the deployment." + CLIENT_ID: + required: true + description: "Specifies the client id." + CLIENT_SECRET: + required: true + description: "Specifies the client secret." + SUBSCRIPTION_ID: + required: true + description: "Specifies the client id." + +permissions: + id-token: write + contents: read + +jobs: + deployment: + name: Terraform Apply + runs-on: self-hosted + continue-on-error: false + environment: ${{ inputs.environment }} + + env: + ARM_TENANT_ID: ${{ secrets.TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.SUBSCRIPTION_ID }} + ARM_CLIENT_ID: ${{ secrets.CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + ARM_USE_OIDC: false + + steps: + # Setup Node + - name: Setup Node + id: node_setup + uses: actions/setup-node@v3 + with: + node-version: 16 + + # Setup Terraform + - name: Setup Terraform + id: terraform_setup + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ inputs.terraform_version }} + terraform_wrapper: true + + # Check Out Repository + - name: Check Out Repository + id: checkout_repository + uses: actions/checkout@v3 + + # Terraform Init + - name: Terraform Init + working-directory: ${{ inputs.working_directory }} + run: | + terraform init + + # Terraform Apply + - name: Terraform Apply + working-directory: ${{ inputs.working_directory }} + run: | + terraform apply -var-file vars.${{ inputs.environment }}.tfvars -auto-approve -input=false diff --git a/.github/workflows/_terraformLintTemplate.yml b/.github/workflows/_terraformLintTemplate.yml new file mode 100644 index 0000000..68a1ff9 --- /dev/null +++ b/.github/workflows/_terraformLintTemplate.yml @@ -0,0 +1,63 @@ +name: Terraform Lint Template + +on: + workflow_call: + inputs: + terraform_version: + required: true + type: string + description: "Specifies the terraform version." + working_directory: + required: true + type: string + description: "Specifies the working directory." + +permissions: + pull-requests: write + +jobs: + deployment: + name: Terraform Lint + runs-on: ubuntu-latest + continue-on-error: false + + steps: + # Setup Terraform + - name: Setup Terraform + id: terraform_setup + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ inputs.terraform_version }} + terraform_wrapper: true + + # Check Out Repository + - name: Check Out Repository + id: checkout_repository + uses: actions/checkout@v3 + + # Terraform Format + - name: Terraform Format + id: terraform_format + working-directory: ${{ inputs.working_directory }} + run: | + terraform fmt -check -recursive + + # Add Pull Request Comment + - name: Add Pull Request Comment + uses: actions/github-script@v6 + id: pr_comment + if: github.event_name == 'pull_request' + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const output = `#### Terraform Lint Results + * Terraform Version 📎\`${{ inputs.terraform_version }}\` + * Working Directory 📂\`${{ inputs.working_directory }}\` + * Terraform Format and Style 🖌\`${{ steps.terraform_format.outcome }}\``; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) diff --git a/.github/workflows/_terraformPlanTemplate.yml b/.github/workflows/_terraformPlanTemplate.yml new file mode 100644 index 0000000..0c8530d --- /dev/null +++ b/.github/workflows/_terraformPlanTemplate.yml @@ -0,0 +1,125 @@ +name: Terraform Plan Template + +on: + workflow_call: + inputs: + environment: + required: true + type: string + default: "dev" + description: "Specifies the environment of the deployment." + terraform_version: + required: true + type: string + description: "Specifies the terraform version." + working_directory: + required: true + type: string + description: "Specifies the working directory." + secrets: + TENANT_ID: + required: true + description: "Specifies the tenant id of the deployment." + CLIENT_ID: + required: true + description: "Specifies the client id." + CLIENT_SECRET: + required: true + description: "Specifies the client secret." + SUBSCRIPTION_ID: + required: true + description: "Specifies the client id." + +permissions: + id-token: write + contents: read + pull-requests: write + +jobs: + deployment: + name: Terraform Plan + runs-on: self-hosted + continue-on-error: false + environment: ${{ inputs.environment }} + + env: + ARM_TENANT_ID: ${{ secrets.TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.SUBSCRIPTION_ID }} + ARM_CLIENT_ID: ${{ secrets.CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + ARM_USE_OIDC: false + + steps: + # Setup Node + - name: Setup Node + id: node_setup + uses: actions/setup-node@v3 + with: + node-version: 16 + + # Setup Terraform + - name: Setup Terraform + id: terraform_setup + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ inputs.terraform_version }} + terraform_wrapper: true + + # Check Out Repository + - name: Check Out Repository + id: checkout_repository + uses: actions/checkout@v3 + + # Terraform Init + - name: Terraform Init + id: terraform_init + working-directory: ${{ inputs.working_directory }} + run: | + terraform init + + # Terraform Validate + - name: Terraform Validate + id: terraform_validate + working-directory: ${{ inputs.working_directory }} + run: | + terraform validate + + # Terraform Plan + - name: Terraform Plan + id: terraform_plan + working-directory: ${{ inputs.working_directory }} + run: | + terraform plan -var-file vars.${{ inputs.environment }}.tfvars -input=false + + # Add Pull Request Comment + - name: Add Pull Request Comment + id: pr_comment + uses: actions/github-script@v6 + if: github.event_name == 'pull_request' + continue-on-error: true + env: + PLAN: "terraform\n${{ steps.terraform_plan.outputs.stdout }}" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const output = `#### Terraform Validation & Plan Results + * Terraform Version 📎\`${{ inputs.terraform_version }}\` + * Working Directory 📂\`${{ inputs.working_directory }}\` + * Terraform Initialization ⚙️\`${{ steps.terraform_init.outcome }}\` + * Terraform Validation 🤖\`${{ steps.terraform_validate.outcome }}\` + * Terraform Plan 📖\`${{ steps.terraform_plan.outcome }}\` + +
Show Plan + + \`\`\`\n + ${process.env.PLAN} + \`\`\` + +
`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..a7bc693 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,44 @@ +name: Lint and Test +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + lint: + name: Lint and Test + runs-on: ubuntu-latest + + steps: + # Setup Python 3.10 + - name: Setup Python 3.10 + id: python_setup + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + # Setup Terraform + - name: Setup Terraform + id: terraform_setup + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: "1.4.6" + terraform_wrapper: true + + # Checkout repository + - name: Check Out Repository + id: checkout_repository + uses: actions/checkout@v3 + + # Run Linting + - name: Run Linting + id: linting + run: | + pip install -r requirements.txt -q + git init + git add * + pre-commit install --install-hooks + pre-commit run --all-files --verbose diff --git a/.github/workflows/newMajorVersion.yml b/.github/workflows/newMajorVersion.yml new file mode 100644 index 0000000..79aad15 --- /dev/null +++ b/.github/workflows/newMajorVersion.yml @@ -0,0 +1,32 @@ +name: Release new major version + +on: + release: + types: [released] + workflow_dispatch: + inputs: + tag_name: + required: true + type: string + description: 'Tag name that the major tag will point to' + +permissions: + contents: write +env: + TAG_NAME: ${{ github.event.inputs.tag_name || github.event.release.tag_name }} + + +jobs: + update_tag: + name: Update the major tag to include the ${{ github.event.inputs.tag_name || github.event.release.tag_name }} changes + runs-on: ubuntu-latest + continue-on-error: false + + steps: + # Update major tag + - name: Update the ${{ env.TAG_NAME }} tag + id: update_tag + uses: actions/publish-action@v0.2.2 + with: + source-tag: ${{ env.TAG_NAME }} + slack-webhook: "" diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml new file mode 100644 index 0000000..f1a9d92 --- /dev/null +++ b/.github/workflows/terraform.yml @@ -0,0 +1,50 @@ +name: Infrastructure Deployment +on: + push: + branches: + - main + paths: + - "**.tf" + + pull_request: + branches: + - main + paths: + - "**.tf" + +jobs: + terraform_lint: + uses: ./.github/workflows/_terraformLintTemplate.yml + name: "Terraform Lint" + with: + terraform_version: "1.4.6" + working_directory: "./code/infra" + + terraform_plan_dev: + uses: ./.github/workflows/_terraformPlanTemplate.yml + name: "Terraform Plan" + needs: [terraform_lint] + with: + environment: "dev" + terraform_version: "1.4.6" + working_directory: "./code/infra" + secrets: + TENANT_ID: ${{ secrets.TENANT_ID }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + SUBSCRIPTION_ID: ${{ secrets.SUBSCRIPTION_ID }} + + terraform_apply_dev: + uses: ./.github/workflows/_terraformApplyTemplate.yml + name: "Terraform Apply" + needs: [terraform_plan_dev] + if: github.event_name == 'push' || github.event_name == 'release' + with: + environment: "dev" + terraform_version: "1.4.6" + working_directory: "./code/infra" + secrets: + TENANT_ID: ${{ secrets.TENANT_ID }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + SUBSCRIPTION_ID: ${{ secrets.SUBSCRIPTION_ID }} From 1254f80566d86c432c53bb6d32c1e28c0fc02eac Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 9 Jun 2023 12:55:59 +0200 Subject: [PATCH 6/9] Add docs folder --- docs/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/.gitkeep diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 From ef1950f3ea7426ecdd4af4d52fb81fabe18ac84f Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 9 Jun 2023 12:56:09 +0200 Subject: [PATCH 7/9] Add tests folder --- tests/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/.gitkeep diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..e69de29 From 56c866a868d458a812f344149ec9a606738143e6 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 9 Jun 2023 12:58:14 +0200 Subject: [PATCH 8/9] Update Readme --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b8f3c9b..f9f0fe7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ -# AzureFunctionPython -This repository provides a scalable baseline for Azure Function written in Python. +# Azure Function Baseline for Python + +This repository provides a scalable baseline for Azure Functions written in Python. It demonstrates: + +1. A compliant infrastructure baseline written in Terraform, +2. A Python code baseline that follows best practices and +3. A safe rollout mechanism of code artifacts. From 36a7e8e3f15e8f3781512ad48269007fd47d0e76 Mon Sep 17 00:00:00 2001 From: Marvin Buss Date: Fri, 9 Jun 2023 12:59:29 +0200 Subject: [PATCH 9/9] Add requirements for pre-commit --- requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d3e5ff0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pre-commit~=3.3.2