From b1686cf3dc36a96e8451572e4413de67af8ab861 Mon Sep 17 00:00:00 2001 From: ahelland Date: Wed, 31 Jan 2024 21:24:15 +0100 Subject: [PATCH] Add infra part two --- infra/level-4/main.bicep | 421 ++++++++++++++++++ infra/level-4/main.bicepparam | 26 ++ .../containers/container-app-acr/README.md | 46 ++ .../containers/container-app-acr/main.bicep | 111 +++++ .../containers/container-app-acr/main.json | 208 +++++++++ .../container-app-acr/test/main.test.bicep | 30 ++ .../containers/container-app-acr/version.json | 7 + .../container-app-docker-hub/README.md | 43 ++ .../container-app-docker-hub/main.bicep | 88 ++++ .../container-app-docker-hub/main.json | 170 +++++++ .../test/main.test.bicep | 28 ++ .../container-app-docker-hub/version.json | 7 + .../container-app-service/README.md | 35 ++ .../container-app-service/main.bicep | 35 ++ .../container-app-service/main.json | 78 ++++ .../test/main.test.bicep | 27 ++ .../container-app-service/version.json | 7 + infra/playbook.dib | 17 +- 18 files changed, 1383 insertions(+), 1 deletion(-) create mode 100644 infra/level-4/main.bicep create mode 100644 infra/level-4/main.bicepparam create mode 100644 infra/modules/containers/container-app-acr/README.md create mode 100644 infra/modules/containers/container-app-acr/main.bicep create mode 100644 infra/modules/containers/container-app-acr/main.json create mode 100644 infra/modules/containers/container-app-acr/test/main.test.bicep create mode 100644 infra/modules/containers/container-app-acr/version.json create mode 100644 infra/modules/containers/container-app-docker-hub/README.md create mode 100644 infra/modules/containers/container-app-docker-hub/main.bicep create mode 100644 infra/modules/containers/container-app-docker-hub/main.json create mode 100644 infra/modules/containers/container-app-docker-hub/test/main.test.bicep create mode 100644 infra/modules/containers/container-app-docker-hub/version.json create mode 100644 infra/modules/containers/container-app-service/README.md create mode 100644 infra/modules/containers/container-app-service/main.bicep create mode 100644 infra/modules/containers/container-app-service/main.json create mode 100644 infra/modules/containers/container-app-service/test/main.test.bicep create mode 100644 infra/modules/containers/container-app-service/version.json diff --git a/infra/level-4/main.bicep b/infra/level-4/main.bicep new file mode 100644 index 0000000..f72e7ee --- /dev/null +++ b/infra/level-4/main.bicep @@ -0,0 +1,421 @@ +targetScope = 'subscription' + +@description('Location for Container Environment') +param location string +@description('Tags retrieved from parameter file.') +param resourceTags object = {} + +param subId string = subscription().id +param acrName string = 'acr${uniqueString(subId)}' + +//Conditionals for granular deployment of individual services +// Note: You may see deployment errors when not enabling services, +// but it should still go through for the ones set to 'true'. +param deploy_postgres bool +param deploy_rabbitMQ bool +param deploy_redis bool + +param deploy_basketAPI bool +param deploy_catalogAPI bool +param deploy_identityAPI bool +param deploy_bff bool +param deploy_orderProcessor bool +param deploy_orderingAPI bool +param deploy_paymentProcessor bool +param deploy_webApp bool +param deploy_webhooksAPI bool +param deploy_webhooksClient bool + +resource rg_cae 'Microsoft.Resources/resourceGroups@2021-04-01' existing = { + name: 'rg-eshop-cae' +} + +resource containerenvironment 'Microsoft.App/managedEnvironments@2023-05-02-preview' existing = { + scope: rg_cae + name: 'eshop-cae-01' +} + +/* If you want to deploy Redis as a service instead of an app use this snippet. +module redisService '../modules/containers/container-app-service/main.bicep' = { + scope: rg_cae + name: 'eshop-redis' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + serviceName: 'redis' + serviceType: 'redis' + } +} +*/ + +module redisApp '../modules/containers/container-app-docker-hub/main.bicep' = if(deploy_redis) { + scope: rg_cae + name: 'eshop-redis' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + name: 'redis' + containerName: 'redis' + containerImage: 'redis:latest' + targetPort: 6379 + transport: 'tcp' + minReplicas: 1 + } +} + +module rabbitMQApp '../modules/containers/container-app-docker-hub/main.bicep' = if(deploy_rabbitMQ) { + scope: rg_cae + name: 'eshop-rabbitmq' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + name: 'eventbus' + containerImage: 'rabbitmq:3-management' + containerName: 'eventbus' + targetPort: 5672 + transport: 'tcp' + minReplicas: 1 + } +} + +// Deploys a PostgreSQL database with vector support +module postgresVectorApp '../modules/containers/container-app-docker-hub/main.bicep' = if(deploy_postgres) { + scope: rg_cae + name: 'eshop-postgres' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + name: 'postgres' + containerImage: 'ankane/pgvector:latest' + containerName: 'postgres' + targetPort: 5432 + transport: 'tcp' + minReplicas: 1 + envVars: [ + { name: 'POSTGRES_HOST_AUTH_METHOD', value: 'scram-sha-256' } + { name: 'POSTGRES_INITDB_ARGS', value: '--auth-host=scram-sha-256 --auth-local=scram-sha-256' } + { name: 'POSTGRES_PASSWORD', value: 'foo' } + ] + } +} + +module basketapi '../modules/containers/container-app-acr/main.bicep' = if(deploy_basketAPI) { + scope: rg_cae + name: 'eshop-basketapi' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/basketapi:latest' + targetPort: 8080 + //The basket api uses gRPC which required http2 + transport: 'http2' + externalIngress: false + containerName: 'basket-api' + identityName: 'eshop-cae-user-mi' + name: 'basket-api' + minReplicas: 1 + //If you want to bind to a Redis service uncomment the line below + //serviceId: redisService.outputs.id + envVars: [ + { name: 'Identity__Url', value: 'https://identity-api.${containerenvironment.properties.defaultDomain}' } + { name: 'ConnectionStrings__redis', value: 'redis:6379' } + { name: 'ConnectionStrings__EventBus', value: 'amqp://guest:guest@eventbus:5672' } + ] + } +} + +module basketapiDns '../modules/network/private-dns-record-a/main.bicep' = if(deploy_basketAPI) { + scope: rg_cae + name: 'eshop-basketapiDns' + params: { + ipAddress: containerenvironment.properties.staticIp + recordName: basketapi.outputs.name + zone: containerenvironment.properties.defaultDomain + } +} + +module catalogapi '../modules/containers/container-app-acr/main.bicep' = if(deploy_catalogAPI) { + scope: rg_cae + name: 'eshop-catalogapi' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/catalogapi:latest' + externalIngress: true + targetPort: 8080 + transport: 'http' + containerName: 'catalog-api' + identityName: 'eshop-cae-user-mi' + name: 'catalog-api' + minReplicas: 1 + envVars: [ + { name: 'ConnectionStrings__EventBus', value: 'amqp://guest:guest@eventbus:5672' } + //See: https://learn.microsoft.com/en-us/dotnet/aspire/database/postgresql-entity-framework-component?tabs=dotnet-cli + { name: 'Aspire__Npgsql__EntityFrameworkCore__PostgreSQL__ConnectionString', value: 'Host=postgres;Database=CatalogDB;Port=5432;Username=postgres;Password=foo' } + ] + } +} + +module catalogapiDns '../modules/network/private-dns-record-a/main.bicep' = if(deploy_catalogAPI) { + scope: rg_cae + name: 'eshop-catalogapiDns' + params: { + ipAddress: containerenvironment.properties.staticIp + recordName: catalogapi.outputs.name + zone: containerenvironment.properties.defaultDomain + } +} + +module identityapi '../modules/containers/container-app-acr/main.bicep' = if(deploy_identityAPI) { + scope: rg_cae + name: 'eshop-identityapi' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/identityapi:latest' + targetPort: 8080 + transport: 'http' + containerName: 'identity-api' + identityName: 'eshop-cae-user-mi' + name: 'identity-api' + minReplicas: 1 + envVars: [ + { name: 'ASPNETCORE_ENVIRONMENT', value: 'Development' } + { name: 'ASPNETCORE_FORWARDEDHEADERS_ENABLED', value: 'true' } + { name: 'BasketApiClient', value: 'https://basket-api.internal.${containerenvironment.properties.defaultDomain}' } + { name: 'OrderingApiClient', value: 'https://ordering-api.${containerenvironment.properties.defaultDomain}' } + { name: 'WebhooksApiClient', value: 'https://webhooks-api.${containerenvironment.properties.defaultDomain}' } + { name: 'WebhooksWebClient', value: 'https://webhooksclient.${containerenvironment.properties.defaultDomain}' } + { name: 'WebAppClient', value: 'https://webapp.${containerenvironment.properties.defaultDomain}' } + { name: 'ConnectionStrings__IdentityDB', value: 'Host=postgres;Database=IdentityDB;Port=5432;Username=postgres;Password=foo' } + ] + } +} + +module identityapiDns '../modules/network/private-dns-record-a/main.bicep' = if(deploy_identityAPI) { + scope: rg_cae + name: 'eshop-identityapiDns' + params: { + ipAddress: containerenvironment.properties.staticIp + recordName: identityapi.outputs.name + zone: containerenvironment.properties.defaultDomain + } +} + +module orderingapi '../modules/containers/container-app-acr/main.bicep' = if(deploy_orderingAPI) { + scope: rg_cae + name: 'eshop-orderingapi' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/orderingapi:latest' + targetPort: 8080 + transport: 'http' + containerName: 'ordering-api' + identityName: 'eshop-cae-user-mi' + name: 'ordering-api' + minReplicas: 1 + envVars: [ + { name: 'Identity__Url', value: 'https://identity-api.${containerenvironment.properties.defaultDomain}' } + { name: 'ConnectionStrings__EventBus', value: 'amqp://guest:guest@eventbus:5672' } + { name: 'ConnectionStrings__OrderingDB', value: 'Host=postgres;Database=OrderingDB;Port=5432;Username=postgres;Password=foo' } + ] + } +} + +module orderingapiDns '../modules/network/private-dns-record-a/main.bicep' = if(deploy_orderingAPI) { + scope: rg_cae + name: 'eshop-orderingapiDns' + params: { + ipAddress: containerenvironment.properties.staticIp + recordName: orderingapi.outputs.name + zone: containerenvironment.properties.defaultDomain + } +} + +module mobilebffshopping '../modules/containers/container-app-acr/main.bicep' = if(deploy_bff) { + scope: rg_cae + name: 'eshop-mobilebffshopping' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/mobilebffshopping:latest' + targetPort: 8080 + containerName: 'mobile-bff' + identityName: 'eshop-cae-user-mi' + name: 'mobile-bff' + minReplicas: 1 + envVars: [ + { name: 'services__catalog-api__0', value: 'catalog-api' } + { name: 'services__catalog-api__1', value: 'catalog-api' } + { name: 'services__identity-api__0', value: 'identity-api' } + { name: 'services__identity-api__1', value: 'identity-api' } + ] + } +} + +module orderprocessor '../modules/containers/container-app-acr/main.bicep' = if(deploy_orderProcessor) { + scope: rg_cae + name: 'eshop-orderprocessor' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/orderprocessor:latest' + targetPort: 8080 + transport: 'http' + containerName: 'order-processor' + identityName: 'eshop-cae-user-mi' + name: 'order-processor' + minReplicas: 1 + envVars: [ + { name: 'ConnectionStrings__EventBus', value: 'amqp://guest:guest@eventbus:1672' } + { name: 'ConnectionStrings__OrderingDB', value: 'Host=postgres;Database=OrderingDB;Port=5432;Username=postgres;Password=foo' } + ] + } +} + +module paymentprocessor '../modules/containers/container-app-acr/main.bicep' = if(deploy_paymentProcessor) { + scope: rg_cae + name: 'eshop-paymentprocessor' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/paymentprocessor:latest' + targetPort: 8080 + transport: 'http' + externalIngress: false + containerName: 'payment-processor' + identityName: 'eshop-cae-user-mi' + name: 'payment-processor' + minReplicas: 1 + envVars: [ + { name: 'ConnectionStrings__EventBus', value: 'amqp://guest:guest@eventbus:5672' } + ] + } +} + +module webhooksapi '../modules/containers/container-app-acr/main.bicep' = if(deploy_webhooksAPI) { + scope: rg_cae + name: 'eshop-webhooksapi' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/webhooksapi:latest' + targetPort: 8080 + transport: 'http' + externalIngress: false + containerName: 'webhooks-api' + identityName: 'eshop-cae-user-mi' + name: 'webhooks-api' + minReplicas: 1 + envVars: [ + { name: 'ConnectionStrings__EventBus', value: 'amqp://guest:guest@eventbus:5672' } + { name: 'ConnectionStrings__WebhooksDB', value: 'Host=postgres;Database=WebhooksDB;Port=5432;Username=postgres;Password=foo' } + { name: 'Identity__Url', value: 'https://identity-api.${containerenvironment.properties.defaultDomain}' } + ] + } +} + +// module webhooksapiDns '../modules/network/private-dns-record-a/main.bicep' = if(deploy_webhooksAPI) { +// scope: rg_cae +// name: 'eshop-webhooksapiDns' +// params: { +// ipAddress: containerenvironment.properties.staticIp +// recordName: webhooksapi.outputs.name +// zone: containerenvironment.properties.defaultDomain +// } +// } + +module webhookclient '../modules/containers/container-app-acr/main.bicep' = if(deploy_webhooksClient) { + scope: rg_cae + name: 'eshop-webhookclient' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/webhookclient:latest' + targetPort: 8080 + containerName: 'webhooksclient' + identityName: 'eshop-cae-user-mi' + name: 'webhooksclient' + minReplicas: 1 + envVars: [ + { name: 'IdentityUrl', value: 'https://identity-api.${containerenvironment.properties.defaultDomain}' } + { name: 'CallBackUrl', value: 'https://webhooksclient.${containerenvironment.properties.defaultDomain}/signin-oidc' } + { name: 'services__webhooks-api__0', value: 'http://webhooks-api.internal.${containerenvironment.properties.defaultDomain}' } + { name: 'services__webhooks-api__1', value: 'https://webhooks-api.internal.${containerenvironment.properties.defaultDomain}' } + ] + } +} + +module webhookclientDns '../modules/network/private-dns-record-a/main.bicep' = if(deploy_webhooksClient) { + scope: rg_cae + name: 'eshop-webhookclientDns' + params: { + ipAddress: containerenvironment.properties.staticIp + recordName: webhookclient.outputs.name + zone: containerenvironment.properties.defaultDomain + } +} + +module webapp '../modules/containers/container-app-acr/main.bicep' = if(deploy_webApp) { + scope: rg_cae + name: 'eshop-webapp' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: '${acrName}.azurecr.io' + containerImage: '${acrName}.azurecr.io/webapp:latest' + targetPort: 8080 + transport: 'http' + containerName: 'webapp' + identityName: 'eshop-cae-user-mi' + name: 'webapp' + minReplicas: 1 + envVars: [ + { name: 'ASPNETCORE_ENVIRONMENT', value: 'Development' } + { name: 'ASPNETCORE_FORWARDEDHEADERS_ENABLED', value: 'true' } + { name: 'IdentityUrl', value: 'https://identity-api.${containerenvironment.properties.defaultDomain}' } + { name: 'CallBackUrl', value: 'https://webapp.${containerenvironment.properties.defaultDomain}/signin-oidc' } + { name: 'ConnectionStrings__EventBus', value: 'amqp://guest:guest@eventbus:5672' } + { name: 'services__basket-api__0', value: 'http://basket-api.internal.${containerenvironment.properties.defaultDomain}' } + { name: 'services__basket-api__1', value: 'https://basket-api.internal.${containerenvironment.properties.defaultDomain}' } + { name: 'services__catalog-api__0', value: 'http://catalog-api.internal.${containerenvironment.properties.defaultDomain}' } + { name: 'services__catalog-api__1', value: 'https://catalog-api.internal.${containerenvironment.properties.defaultDomain}' } + { name: 'services__ordering-api__0', value: 'http://ordering-api.internal.${containerenvironment.properties.defaultDomain}' } + { name: 'services__ordering-api__1', value: 'https://ordering-api.internal.${containerenvironment.properties.defaultDomain}' } + ] + } +} + +module webappDns '../modules/network/private-dns-record-a/main.bicep' = if(deploy_webApp) { + scope: rg_cae + name: 'eshop-webappDns' + params: { + ipAddress: containerenvironment.properties.staticIp + recordName: webapp.outputs.name + zone: containerenvironment.properties.defaultDomain + } +} diff --git a/infra/level-4/main.bicepparam b/infra/level-4/main.bicepparam new file mode 100644 index 0000000..da1db90 --- /dev/null +++ b/infra/level-4/main.bicepparam @@ -0,0 +1,26 @@ +using './main.bicep' + +param resourceTags = { + IaC: 'Bicep' + Source: 'GitHub' +} + +param location = 'westeurope' + +// Conditionals for granular deployment of individual services +// Note: You may see deployment errors when not enabling services, +// but it should still go through for the ones set to 'true'. +param deploy_postgres = true +param deploy_rabbitMQ = true +param deploy_redis = true + +param deploy_basketAPI = true +param deploy_bff = true +param deploy_catalogAPI = true +param deploy_identityAPI = true +param deploy_orderProcessor = true +param deploy_orderingAPI = true +param deploy_paymentProcessor = true +param deploy_webApp = true +param deploy_webhooksAPI = true +param deploy_webhooksClient = true diff --git a/infra/modules/containers/container-app-acr/README.md b/infra/modules/containers/container-app-acr/README.md new file mode 100644 index 0000000..c45d6d7 --- /dev/null +++ b/infra/modules/containers/container-app-acr/README.md @@ -0,0 +1,46 @@ +# Container App ACR + +Container App ACR + +## Details + +{{Add detailed information about the module}} + +## Parameters + +| Name | Type | Required | Description | +| :-------------------------- | :-------------: | :------: | :----------------------------------------------------------------------------------------- | +| `location` | `string` | No | Specifies the location for resources. | +| `resourceTags` | `object` | No | Tags retrieved from parameter file. | +| `name` | `string` | Yes | Name of container app. | +| `containerAppEnvironmentId` | `string` | Yes | The id of the container environment to deploy app to. | +| `containerImage` | `string` | No | Image of container. Defaults to mcr quickstart. | +| `targetPort` | `int` | Yes | The port exposed on the target container. | +| `transport` | `string` | No | Which transport protocol to expose. | +| `externalIngress` | `bool` | No | Enable external ingress. | +| `minReplicas` | `int` | No | Minimum number of replicas. | +| `maxReplicas` | `int` | No | Maximum number of replicas. | +| `containerName` | `string` | No | Name of container. | +| `containerRegistry` | `string` | Yes | Registry to use for pulling images from. (Assumed to be in the form contosoacr.azurecr.io) | +| `identityName` | `string` | Yes | Id of the user-assigned managed identity to use. | +| `envVars` | `array` | No | Environment variables. | +| `serviceId` | `null | string` | No | Container App Service (Redis) to bind to. | + +## Outputs + +| Name | Type | Description | +| :------------ | :------: | :---------------------------------------------------------- | +| `name` | `string` | Name of the container app. | +| `principalId` | `string` | The principalId for the system managed identity of the app. | + +## Examples + +### Example 1 + +```bicep +``` + +### Example 2 + +```bicep +``` \ No newline at end of file diff --git a/infra/modules/containers/container-app-acr/main.bicep b/infra/modules/containers/container-app-acr/main.bicep new file mode 100644 index 0000000..5ac7c63 --- /dev/null +++ b/infra/modules/containers/container-app-acr/main.bicep @@ -0,0 +1,111 @@ +metadata name = 'Container App ACR' +metadata description = 'Container App ACR' +metadata owner = 'ahelland' + +@description('Specifies the location for resources.') +param location string = resourceGroup().location +@description('Tags retrieved from parameter file.') +param resourceTags object = {} +@description('Name of container app.') +param name string +@description('The id of the container environment to deploy app to.') +param containerAppEnvironmentId string +@description('Image of container. Defaults to mcr quickstart.') +param containerImage string = 'mcr.microsoft.com/k8se/quickstart:latest' +@description('The port exposed on the target container.') +param targetPort int +@allowed([ + 'Auto' + 'http' + 'http2' + 'tcp' +]) +@description('Which transport protocol to expose.') +param transport string = 'Auto' +@description('Enable external ingress.') +param externalIngress bool = true +@description('Minimum number of replicas.') +param minReplicas int = 0 +@description('Maximum number of replicas.') +param maxReplicas int = 10 +@description('Name of container.') +param containerName string = 'simple-hello-world-container' +@description('Registry to use for pulling images from. (Assumed to be in the form contosoacr.azurecr.io)') +param containerRegistry string +@description('Id of the user-assigned managed identity to use.') +param identityName string +@description('Environment variables.') +param envVars array = [] +@description('Container App Service (Redis) to bind to.') +param serviceId string? + +resource mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' existing = { + name: identityName +} + +resource containerApp 'Microsoft.App/containerApps@2023-05-02-preview' = { + name: name + location: location + tags: resourceTags + identity: { + type: 'SystemAssigned,UserAssigned' + userAssignedIdentities: { + '${mi.id}':{} + } + } + properties: { + managedEnvironmentId: containerAppEnvironmentId + environmentId: containerAppEnvironmentId + workloadProfileName: 'Consumption' + configuration: { + registries: [ + { + identity: mi.id + server: containerRegistry + } + ] + activeRevisionsMode: 'Single' + ingress: { + external: externalIngress + targetPort: targetPort + exposedPort: 0 + transport: transport + traffic: [ + { + weight: 100 + latestRevision: true + } + ] + allowInsecure: false + } + } + template: { + serviceBinds: (!empty(serviceId)) ? [ + { + serviceId: serviceId + name: 'redis' + } + ] : [] + containers: [ + { + image: containerImage + name: containerName + env: envVars + resources: { + cpu: json('0.25') + memory: '0.5Gi' + } + } + ] + scale: { + minReplicas: minReplicas + maxReplicas: maxReplicas + } + } + } +} + +@description('Name of the container app.') +output name string = containerApp.name +@description('The principalId for the system managed identity of the app.') +output principalId string = containerApp.identity.principalId diff --git a/infra/modules/containers/container-app-acr/main.json b/infra/modules/containers/container-app-acr/main.json new file mode 100644 index 0000000..abe8c51 --- /dev/null +++ b/infra/modules/containers/container-app-acr/main.json @@ -0,0 +1,208 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "8607675583624901773" + }, + "name": "Container App ACR", + "description": "Container App ACR", + "owner": "ahelland" + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Specifies the location for resources." + } + }, + "resourceTags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags retrieved from parameter file." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of container app." + } + }, + "containerAppEnvironmentId": { + "type": "string", + "metadata": { + "description": "The id of the container environment to deploy app to." + } + }, + "containerImage": { + "type": "string", + "defaultValue": "mcr.microsoft.com/k8se/quickstart:latest", + "metadata": { + "description": "Image of container. Defaults to mcr quickstart." + } + }, + "targetPort": { + "type": "int", + "metadata": { + "description": "The port exposed on the target container." + } + }, + "transport": { + "type": "string", + "defaultValue": "Auto", + "allowedValues": [ + "Auto", + "http", + "http2", + "tcp" + ], + "metadata": { + "description": "Which transport protocol to expose." + } + }, + "externalIngress": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable external ingress." + } + }, + "minReplicas": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Minimum number of replicas." + } + }, + "maxReplicas": { + "type": "int", + "defaultValue": 10, + "metadata": { + "description": "Maximum number of replicas." + } + }, + "containerName": { + "type": "string", + "defaultValue": "simple-hello-world-container", + "metadata": { + "description": "Name of container." + } + }, + "containerRegistry": { + "type": "string", + "metadata": { + "description": "Registry to use for pulling images from. (Assumed to be in the form contosoacr.azurecr.io)" + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Id of the user-assigned managed identity to use." + } + }, + "envVars": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Environment variables." + } + }, + "serviceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Container App Service (Redis) to bind to." + } + } + }, + "resources": { + "mi": { + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-07-31-preview", + "name": "[parameters('identityName')]" + }, + "containerApp": { + "type": "Microsoft.App/containerApps", + "apiVersion": "2023-05-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('resourceTags')]", + "identity": { + "type": "SystemAssigned,UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": { + "managedEnvironmentId": "[parameters('containerAppEnvironmentId')]", + "environmentId": "[parameters('containerAppEnvironmentId')]", + "workloadProfileName": "Consumption", + "configuration": { + "registries": [ + { + "identity": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]", + "server": "[parameters('containerRegistry')]" + } + ], + "activeRevisionsMode": "Single", + "ingress": { + "external": "[parameters('externalIngress')]", + "targetPort": "[parameters('targetPort')]", + "exposedPort": 0, + "transport": "[parameters('transport')]", + "traffic": [ + { + "weight": 100, + "latestRevision": true + } + ], + "allowInsecure": false + } + }, + "template": { + "serviceBinds": "[if(not(empty(parameters('serviceId'))), createArray(createObject('serviceId', parameters('serviceId'), 'name', 'redis')), createArray())]", + "containers": [ + { + "image": "[parameters('containerImage')]", + "name": "[parameters('containerName')]", + "env": "[parameters('envVars')]", + "resources": { + "cpu": "[json('0.25')]", + "memory": "0.5Gi" + } + } + ], + "scale": { + "minReplicas": "[parameters('minReplicas')]", + "maxReplicas": "[parameters('maxReplicas')]" + } + } + }, + "dependsOn": [ + "mi" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the container app." + }, + "value": "[parameters('name')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "The principalId for the system managed identity of the app." + }, + "value": "[reference('containerApp', '2023-05-02-preview', 'full').identity.principalId]" + } + } +} \ No newline at end of file diff --git a/infra/modules/containers/container-app-acr/test/main.test.bicep b/infra/modules/containers/container-app-acr/test/main.test.bicep new file mode 100644 index 0000000..40cf4d1 --- /dev/null +++ b/infra/modules/containers/container-app-acr/test/main.test.bicep @@ -0,0 +1,30 @@ +targetScope = 'subscription' + +@description('Location for Container App.') +param location string +@description('Tags retrieved from parameter file.') +param resourceTags object = {} + +resource rg_cae 'Microsoft.Resources/resourceGroups@2021-04-01' existing = { + name: 'rg-cae' +} + +resource containerenvironment 'Microsoft.App/managedEnvironments@2023-05-02-preview' existing = { + scope: rg_cae + name: 'cae-01' +} + +module helloApp '../main.bicep' = { + scope: rg_cae + name: 'container-app-hello' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + containerRegistry: 'eshop.azurecr.io' + containerImage: 'helloword:latest' + targetPort: 8080 + identityName: 'eshop-user-mi' + name: 'hello' + } +} diff --git a/infra/modules/containers/container-app-acr/version.json b/infra/modules/containers/container-app-acr/version.json new file mode 100644 index 0000000..a830c3d --- /dev/null +++ b/infra/modules/containers/container-app-acr/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.10", + "pathFilters": [ + "./main.json" + ] +} \ No newline at end of file diff --git a/infra/modules/containers/container-app-docker-hub/README.md b/infra/modules/containers/container-app-docker-hub/README.md new file mode 100644 index 0000000..c4c659a --- /dev/null +++ b/infra/modules/containers/container-app-docker-hub/README.md @@ -0,0 +1,43 @@ +# Container App Docker Hub + +Container App Docker Hub + +## Details + +{{Add detailed information about the module}} + +## Parameters + +| Name | Type | Required | Description | +| :-------------------------- | :-------------: | :------: | :---------------------------------------------------------------------- | +| `location` | `string` | No | Specifies the location for resources. | +| `resourceTags` | `object` | No | Tags retrieved from parameter file. | +| `name` | `string` | Yes | Name of container app. | +| `containerAppEnvironmentId` | `string` | Yes | The id of the container environment to deploy app to. | +| `containerImage` | `string` | No | Image of container. Defaults to mcr quickstart. | +| `containerName` | `string` | No | Name of container. | +| `targetPort` | `int` | Yes | The port exposed on the target container. | +| `exposedPort` | `int | null` | No | The port exposed on ingress. | +| `transport` | `string` | No | Which transport protocol to expose. | +| `serviceType` | `null | string` | No | For containers instrumented by Aspire a service type might be required. | +| `minReplicas` | `int` | No | Minimum number of replicas. | +| `maxReplicas` | `int` | No | Maximum number of replicas. | +| `envVars` | `array` | No | Environment variables. | + +## Outputs + +| Name | Type | Description | +| :----- | :------: | :------------------------- | +| `name` | `string` | Name of the container app. | + +## Examples + +### Example 1 + +```bicep +``` + +### Example 2 + +```bicep +``` \ No newline at end of file diff --git a/infra/modules/containers/container-app-docker-hub/main.bicep b/infra/modules/containers/container-app-docker-hub/main.bicep new file mode 100644 index 0000000..6c2ebe3 --- /dev/null +++ b/infra/modules/containers/container-app-docker-hub/main.bicep @@ -0,0 +1,88 @@ +metadata name = 'Container App Docker Hub' +metadata description = 'Container App Docker Hub' +metadata owner = 'ahelland' + +@description('Specifies the location for resources.') +param location string = resourceGroup().location +@description('Tags retrieved from parameter file.') +param resourceTags object = {} +@description('Name of container app.') +param name string +@description('The id of the container environment to deploy app to.') +param containerAppEnvironmentId string +@description('Image of container. Defaults to mcr quickstart.') +param containerImage string = 'mcr.microsoft.com/k8se/quickstart:latest' +@description('Name of container.') +param containerName string +@description('The port exposed on the target container.') +param targetPort int +@description('The port exposed on ingress.') +param exposedPort int? +@allowed([ + 'http' + 'tcp' +]) +@description('Which transport protocol to expose.') +param transport string = 'http' +@description('For containers instrumented by Aspire a service type might be required.') +param serviceType string? +@description('Minimum number of replicas.') +param minReplicas int = 0 +@description('Maximum number of replicas.') +param maxReplicas int = 10 +@description('Environment variables.') +param envVars array = [] + +resource containerApp 'Microsoft.App/containerApps@2023-05-02-preview' = { + name: name + location: location + tags: resourceTags + properties: { + managedEnvironmentId: containerAppEnvironmentId + environmentId: containerAppEnvironmentId + workloadProfileName: 'Consumption' + configuration: { + activeRevisionsMode: 'Single' + ingress: { + external: true + targetPort: targetPort + exposedPort: exposedPort + transport: transport + traffic: [ + { + weight: 100 + latestRevision: true + } + ] + allowInsecure: false + } + service: (!empty(serviceType)) ? { + type: serviceType + } : null + } + template: { + revisionSuffix: '' + containers: [ + { + image: containerImage + name: containerName + env: envVars + resources: { + cpu: json('0.25') + memory: '0.5Gi' + } + } + ] + scale: { + minReplicas: minReplicas + maxReplicas: maxReplicas + } + } + } + identity: { + type: 'None' + } +} + +@description('Name of the container app.') +output name string = containerApp.name diff --git a/infra/modules/containers/container-app-docker-hub/main.json b/infra/modules/containers/container-app-docker-hub/main.json new file mode 100644 index 0000000..d6f6fe4 --- /dev/null +++ b/infra/modules/containers/container-app-docker-hub/main.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "11323196703026034533" + }, + "name": "Container App Docker Hub", + "description": "Container App Docker Hub", + "owner": "ahelland" + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Specifies the location for resources." + } + }, + "resourceTags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags retrieved from parameter file." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of container app." + } + }, + "containerAppEnvironmentId": { + "type": "string", + "metadata": { + "description": "The id of the container environment to deploy app to." + } + }, + "containerImage": { + "type": "string", + "defaultValue": "mcr.microsoft.com/k8se/quickstart:latest", + "metadata": { + "description": "Image of container. Defaults to mcr quickstart." + } + }, + "containerName": { + "type": "string", + "defaultValue": "simple-hello-world-container", + "metadata": { + "description": "Name of container." + } + }, + "targetPort": { + "type": "int", + "metadata": { + "description": "The port exposed on the target container." + } + }, + "exposedPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "The port exposed on ingress." + } + }, + "transport": { + "type": "string", + "defaultValue": "http", + "allowedValues": [ + "http", + "tcp" + ], + "metadata": { + "description": "Which transport protocol to expose." + } + }, + "serviceType": { + "type": "string", + "nullable": true, + "metadata": { + "description": "For containers instrumented by Aspire a service type might be required." + } + }, + "minReplicas": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Minimum number of replicas." + } + }, + "maxReplicas": { + "type": "int", + "defaultValue": 10, + "metadata": { + "description": "Maximum number of replicas." + } + }, + "envVars": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Environment variables." + } + } + }, + "resources": { + "containerApp": { + "type": "Microsoft.App/containerApps", + "apiVersion": "2023-05-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('resourceTags')]", + "properties": { + "managedEnvironmentId": "[parameters('containerAppEnvironmentId')]", + "environmentId": "[parameters('containerAppEnvironmentId')]", + "workloadProfileName": "Consumption", + "configuration": { + "activeRevisionsMode": "Single", + "ingress": { + "external": true, + "targetPort": "[parameters('targetPort')]", + "exposedPort": "[parameters('exposedPort')]", + "transport": "[parameters('transport')]", + "traffic": [ + { + "weight": 100, + "latestRevision": true + } + ], + "allowInsecure": false + }, + "service": "[if(not(empty(parameters('serviceType'))), createObject('type', parameters('serviceType')), null())]" + }, + "template": { + "revisionSuffix": "", + "containers": [ + { + "image": "[parameters('containerImage')]", + "name": "[parameters('containerName')]", + "env": "[parameters('envVars')]", + "resources": { + "cpu": "[json('0.25')]", + "memory": "0.5Gi" + } + } + ], + "scale": { + "minReplicas": "[parameters('minReplicas')]", + "maxReplicas": "[parameters('maxReplicas')]" + } + } + }, + "identity": { + "type": "None" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the container app." + }, + "value": "[parameters('name')]" + } + } +} \ No newline at end of file diff --git a/infra/modules/containers/container-app-docker-hub/test/main.test.bicep b/infra/modules/containers/container-app-docker-hub/test/main.test.bicep new file mode 100644 index 0000000..54ea828 --- /dev/null +++ b/infra/modules/containers/container-app-docker-hub/test/main.test.bicep @@ -0,0 +1,28 @@ +targetScope = 'subscription' + +@description('Location for Container App.') +param location string +@description('Tags retrieved from parameter file.') +param resourceTags object = {} + +resource rg_cae 'Microsoft.Resources/resourceGroups@2021-04-01' existing = { + name: 'rg-cae' +} + +resource containerenvironment 'Microsoft.App/managedEnvironments@2023-05-02-preview' existing = { + scope: rg_cae + name: 'cae-01' +} + +module helloApp '../main.bicep' = { + scope: rg_cae + name: 'container-app-hello' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + name: 'hello' + targetPort: 8080 + containerName: 'hello-world' + } +} diff --git a/infra/modules/containers/container-app-docker-hub/version.json b/infra/modules/containers/container-app-docker-hub/version.json new file mode 100644 index 0000000..a830c3d --- /dev/null +++ b/infra/modules/containers/container-app-docker-hub/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.10", + "pathFilters": [ + "./main.json" + ] +} \ No newline at end of file diff --git a/infra/modules/containers/container-app-service/README.md b/infra/modules/containers/container-app-service/README.md new file mode 100644 index 0000000..7954e7a --- /dev/null +++ b/infra/modules/containers/container-app-service/README.md @@ -0,0 +1,35 @@ +# Container App Service + +Container App Service + +## Details + +{{Add detailed information about the module}} + +## Parameters + +| Name | Type | Required | Description | +| :-------------------------- | :------: | :------: | :---------------------------------------- | +| `location` | `string` | No | Specifies the location for resources. | +| `resourceTags` | `object` | No | Tags retrieved from parameter file. | +| `serviceName` | `string` | Yes | Name of the service- | +| `serviceType` | `string` | Yes | Type of service. | +| `containerAppEnvironmentId` | `string` | Yes | Id of container environment to deploy to. | + +## Outputs + +| Name | Type | Description | +| :--- | :------: | :--------------------- | +| `id` | `string` | The id of the service. | + +## Examples + +### Example 1 + +```bicep +``` + +### Example 2 + +```bicep +``` \ No newline at end of file diff --git a/infra/modules/containers/container-app-service/main.bicep b/infra/modules/containers/container-app-service/main.bicep new file mode 100644 index 0000000..4ef93b3 --- /dev/null +++ b/infra/modules/containers/container-app-service/main.bicep @@ -0,0 +1,35 @@ +metadata name = 'Container App Service' +metadata description = 'Container App Service' +metadata owner = 'ahelland' + +@description('Specifies the location for resources.') +param location string = resourceGroup().location +@description('Tags retrieved from parameter file.') +param resourceTags object = {} +@description('Name of the service-') +param serviceName string +@description('Type of service.') +@allowed([ + 'redis' + 'postgres' +]) +param serviceType string +@description('Id of container environment to deploy to.') +param containerAppEnvironmentId string + +resource containerService 'Microsoft.App/containerApps@2023-04-01-preview' = { + name: serviceName + location: location + tags: resourceTags + properties: { + environmentId: containerAppEnvironmentId + configuration: { + service: { + type: serviceType + } + } + } + } + +@description('The id of the service.') +output id string = containerService.id diff --git a/infra/modules/containers/container-app-service/main.json b/infra/modules/containers/container-app-service/main.json new file mode 100644 index 0000000..158c44f --- /dev/null +++ b/infra/modules/containers/container-app-service/main.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.24.24.22086", + "templateHash": "3084504074608692895" + }, + "name": "Container App Service", + "description": "Container App Service", + "owner": "ahelland" + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Specifies the location for resources." + } + }, + "resourceTags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags retrieved from parameter file." + } + }, + "serviceName": { + "type": "string", + "metadata": { + "description": "Name of the service-" + } + }, + "serviceType": { + "type": "string", + "allowedValues": [ + "redis", + "postgres" + ], + "metadata": { + "description": "Type of service." + } + }, + "containerAppEnvironmentId": { + "type": "string", + "metadata": { + "description": "Id of container environment to deploy to." + } + } + }, + "resources": [ + { + "type": "Microsoft.App/containerApps", + "apiVersion": "2023-04-01-preview", + "name": "[parameters('serviceName')]", + "location": "[parameters('location')]", + "tags": "[parameters('resourceTags')]", + "properties": { + "environmentId": "[parameters('containerAppEnvironmentId')]", + "configuration": { + "service": { + "type": "[parameters('serviceType')]" + } + } + } + } + ], + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "The id of the service." + }, + "value": "[resourceId('Microsoft.App/containerApps', parameters('serviceName'))]" + } + } +} \ No newline at end of file diff --git a/infra/modules/containers/container-app-service/test/main.test.bicep b/infra/modules/containers/container-app-service/test/main.test.bicep new file mode 100644 index 0000000..8aa47d0 --- /dev/null +++ b/infra/modules/containers/container-app-service/test/main.test.bicep @@ -0,0 +1,27 @@ +targetScope = 'subscription' + +@description('Location for Container Service.') +param location string +@description('Tags retrieved from parameter file.') +param resourceTags object = {} + +resource rg_cae 'Microsoft.Resources/resourceGroups@2021-04-01' existing = { + name: 'rg-cae' +} + +resource containerenvironment 'Microsoft.App/managedEnvironments@2023-05-02-preview' existing = { + scope: rg_cae + name: 'cae-01' +} + +module postgres '../main.bicep' = { + scope: rg_cae + name: 'container-service-postgres' + params: { + location: location + resourceTags: resourceTags + containerAppEnvironmentId: containerenvironment.id + serviceName: 'postgres' + serviceType: 'postgres' + } +} diff --git a/infra/modules/containers/container-app-service/version.json b/infra/modules/containers/container-app-service/version.json new file mode 100644 index 0000000..a830c3d --- /dev/null +++ b/infra/modules/containers/container-app-service/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.10", + "pathFilters": [ + "./main.json" + ] +} \ No newline at end of file diff --git a/infra/playbook.dib b/infra/playbook.dib index 97e1b6e..7bba90b 100644 --- a/infra/playbook.dib +++ b/infra/playbook.dib @@ -1,6 +1,6 @@ #!meta -{"kernelInfo":{"defaultKernelName":"pwsh","items":[{"aliases":[],"languageName":"pwsh","name":"pwsh"}]}} +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"},{"aliases":[],"languageName":"pwsh","name":"pwsh"}]}} #!markdown @@ -44,3 +44,18 @@ az stack sub create --name eshop-devCenter --location westeurope --template-file # A deployment stack az stack sub create --name eshop-level-3 --location westeurope --template-file .\level-3\main.bicep --parameters .\level-3\main.bicepparam --deny-settings-mode none + +#!markdown + +### Level-4 + +#!pwsh + +# A what-if to point out errors not caught by the linter +# az deployment sub what-if --location westeurope --name level-4 --template-file .\level-4\main.bicep --parameters .\level-4\main.bicepparam + +# A stand-alone deployment +# az deployment sub create --location westeurope --name eshop-level-4 --template-file .\level-4\main.bicep --parameters .\level-4\main.bicepparam + +# A deployment stack +az stack sub create --name eshop-level-4 --location westeurope --template-file .\level-4\main.bicep --parameters .\level-4\main.bicepparam --deny-settings-mode none