├── .github
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── bicep-audit.yml
├── .vscode
└── launch.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── bicep
├── infra
│ ├── container-apps-environment.bicep
│ ├── container-registry.bicep
│ ├── deploy.sh
│ ├── log-analytics-workspace.bicep
│ ├── main.bicep
│ ├── main.json
│ ├── main.parameters.json
│ ├── managed-identity.bicep
│ ├── service-bus.bicep
│ └── virtual-network.bicep
└── jobs
│ ├── container-apps-job.bicep
│ ├── container-registry.bicep
│ ├── deploy.sh
│ ├── main.bicep
│ └── main.parameters.json
├── images
├── architecture.png
├── executionhistory.png
├── executionlogs.png
├── joblogs.png
├── messageflow.png
└── resources.png
├── scripts
├── 00-variables.sh
├── 01-create-job.sh
├── 02-start-job.sh
└── 03-view-managed-identity.sh
├── src
├── .env
├── .local
├── 00-variables.sh
├── 01-build-docker-images.sh
├── 02-run-docker-container.sh
├── 03-push-docker-image.sh
├── Dockerfile
├── requirements.txt
├── sbprocessor.py
├── sbreceiver.py
└── sbsender.py
└── visio
└── aca-jobs.vsdx
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
4 | > Please provide us with the following information:
5 | > ---------------------------------------------------------------
6 |
7 | ### This issue is for a: (mark with an `x`)
8 | ```
9 | - [ ] bug report -> please search issues before submitting
10 | - [ ] feature request
11 | - [ ] documentation issue or request
12 | - [ ] regression (a behavior that used to work and stopped in a new release)
13 | ```
14 |
15 | ### Minimal steps to reproduce
16 | >
17 |
18 | ### Any log messages given by the failure
19 | >
20 |
21 | ### Expected/desired behavior
22 | >
23 |
24 | ### OS and Version?
25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?)
26 |
27 | ### Versions
28 | >
29 |
30 | ### Mention any other details that might be useful
31 |
32 | > ---------------------------------------------------------------
33 | > Thanks! We'll be in touch soon.
34 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Purpose
2 |
3 | * ...
4 |
5 | ## Does this introduce a breaking change?
6 |
7 | ```
8 | [ ] Yes
9 | [ ] No
10 | ```
11 |
12 | ## Pull Request Type
13 | What kind of change does this Pull Request introduce?
14 |
15 |
16 | ```
17 | [ ] Bugfix
18 | [ ] Feature
19 | [ ] Code style update (formatting, local variables)
20 | [ ] Refactoring (no functional changes, no api changes)
21 | [ ] Documentation content changes
22 | [ ] Other... Please describe:
23 | ```
24 |
25 | ## How to Test
26 | * Get the code
27 |
28 | ```
29 | git clone [repo-address]
30 | cd [repo-name]
31 | git checkout [branch-name]
32 | npm install
33 | ```
34 |
35 | * Test the code
36 |
37 | ```
38 | ```
39 |
40 | ## What to Check
41 | Verify that the following are valid
42 | * ...
43 |
44 | ## Other Information
45 |
--------------------------------------------------------------------------------
/.github/workflows/bicep-audit.yml:
--------------------------------------------------------------------------------
1 | name: Validate bicep templates
2 | on:
3 | push:
4 | branches:
5 | - main
6 | paths:
7 | - "**/*.bicep"
8 | pull_request:
9 | branches:
10 | - main
11 | paths:
12 | - "**/*.bicep"
13 | workflow_dispatch:
14 |
15 | jobs:
16 | build:
17 | runs-on: ubuntu-latest
18 | permissions:
19 | security-events: write
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@v4
23 |
24 | - name: Run Microsoft Security DevOps Analysis
25 | uses: microsoft/security-devops-action@preview
26 | id: msdo
27 | continue-on-error: true
28 | with:
29 | tools: templateanalyzer
30 |
31 | - name: Upload alerts to Security tab
32 | uses: github/codeql-action/upload-sarif@v3
33 | if: github.repository_owner == 'Azure-Samples'
34 | with:
35 | sarif_file: ${{ steps.msdo.outputs.sarifFile }}
36 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Python: Current File",
9 | "type": "python",
10 | "request": "launch",
11 | "program": "${file}",
12 | "console": "integratedTerminal",
13 | "justMyCode": false
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [project-title] Changelog
2 |
3 |
4 | # x.y.z (yyyy-mm-dd)
5 |
6 | *Features*
7 | * ...
8 |
9 | *Bug Fixes*
10 | * ...
11 |
12 | *Breaking Changes*
13 | * ...
14 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to [project-title]
2 |
3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
6 |
7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide
8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
9 | provided by the bot. You will only need to do this once across all repos using our CLA.
10 |
11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
14 |
15 | - [Code of Conduct](#coc)
16 | - [Issues and Bugs](#issue)
17 | - [Feature Requests](#feature)
18 | - [Submission Guidelines](#submit)
19 |
20 | ## Code of Conduct
21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
22 |
23 | ## Found an Issue?
24 | If you find a bug in the source code or a mistake in the documentation, you can help us by
25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can
26 | [submit a Pull Request](#submit-pr) with a fix.
27 |
28 | ## Want a Feature?
29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub
30 | Repository. If you would like to *implement* a new feature, please submit an issue with
31 | a proposal for your work first, to be sure that we can use it.
32 |
33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
34 |
35 | ## Submission Guidelines
36 |
37 | ### Submitting an Issue
38 | Before you submit an issue, search the archive, maybe your question was already answered.
39 |
40 | If your issue appears to be a bug, and hasn't been reported, open a new issue.
41 | Help us to maximize the effort we can spend fixing issues and adding new
42 | features, by not reporting duplicate issues. Providing the following information will increase the
43 | chances of your issue being dealt with quickly:
44 |
45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
46 | * **Version** - what version is affected (e.g. 0.1.2)
47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you
48 | * **Browsers and Operating System** - is this a problem with all browsers?
49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps
50 | * **Related Issues** - has a similar issue been reported before?
51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
52 | causing the problem (line of code or commit)
53 |
54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new].
55 |
56 | ### Submitting a Pull Request (PR)
57 | Before you submit your Pull Request (PR) consider the following guidelines:
58 |
59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR
60 | that relates to your submission. You don't want to duplicate effort.
61 |
62 | * Make your changes in a new git fork:
63 |
64 | * Commit your changes using a descriptive commit message
65 | * Push your fork to GitHub:
66 | * In GitHub, create a pull request
67 | * If we suggest changes then:
68 | * Make the required updates.
69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request):
70 |
71 | ```shell
72 | git rebase master -i
73 | git push -f
74 | ```
75 |
76 | That's it! Thank you for your contribution!
77 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
--------------------------------------------------------------------------------
/bicep/infra/container-apps-environment.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Specifies the name of the Azure Container Apps Environment.')
3 | param name string
4 |
5 | @description('Specifies the location.')
6 | param location string = resourceGroup().location
7 |
8 | @description('Specifies the resource tags.')
9 | param tags object
10 |
11 | @description('Specifies whether the environment only has an internal load balancer. These environments do not have a public static IP resource. They must provide infrastructureSubnetId if enabling this property')
12 | param internal bool = false
13 |
14 | @description('Specifies the IP range in CIDR notation assigned to the Docker bridge, network. Must not overlap with any other provided IP ranges.')
15 | param dockerBridgeCidr string
16 |
17 | @description('Specifies the IP range in CIDR notation that can be reserved for environment infrastructure IP addresses. Must not overlap with any other provided IP ranges.')
18 | param platformReservedCidr string
19 |
20 | @description('Specifies an IP address from the IP range defined by platformReservedCidr that will be reserved for the internal DNS server.')
21 | param platformReservedDnsIP string
22 |
23 | @description('Specifies whether the Azure Container Apps environment should be zone-redundant.')
24 | param zoneRedundant bool = true
25 |
26 | @description('Specifies the resource id of the infrastructure subnet.')
27 | param infrastructureSubnetId string
28 |
29 | @description('Specifies the name of the Log Analytics workspace.')
30 | param workspaceName string
31 |
32 | @description('Specifies the Azure Monitor instrumentation key used by Dapr to export Service to Service communication telemetry.')
33 | param daprAIInstrumentationKey string = ''
34 |
35 | @description('Specifies the configuration of Dapr component.')
36 | param daprAIConnectionString string = ''
37 |
38 | @description('Specifies the certificate password.')
39 | @secure()
40 | param certificatePassword string = ''
41 |
42 | @description('Specifies the PFX or PEM certificate value.')
43 | param certificateValue string = ''
44 |
45 | @description('Specifies the DNS suffix for the environment domain.')
46 | param dnsSuffix string = ''
47 |
48 | @description('Specifies workload profiles configured for the Managed Environment.')
49 | param workloadProfiles array = []
50 |
51 | // Resources
52 | resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' existing = {
53 | name: workspaceName
54 | }
55 |
56 | resource environment 'Microsoft.App/managedEnvironments@2023-04-01-preview' = {
57 | name: name
58 | location: location
59 | tags: tags
60 | properties: {
61 | customDomainConfiguration: empty(certificatePassword) && empty(certificateValue) && empty(dnsSuffix)? null : {
62 | certificatePassword: certificatePassword
63 | certificateValue: certificateValue
64 | dnsSuffix: dnsSuffix
65 | }
66 | daprAIInstrumentationKey: daprAIInstrumentationKey
67 | daprAIConnectionString: daprAIConnectionString
68 | vnetConfiguration: {
69 | internal: internal
70 | infrastructureSubnetId: infrastructureSubnetId
71 | dockerBridgeCidr: dockerBridgeCidr
72 | platformReservedCidr: platformReservedCidr
73 | platformReservedDnsIP: platformReservedDnsIP
74 | }
75 | appLogsConfiguration: {
76 | destination: 'log-analytics'
77 | logAnalyticsConfiguration: {
78 | customerId: logAnalyticsWorkspace.properties.customerId
79 | sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey
80 | }
81 | }
82 | zoneRedundant: zoneRedundant
83 | workloadProfiles: workloadProfiles
84 | }
85 | }
86 |
87 | // Outputs
88 | output id string = environment.id
89 | output name string = environment.name
90 | output daprConfiguration object = environment.properties.daprConfiguration
91 | output kedaConfiguration object = environment.properties.kedaConfiguration
92 | output appLogsConfiguration object = environment.properties.appLogsConfiguration
93 |
--------------------------------------------------------------------------------
/bicep/infra/container-registry.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Name of your Azure Container Registry')
3 | @minLength(5)
4 | @maxLength(50)
5 | param name string = 'acr${uniqueString(resourceGroup().id)}'
6 |
7 | @description('Enable admin user that have push / pull permission to the registry.')
8 | param adminUserEnabled bool = false
9 |
10 | @description('Tier of your Azure Container Registry.')
11 | @allowed([
12 | 'Basic'
13 | 'Standard'
14 | 'Premium'
15 | ])
16 | param sku string = 'Premium'
17 |
18 | @description('Specifies the resource id of the Log Analytics workspace.')
19 | param workspaceId string
20 |
21 | @description('Specifies the workspace data retention in days.')
22 | param retentionInDays int = 60
23 |
24 | @description('Specifies the location.')
25 | param location string = resourceGroup().location
26 |
27 | @description('Specifies the resource tags.')
28 | param tags object
29 |
30 | // Variables
31 | var diagnosticSettingsName = 'diagnosticSettings'
32 | var logCategories = [
33 | 'ContainerRegistryRepositoryEvents'
34 | 'ContainerRegistryLoginEvents'
35 | ]
36 | var metricCategories = [
37 | 'AllMetrics'
38 | ]
39 | var logs = [for category in logCategories: {
40 | category: category
41 | enabled: true
42 | retentionPolicy: {
43 | enabled: true
44 | days: retentionInDays
45 | }
46 | }]
47 | var metrics = [for category in metricCategories: {
48 | category: category
49 | enabled: true
50 | retentionPolicy: {
51 | enabled: true
52 | days: retentionInDays
53 | }
54 | }]
55 |
56 | // Resources
57 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2021-12-01-preview' = {
58 | name: name
59 | location: location
60 | tags: tags
61 | sku: {
62 | name: sku
63 | }
64 | properties: {
65 | adminUserEnabled: adminUserEnabled
66 | }
67 | }
68 |
69 | resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
70 | name: diagnosticSettingsName
71 | scope: containerRegistry
72 | properties: {
73 | workspaceId: workspaceId
74 | logs: logs
75 | metrics: metrics
76 | }
77 | }
78 |
79 | // Outputs
80 | output id string = containerRegistry.id
81 | output name string = containerRegistry.name
82 |
--------------------------------------------------------------------------------
/bicep/infra/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Variables
4 | location='northeurope'
5 | deploymentName='main'
6 | prefix='Gundam'
7 | resourceGroupName="${prefix}RG"
8 |
9 | # Commands
10 | validateTemplate=1
11 | useWhatIf=1
12 | deploy=1
13 |
14 | # Template
15 | template="main.bicep"
16 | parameters="main.parameters.json"
17 |
18 | # Subscription id, subscription name, and tenant id of the current subscription
19 | subscriptionId=$(az account show --query id --output tsv)
20 | subscriptionName=$(az account show --query name --output tsv)
21 | tenantId=$(az account show --query tenantId --output tsv)
22 |
23 | # Check if the resource group already exists
24 | echo "Checking if [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription..."
25 |
26 | az group show --name $resourceGroupName &>/dev/null
27 |
28 | if [[ $? != 0 ]]; then
29 | echo "No [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription"
30 | echo "Creating [$resourceGroupName] resource group in the [$subscriptionName] subscription..."
31 |
32 | # Create the resource group
33 | az group create --name $resourceGroupName --location $location 1>/dev/null
34 |
35 | if [[ $? == 0 ]]; then
36 | echo "[$resourceGroupName] resource group successfully created in the [$subscriptionName] subscription"
37 | else
38 | echo "Failed to create [$resourceGroupName] resource group in the [$subscriptionName] subscription"
39 | exit
40 | fi
41 | else
42 | echo "[$resourceGroupName] resource group already exists in the [$subscriptionName] subscription"
43 | fi
44 |
45 | # Validate the Bicep template
46 | if [[ $validateTemplate == 1 ]]; then
47 | if [[ $useWhatIf == 1 ]]; then
48 | # Execute a deployment What-If operation at resource group scope.
49 | echo "Previewing changes deployed by [$template] Bicep template..."
50 | az deployment group what-if \
51 | --resource-group $resourceGroupName \
52 | --template-file $template \
53 | --no-pretty-print \
54 | --output jsonc \
55 | --parameters $parameters \
56 | --parameters location=$location \
57 | prefix=$prefix
58 |
59 | if [[ $? == 0 ]]; then
60 | echo "[$template] Bicep template validation succeeded"
61 | else
62 | echo "Failed to validate [$template] Bicep template"
63 | exit
64 | fi
65 | else
66 | # Validate the Bicep template
67 | echo "Validating [$template] Bicep template..."
68 | output=$(az deployment group validate \
69 | --resource-group $resourceGroupName \
70 | --template-file $template \
71 | --no-pretty-print \
72 | --output jsonc \
73 | --parameters $parameters \
74 | --parameters location=$location \
75 | prefix=$prefix)
76 |
77 | if [[ $? == 0 ]]; then
78 | echo "[$template] Bicep template validation succeeded"
79 | else
80 | echo "Failed to validate [$template] Bicep template"
81 | echo $output
82 | exit
83 | fi
84 | fi
85 | fi
86 |
87 | # Deploy the Bicep template
88 | if [[ $deploy == 1 ]]; then
89 | az deployment group create \
90 | --name $deploymentName \
91 | --resource-group $resourceGroupName \
92 | --template-file $template \
93 | --parameters $parameters \
94 | --parameters location=$location \
95 | prefix=$prefix 1>/dev/null
96 |
97 | if [[ $? == 0 ]]; then
98 | echo "[$deploymentName] deployment successfully created in the [$resourceGroupName] resource group"
99 | else
100 | echo "Failed to create [$deploymentName] deployment in the [$resourceGroupName] resource group"
101 | exit -1
102 | fi
103 | fi
--------------------------------------------------------------------------------
/bicep/infra/log-analytics-workspace.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Specifies the name of the Log Analytics workspace.')
3 | param name string
4 |
5 | @description('Specifies the service tier of the workspace: Free, Standalone, PerNode, Per-GB.')
6 | @allowed([
7 | 'Free'
8 | 'Standalone'
9 | 'PerNode'
10 | 'PerGB2018'
11 | ])
12 | param sku string = 'PerNode'
13 |
14 | @description('Specifies the workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku. 730 days is the maximum allowed for all other Skus.')
15 | param retentionInDays int = 60
16 |
17 | @description('Specifies the location.')
18 | param location string = resourceGroup().location
19 |
20 | @description('Specifies the resource tags.')
21 | param tags object
22 |
23 | // Resources
24 | resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = {
25 | name: name
26 | tags: tags
27 | location: location
28 | properties: {
29 | sku: {
30 | name: sku
31 | }
32 | retentionInDays: retentionInDays
33 | }
34 | }
35 |
36 | //Outputs
37 | output id string = logAnalyticsWorkspace.id
38 | output name string = logAnalyticsWorkspace.name
39 | output customerId string = logAnalyticsWorkspace.properties.customerId
40 |
--------------------------------------------------------------------------------
/bicep/infra/main.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Specifies the name prefix.')
3 | param prefix string = '$uniqueString(resourceGroup().id)'
4 |
5 | @description('Specifies whether name resources are in CamelCase, UpperCamelCase, or KebabCase.')
6 | @allowed([
7 | 'CamelCase'
8 | 'UpperCamelCase'
9 | 'KebabCase'
10 | ])
11 | param letterCaseType string = 'UpperCamelCase'
12 |
13 | @description('Specifies the location.')
14 | param location string = resourceGroup().location
15 |
16 | @description('Specifies the resource tags.')
17 | param tags object = {
18 | IaC: 'Bicep'
19 | Demo: 'Azure Container Apps Jobs'
20 | }
21 |
22 | @description('Specifies the name of the Azure Container Apps Environment.')
23 | param containerAppEnvironmentName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}Environment' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}Environment' : '${toLower(prefix)}-environment'
24 |
25 | @description('Specifies whether the environment only has an internal load balancer. These environments do not have a public static IP resource. They must provide infrastructureSubnetId if enabling this property')
26 | param internal bool = false
27 |
28 | @description('Specifies the IP range in CIDR notation assigned to the Docker bridge, network. Must not overlap with any other provided IP ranges.')
29 | param dockerBridgeCidr string = '10.2.0.1/16'
30 |
31 | @description('Specifies the IP range in CIDR notation that can be reserved for environment infrastructure IP addresses. Must not overlap with any other provided IP ranges.')
32 | param platformReservedCidr string = '10.1.0.0/16'
33 |
34 | @description('Specifies an IP address from the IP range defined by platformReservedCidr that will be reserved for the internal DNS server.')
35 | param platformReservedDnsIP string = '10.1.0.2'
36 |
37 | @description('Specifies whether the Azure Container Apps environment should be zone-redundant.')
38 | param zoneRedundant bool = true
39 |
40 | @description('Specifies the name of the Log Analytics workspace.')
41 | param logAnalyticsWorkspaceName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}Workspace' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}Workspace' : '${toLower(prefix)}-workspace'
42 |
43 | @description('Specifies the workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku. 730 days is the maximum allowed for all other Skus.')
44 | param logAnalyticsRetentionInDays int = 30
45 |
46 | @description('Specifies the service tier of the workspace: Free, Standalone, PerNode, Per-GB.')
47 | @allowed([
48 | 'Free'
49 | 'Standalone'
50 | 'PerNode'
51 | 'PerGB2018'
52 | ])
53 | param logAnalyticsSku string = 'PerNode'
54 |
55 | @description('Specifies the name of the virtual network.')
56 | param virtualNetworkName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}Vnet' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}Vnet' : '${toLower(prefix)}-vnet'
57 |
58 | @description('Specifies the address prefixes of the virtual network.')
59 | param virtualNetworkAddressPrefixes string = '10.0.0.0/8'
60 |
61 | @description('Specifies the name of a subnet for the Azure Container Apps environment infrastructure components. This subnet must be in the same VNET as the subnet defined in runtimeSubnetId. Must not overlap with any other provided IP ranges.')
62 | param infrastructureSubnetName string = 'InfrastructureSubnet'
63 |
64 | @description('Specifies the address prefix of a subnet for the Azure Container Apps environment infrastructure components. This subnet must be in the same VNET as the subnet defined in runtimeSubnetId. Must not overlap with any other provided IP ranges.')
65 | param infrastructureSubnetAddressPrefix string = '10.0.0.0/21'
66 |
67 | @description('Specifies the name of the subnet hosting the worker nodes of the user agent pool of the AKS cluster.')
68 | param privateLinkServiceSubnetName string = 'PrivateSubnet'
69 |
70 | @description('Specifies the address prefix of the subnet hosting the worker nodes of the user agent pool of the AKS cluster.')
71 | param privateLinkServiceSubnetAddressPrefix string = '10.0.16.0/21'
72 |
73 | @description('Specifies the name of the Service Bus namespace.')
74 | param serviceBusNamespace string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}ServiceBus' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}ServiceBus' : '${toLower(prefix)}-servicebus'
75 |
76 | @description('Enabling this property creates a Premium Service Bus Namespace in regions supported availability zones.')
77 | param serviceBusZoneRedundant bool = true
78 |
79 | @description('Specifies the name of Service Bus namespace SKU.')
80 | @allowed([
81 | 'Basic'
82 | 'Premium'
83 | 'Standard'
84 | ])
85 | param serviceBusSkuName string = 'Premium'
86 |
87 | @description('Specifies the messaging units for the Service Bus namespace. For Premium tier, capacity are 1,2 and 4.')
88 | param serviceBusCapacity int = 1
89 |
90 | @description('Specifies the name of the Service Bus queue.')
91 | param serviceBusQueueNames array = [ 'parameters', 'results' ]
92 |
93 | @description('Specifies the lock duration of the queue.')
94 | param serviceBusQueueLockDuration string = 'PT5M'
95 |
96 | @description('Specifies the maximum delivery count of the queue.')
97 | param serviceBusQueueMaxDeliveryCount int = 10
98 |
99 | @description('Specifies whether duplication is enabled on the queue.')
100 | param serviceBusQueueRequiresDuplicateDetection bool = false
101 |
102 | @description('Specifies whether session is enabled on the queue.')
103 | param serviceBusQueueRequiresSession bool = false
104 |
105 | @description('Specifies whether dead lettering is enabled on the queue.')
106 | param serviceBusQueueDeadLetteringOnMessageExpiration bool = false
107 |
108 | @description('Specifies the duplicate detection history time of the queue.')
109 | param serviceBusQueueDuplicateDetectionHistoryTimeWindow string = 'PT10M'
110 |
111 | @description('Specifies the name of the private link to the storage account.')
112 | param serviceBusNamespacePrivateEndpointName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}ServiceBusNamespacePrivateEndpoint' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}ServiceBusNamespacePrivateEndpoint' : '${toLower(prefix)}-service-bus-namespace-private-endpoint'
113 |
114 | @description('Specifies the name of the user-defined managed identity.')
115 | param managedIdentityName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}JobManagedIdentity' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}JobManagedIdentity' : '${toLower(prefix)}-job-managed-identity'
116 |
117 | @description('Specifies the name of the Azure Container Registry')
118 | @minLength(5)
119 | @maxLength(50)
120 | param acrName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}Acr' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}Acr' : '${toLower(prefix)}-acr'
121 |
122 | @description('Enable admin user that have push / pull permission to the registry.')
123 | param acrAdminUserEnabled bool = false
124 |
125 | @description('Specifies the name of the private link to the Azure Container Registry.')
126 | param acrPrivateEndpointName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}AcrPrivateEndpoint' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}AcrPrivateEndpoint' : '${toLower(prefix)}-acr-private-endpoint'
127 |
128 | @description('Specifies the SKU name of the Azure Container Registry.')
129 | @allowed([
130 | 'Basic'
131 | 'Standard'
132 | 'Premium'
133 | ])
134 | param acrSkuName string = 'Premium'
135 |
136 | // Resources
137 | module workspace 'log-analytics-workspace.bicep' = {
138 | name: 'workspace'
139 | scope: resourceGroup()
140 | params: {
141 | name: logAnalyticsWorkspaceName
142 | sku: logAnalyticsSku
143 | retentionInDays: logAnalyticsRetentionInDays
144 | location: location
145 | tags: tags
146 | }
147 | }
148 |
149 | module namespace 'service-bus.bicep' = {
150 | name: 'namespace'
151 | scope: resourceGroup()
152 | params: {
153 | name: serviceBusNamespace
154 | location: location
155 | tags: tags
156 | zoneRedundant: serviceBusZoneRedundant
157 | skuName: serviceBusSkuName
158 | capacity: serviceBusCapacity
159 | queueNames: serviceBusQueueNames
160 | lockDuration: serviceBusQueueLockDuration
161 | maxDeliveryCount: serviceBusQueueMaxDeliveryCount
162 | requiresDuplicateDetection: serviceBusQueueRequiresDuplicateDetection
163 | requiresSession: serviceBusQueueRequiresSession
164 | deadLetteringOnMessageExpiration: serviceBusQueueDeadLetteringOnMessageExpiration
165 | duplicateDetectionHistoryTimeWindow: serviceBusQueueDuplicateDetectionHistoryTimeWindow
166 | workspaceId: workspace.outputs.id
167 | }
168 | }
169 |
170 | module network 'virtual-network.bicep' = {
171 | name: 'network'
172 | scope: resourceGroup()
173 | params: {
174 | name: virtualNetworkName
175 | location: location
176 | tags: tags
177 | addressPrefixes: virtualNetworkAddressPrefixes
178 | infrastructureSubnetName: infrastructureSubnetName
179 | infrastructureSubnetAddressPrefix: infrastructureSubnetAddressPrefix
180 | privateLinkServiceSubnetName: privateLinkServiceSubnetName
181 | privateLinkServiceSubnetAddressPrefix: privateLinkServiceSubnetAddressPrefix
182 | workspaceId: workspace.outputs.id
183 | serviceBusNamespacePrivateEndpointEnabled: serviceBusSkuName == 'Premium'
184 | serviceBusNamespacePrivateEndpointName: serviceBusNamespacePrivateEndpointName
185 | serviceBusNamespaceId: namespace.outputs.id
186 | acrPrivateEndpointEnabled: acrSkuName == 'Premium'
187 | acrPrivateEndpointName: acrPrivateEndpointName
188 | acrId: acr.outputs.id
189 | }
190 | }
191 |
192 | module environment 'container-apps-environment.bicep' = {
193 | name: 'environment'
194 | scope: resourceGroup()
195 | params: {
196 | name: containerAppEnvironmentName
197 | location: location
198 | tags: tags
199 | internal: internal
200 | dockerBridgeCidr: dockerBridgeCidr
201 | platformReservedCidr: platformReservedCidr
202 | platformReservedDnsIP: platformReservedDnsIP
203 | zoneRedundant: zoneRedundant
204 | workspaceName: logAnalyticsWorkspaceName
205 | infrastructureSubnetId: network.outputs.infrastructureSubnetId
206 | }
207 | }
208 |
209 | module managedIdentity 'managed-identity.bicep' = {
210 | name: 'managedIdentity'
211 | scope: resourceGroup()
212 | params: {
213 | name: managedIdentityName
214 | location: location
215 | serviceBusNamespace: namespace.outputs.name
216 | acrName: acr.outputs.name
217 | tags: tags
218 | }
219 | }
220 |
221 | module acr 'container-registry.bicep' = {
222 | name: 'containerRegistry'
223 | params: {
224 | name: acrName
225 | sku: acrSkuName
226 | adminUserEnabled: acrAdminUserEnabled
227 | workspaceId: workspace.outputs.id
228 | retentionInDays: logAnalyticsRetentionInDays
229 | location: location
230 | tags: tags
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/bicep/infra/main.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "metadata": {
5 | "_generator": {
6 | "name": "bicep",
7 | "version": "0.18.4.5664",
8 | "templateHash": "15046971136878174535"
9 | }
10 | },
11 | "parameters": {
12 | "prefix": {
13 | "type": "string",
14 | "defaultValue": "$uniqueString(resourceGroup().id)",
15 | "metadata": {
16 | "description": "Specifies the name prefix."
17 | }
18 | },
19 | "letterCaseType": {
20 | "type": "string",
21 | "defaultValue": "UpperCamelCase",
22 | "allowedValues": [
23 | "CamelCase",
24 | "UpperCamelCase",
25 | "KebabCase"
26 | ],
27 | "metadata": {
28 | "description": "Specifies whether name resources are in CamelCase, UpperCamelCase, or KebabCase."
29 | }
30 | },
31 | "location": {
32 | "type": "string",
33 | "defaultValue": "[resourceGroup().location]",
34 | "metadata": {
35 | "description": "Specifies the location."
36 | }
37 | },
38 | "tags": {
39 | "type": "object",
40 | "defaultValue": {
41 | "IaC": "Bicep",
42 | "Demo": "Azure Container Apps Jobs"
43 | },
44 | "metadata": {
45 | "description": "Specifies the resource tags."
46 | }
47 | },
48 | "containerAppEnvironmentName": {
49 | "type": "string",
50 | "defaultValue": "[if(equals(parameters('letterCaseType'), 'UpperCamelCase'), format('{0}{1}Environment', toUpper(first(parameters('prefix'))), toLower(substring(parameters('prefix'), 1, sub(length(parameters('prefix')), 1)))), if(equals(parameters('letterCaseType'), 'CamelCase'), format('{0}Environment', toLower(parameters('prefix'))), format('{0}-environment', toLower(parameters('prefix')))))]",
51 | "metadata": {
52 | "description": "Specifies the name of the Azure Container Apps Environment."
53 | }
54 | },
55 | "internal": {
56 | "type": "bool",
57 | "defaultValue": false,
58 | "metadata": {
59 | "description": "Specifies whether the environment only has an internal load balancer. These environments do not have a public static IP resource. They must provide infrastructureSubnetId if enabling this property"
60 | }
61 | },
62 | "dockerBridgeCidr": {
63 | "type": "string",
64 | "defaultValue": "10.2.0.1/16",
65 | "metadata": {
66 | "description": "Specifies the IP range in CIDR notation assigned to the Docker bridge, network. Must not overlap with any other provided IP ranges."
67 | }
68 | },
69 | "platformReservedCidr": {
70 | "type": "string",
71 | "defaultValue": "10.1.0.0/16",
72 | "metadata": {
73 | "description": "Specifies the IP range in CIDR notation that can be reserved for environment infrastructure IP addresses. Must not overlap with any other provided IP ranges."
74 | }
75 | },
76 | "platformReservedDnsIP": {
77 | "type": "string",
78 | "defaultValue": "10.1.0.2",
79 | "metadata": {
80 | "description": "Specifies an IP address from the IP range defined by platformReservedCidr that will be reserved for the internal DNS server."
81 | }
82 | },
83 | "zoneRedundant": {
84 | "type": "bool",
85 | "defaultValue": true,
86 | "metadata": {
87 | "description": "Specifies whether the Azure Container Apps environment should be zone-redundant."
88 | }
89 | },
90 | "logAnalyticsWorkspaceName": {
91 | "type": "string",
92 | "defaultValue": "[if(equals(parameters('letterCaseType'), 'UpperCamelCase'), format('{0}{1}Workspace', toUpper(first(parameters('prefix'))), toLower(substring(parameters('prefix'), 1, sub(length(parameters('prefix')), 1)))), if(equals(parameters('letterCaseType'), 'CamelCase'), format('{0}Workspace', toLower(parameters('prefix'))), format('{0}-workspace', toLower(parameters('prefix')))))]",
93 | "metadata": {
94 | "description": "Specifies the name of the Log Analytics workspace."
95 | }
96 | },
97 | "logAnalyticsRetentionInDays": {
98 | "type": "int",
99 | "defaultValue": 30,
100 | "metadata": {
101 | "description": "Specifies the workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku. 730 days is the maximum allowed for all other Skus."
102 | }
103 | },
104 | "logAnalyticsSku": {
105 | "type": "string",
106 | "defaultValue": "PerNode",
107 | "allowedValues": [
108 | "Free",
109 | "Standalone",
110 | "PerNode",
111 | "PerGB2018"
112 | ],
113 | "metadata": {
114 | "description": "Specifies the service tier of the workspace: Free, Standalone, PerNode, Per-GB."
115 | }
116 | },
117 | "virtualNetworkName": {
118 | "type": "string",
119 | "defaultValue": "[if(equals(parameters('letterCaseType'), 'UpperCamelCase'), format('{0}{1}Vnet', toUpper(first(parameters('prefix'))), toLower(substring(parameters('prefix'), 1, sub(length(parameters('prefix')), 1)))), if(equals(parameters('letterCaseType'), 'CamelCase'), format('{0}Vnet', toLower(parameters('prefix'))), format('{0}-vnet', toLower(parameters('prefix')))))]",
120 | "metadata": {
121 | "description": "Specifies the name of the virtual network."
122 | }
123 | },
124 | "virtualNetworkAddressPrefixes": {
125 | "type": "string",
126 | "defaultValue": "10.0.0.0/8",
127 | "metadata": {
128 | "description": "Specifies the address prefixes of the virtual network."
129 | }
130 | },
131 | "infrastructureSubnetName": {
132 | "type": "string",
133 | "defaultValue": "InfrastructureSubnet",
134 | "metadata": {
135 | "description": "Specifies the name of a subnet for the Azure Container Apps environment infrastructure components. This subnet must be in the same VNET as the subnet defined in runtimeSubnetId. Must not overlap with any other provided IP ranges."
136 | }
137 | },
138 | "infrastructureSubnetAddressPrefix": {
139 | "type": "string",
140 | "defaultValue": "10.0.0.0/21",
141 | "metadata": {
142 | "description": "Specifies the address prefix of a subnet for the Azure Container Apps environment infrastructure components. This subnet must be in the same VNET as the subnet defined in runtimeSubnetId. Must not overlap with any other provided IP ranges."
143 | }
144 | },
145 | "privateLinkServiceSubnetName": {
146 | "type": "string",
147 | "defaultValue": "PrivateSubnet",
148 | "metadata": {
149 | "description": "Specifies the name of the subnet hosting the worker nodes of the user agent pool of the AKS cluster."
150 | }
151 | },
152 | "privateLinkServiceSubnetAddressPrefix": {
153 | "type": "string",
154 | "defaultValue": "10.0.16.0/21",
155 | "metadata": {
156 | "description": "Specifies the address prefix of the subnet hosting the worker nodes of the user agent pool of the AKS cluster."
157 | }
158 | },
159 | "serviceBusNamespace": {
160 | "type": "string",
161 | "defaultValue": "[if(equals(parameters('letterCaseType'), 'UpperCamelCase'), format('{0}{1}ServiceBus', toUpper(first(parameters('prefix'))), toLower(substring(parameters('prefix'), 1, sub(length(parameters('prefix')), 1)))), if(equals(parameters('letterCaseType'), 'CamelCase'), format('{0}ServiceBus', toLower(parameters('prefix'))), format('{0}-servicebus', toLower(parameters('prefix')))))]",
162 | "metadata": {
163 | "description": "Specifies the name of the Service Bus namespace."
164 | }
165 | },
166 | "serviceBusZoneRedundant": {
167 | "type": "bool",
168 | "defaultValue": true,
169 | "metadata": {
170 | "description": "Enabling this property creates a Premium Service Bus Namespace in regions supported availability zones."
171 | }
172 | },
173 | "serviceBusSkuName": {
174 | "type": "string",
175 | "defaultValue": "Premium",
176 | "allowedValues": [
177 | "Basic",
178 | "Premium",
179 | "Standard"
180 | ],
181 | "metadata": {
182 | "description": "Specifies the name of Service Bus namespace SKU."
183 | }
184 | },
185 | "serviceBusCapacity": {
186 | "type": "int",
187 | "defaultValue": 1,
188 | "metadata": {
189 | "description": "Specifies the messaging units for the Service Bus namespace. For Premium tier, capacity are 1,2 and 4."
190 | }
191 | },
192 | "serviceBusQueueNames": {
193 | "type": "array",
194 | "defaultValue": [
195 | "parameters",
196 | "results"
197 | ],
198 | "metadata": {
199 | "description": "Specifies the name of the Service Bus queue."
200 | }
201 | },
202 | "serviceBusQueueLockDuration": {
203 | "type": "string",
204 | "defaultValue": "PT5M",
205 | "metadata": {
206 | "description": "Specifies the lock duration of the queue."
207 | }
208 | },
209 | "serviceBusQueueMaxDeliveryCount": {
210 | "type": "int",
211 | "defaultValue": 10,
212 | "metadata": {
213 | "description": "Specifies the maximum delivery count of the queue."
214 | }
215 | },
216 | "serviceBusQueueRequiresDuplicateDetection": {
217 | "type": "bool",
218 | "defaultValue": false,
219 | "metadata": {
220 | "description": "Specifies whether duplication is enabled on the queue."
221 | }
222 | },
223 | "serviceBusQueueRequiresSession": {
224 | "type": "bool",
225 | "defaultValue": false,
226 | "metadata": {
227 | "description": "Specifies whether session is enabled on the queue."
228 | }
229 | },
230 | "serviceBusQueueDeadLetteringOnMessageExpiration": {
231 | "type": "bool",
232 | "defaultValue": false,
233 | "metadata": {
234 | "description": "Specifies whether dead lettering is enabled on the queue."
235 | }
236 | },
237 | "serviceBusQueueDuplicateDetectionHistoryTimeWindow": {
238 | "type": "string",
239 | "defaultValue": "PT10M",
240 | "metadata": {
241 | "description": "Specifies the duplicate detection history time of the queue."
242 | }
243 | },
244 | "serviceBusNamespacePrivateEndpointName": {
245 | "type": "string",
246 | "defaultValue": "[if(equals(parameters('letterCaseType'), 'UpperCamelCase'), format('{0}{1}ServiceBusNamespacePrivateEndpoint', toUpper(first(parameters('prefix'))), toLower(substring(parameters('prefix'), 1, sub(length(parameters('prefix')), 1)))), if(equals(parameters('letterCaseType'), 'CamelCase'), format('{0}ServiceBusNamespacePrivateEndpoint', toLower(parameters('prefix'))), format('{0}-service-bus-namespace-private-endpoint', toLower(parameters('prefix')))))]",
247 | "metadata": {
248 | "description": "Specifies the name of the private link to the storage account."
249 | }
250 | },
251 | "managedIdentityName": {
252 | "type": "string",
253 | "defaultValue": "[if(equals(parameters('letterCaseType'), 'UpperCamelCase'), format('{0}{1}JobManagedIdentity', toUpper(first(parameters('prefix'))), toLower(substring(parameters('prefix'), 1, sub(length(parameters('prefix')), 1)))), if(equals(parameters('letterCaseType'), 'CamelCase'), format('{0}JobManagedIdentity', toLower(parameters('prefix'))), format('{0}-job-managed-identity', toLower(parameters('prefix')))))]",
254 | "metadata": {
255 | "description": "Specifies the name of the user-defined managed identity."
256 | }
257 | },
258 | "acrName": {
259 | "type": "string",
260 | "defaultValue": "[if(equals(parameters('letterCaseType'), 'UpperCamelCase'), format('{0}{1}Acr', toUpper(first(parameters('prefix'))), toLower(substring(parameters('prefix'), 1, sub(length(parameters('prefix')), 1)))), if(equals(parameters('letterCaseType'), 'CamelCase'), format('{0}Acr', toLower(parameters('prefix'))), format('{0}-acr', toLower(parameters('prefix')))))]",
261 | "maxLength": 50,
262 | "minLength": 5,
263 | "metadata": {
264 | "description": "Specifies the name of the Azure Container Registry"
265 | }
266 | },
267 | "acrAdminUserEnabled": {
268 | "type": "bool",
269 | "defaultValue": false,
270 | "metadata": {
271 | "description": "Enable admin user that have push / pull permission to the registry."
272 | }
273 | },
274 | "acrPrivateEndpointName": {
275 | "type": "string",
276 | "defaultValue": "[if(equals(parameters('letterCaseType'), 'UpperCamelCase'), format('{0}{1}AcrPrivateEndpoint', toUpper(first(parameters('prefix'))), toLower(substring(parameters('prefix'), 1, sub(length(parameters('prefix')), 1)))), if(equals(parameters('letterCaseType'), 'CamelCase'), format('{0}AcrPrivateEndpoint', toLower(parameters('prefix'))), format('{0}-acr-private-endpoint', toLower(parameters('prefix')))))]",
277 | "metadata": {
278 | "description": "Specifies the name of the private link to the Azure Container Registry."
279 | }
280 | },
281 | "acrSkuName": {
282 | "type": "string",
283 | "defaultValue": "Premium",
284 | "allowedValues": [
285 | "Basic",
286 | "Standard",
287 | "Premium"
288 | ],
289 | "metadata": {
290 | "description": "Specifies the SKU name of the Azure Container Registry."
291 | }
292 | }
293 | },
294 | "resources": [
295 | {
296 | "type": "Microsoft.Resources/deployments",
297 | "apiVersion": "2022-09-01",
298 | "name": "workspace",
299 | "properties": {
300 | "expressionEvaluationOptions": {
301 | "scope": "inner"
302 | },
303 | "mode": "Incremental",
304 | "parameters": {
305 | "name": {
306 | "value": "[parameters('logAnalyticsWorkspaceName')]"
307 | },
308 | "sku": {
309 | "value": "[parameters('logAnalyticsSku')]"
310 | },
311 | "retentionInDays": {
312 | "value": "[parameters('logAnalyticsRetentionInDays')]"
313 | },
314 | "location": {
315 | "value": "[parameters('location')]"
316 | },
317 | "tags": {
318 | "value": "[parameters('tags')]"
319 | }
320 | },
321 | "template": {
322 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
323 | "contentVersion": "1.0.0.0",
324 | "metadata": {
325 | "_generator": {
326 | "name": "bicep",
327 | "version": "0.18.4.5664",
328 | "templateHash": "1345435483846805772"
329 | }
330 | },
331 | "parameters": {
332 | "name": {
333 | "type": "string",
334 | "metadata": {
335 | "description": "Specifies the name of the Log Analytics workspace."
336 | }
337 | },
338 | "sku": {
339 | "type": "string",
340 | "defaultValue": "PerNode",
341 | "allowedValues": [
342 | "Free",
343 | "Standalone",
344 | "PerNode",
345 | "PerGB2018"
346 | ],
347 | "metadata": {
348 | "description": "Specifies the service tier of the workspace: Free, Standalone, PerNode, Per-GB."
349 | }
350 | },
351 | "retentionInDays": {
352 | "type": "int",
353 | "defaultValue": 60,
354 | "metadata": {
355 | "description": "Specifies the workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku. 730 days is the maximum allowed for all other Skus."
356 | }
357 | },
358 | "location": {
359 | "type": "string",
360 | "defaultValue": "[resourceGroup().location]",
361 | "metadata": {
362 | "description": "Specifies the location."
363 | }
364 | },
365 | "tags": {
366 | "type": "object",
367 | "metadata": {
368 | "description": "Specifies the resource tags."
369 | }
370 | }
371 | },
372 | "resources": [
373 | {
374 | "type": "Microsoft.OperationalInsights/workspaces",
375 | "apiVersion": "2021-12-01-preview",
376 | "name": "[parameters('name')]",
377 | "tags": "[parameters('tags')]",
378 | "location": "[parameters('location')]",
379 | "properties": {
380 | "sku": {
381 | "name": "[parameters('sku')]"
382 | },
383 | "retentionInDays": "[parameters('retentionInDays')]"
384 | }
385 | }
386 | ],
387 | "outputs": {
388 | "id": {
389 | "type": "string",
390 | "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]"
391 | },
392 | "name": {
393 | "type": "string",
394 | "value": "[parameters('name')]"
395 | },
396 | "customerId": {
397 | "type": "string",
398 | "value": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), '2021-12-01-preview').customerId]"
399 | }
400 | }
401 | }
402 | }
403 | },
404 | {
405 | "type": "Microsoft.Resources/deployments",
406 | "apiVersion": "2022-09-01",
407 | "name": "namespace",
408 | "properties": {
409 | "expressionEvaluationOptions": {
410 | "scope": "inner"
411 | },
412 | "mode": "Incremental",
413 | "parameters": {
414 | "name": {
415 | "value": "[parameters('serviceBusNamespace')]"
416 | },
417 | "location": {
418 | "value": "[parameters('location')]"
419 | },
420 | "tags": {
421 | "value": "[parameters('tags')]"
422 | },
423 | "zoneRedundant": {
424 | "value": "[parameters('serviceBusZoneRedundant')]"
425 | },
426 | "skuName": {
427 | "value": "[parameters('serviceBusSkuName')]"
428 | },
429 | "capacity": {
430 | "value": "[parameters('serviceBusCapacity')]"
431 | },
432 | "queueNames": {
433 | "value": "[parameters('serviceBusQueueNames')]"
434 | },
435 | "lockDuration": {
436 | "value": "[parameters('serviceBusQueueLockDuration')]"
437 | },
438 | "maxDeliveryCount": {
439 | "value": "[parameters('serviceBusQueueMaxDeliveryCount')]"
440 | },
441 | "requiresDuplicateDetection": {
442 | "value": "[parameters('serviceBusQueueRequiresDuplicateDetection')]"
443 | },
444 | "requiresSession": {
445 | "value": "[parameters('serviceBusQueueRequiresSession')]"
446 | },
447 | "deadLetteringOnMessageExpiration": {
448 | "value": "[parameters('serviceBusQueueDeadLetteringOnMessageExpiration')]"
449 | },
450 | "duplicateDetectionHistoryTimeWindow": {
451 | "value": "[parameters('serviceBusQueueDuplicateDetectionHistoryTimeWindow')]"
452 | },
453 | "workspaceId": {
454 | "value": "[reference(resourceId('Microsoft.Resources/deployments', 'workspace'), '2022-09-01').outputs.id.value]"
455 | }
456 | },
457 | "template": {
458 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
459 | "contentVersion": "1.0.0.0",
460 | "metadata": {
461 | "_generator": {
462 | "name": "bicep",
463 | "version": "0.18.4.5664",
464 | "templateHash": "6642034885011448787"
465 | }
466 | },
467 | "parameters": {
468 | "name": {
469 | "type": "string",
470 | "metadata": {
471 | "description": "Specifies the name of the Service Bus namespace."
472 | }
473 | },
474 | "zoneRedundant": {
475 | "type": "bool",
476 | "defaultValue": true,
477 | "metadata": {
478 | "description": "Enabling this property creates a Premium Service Bus Namespace in regions supported availability zones."
479 | }
480 | },
481 | "skuName": {
482 | "type": "string",
483 | "defaultValue": "Standard",
484 | "allowedValues": [
485 | "Basic",
486 | "Premium",
487 | "Standard"
488 | ],
489 | "metadata": {
490 | "description": "Specifies the name of Service Bus namespace SKU."
491 | }
492 | },
493 | "capacity": {
494 | "type": "int",
495 | "defaultValue": 1,
496 | "metadata": {
497 | "description": "Specifies the messaging units for the Service Bus namespace. For Premium tier, capacity are 1,2 and 4."
498 | }
499 | },
500 | "queueNames": {
501 | "type": "array",
502 | "defaultValue": [],
503 | "metadata": {
504 | "description": "Specifies the name of the Service Bus queue."
505 | }
506 | },
507 | "lockDuration": {
508 | "type": "string",
509 | "defaultValue": "PT5M",
510 | "metadata": {
511 | "description": "Specifies the lock duration of the queue."
512 | }
513 | },
514 | "maxDeliveryCount": {
515 | "type": "int",
516 | "defaultValue": 10,
517 | "metadata": {
518 | "description": "Specifies the maximum delivery count of the queue."
519 | }
520 | },
521 | "requiresDuplicateDetection": {
522 | "type": "bool",
523 | "defaultValue": false,
524 | "metadata": {
525 | "description": "Specifies whether duplication is enabled on the queue."
526 | }
527 | },
528 | "requiresSession": {
529 | "type": "bool",
530 | "defaultValue": false,
531 | "metadata": {
532 | "description": "Specifies whether session is enabled on the queue."
533 | }
534 | },
535 | "deadLetteringOnMessageExpiration": {
536 | "type": "bool",
537 | "defaultValue": false,
538 | "metadata": {
539 | "description": "Specifies whether dead lettering is enabled on the queue."
540 | }
541 | },
542 | "duplicateDetectionHistoryTimeWindow": {
543 | "type": "string",
544 | "defaultValue": "PT10M",
545 | "metadata": {
546 | "description": "Specifies the duplicate detection history time of the queue."
547 | }
548 | },
549 | "workspaceId": {
550 | "type": "string",
551 | "metadata": {
552 | "description": "Specifies the resource id of the Log Analytics workspace."
553 | }
554 | },
555 | "retentionInDays": {
556 | "type": "int",
557 | "defaultValue": 60,
558 | "metadata": {
559 | "description": "Specifies the workspace data retention in days."
560 | }
561 | },
562 | "location": {
563 | "type": "string",
564 | "defaultValue": "[resourceGroup().location]",
565 | "metadata": {
566 | "description": "Specifies the location."
567 | }
568 | },
569 | "tags": {
570 | "type": "object",
571 | "metadata": {
572 | "description": "Specifies the resource tags."
573 | }
574 | }
575 | },
576 | "variables": {
577 | "copy": [
578 | {
579 | "name": "logs",
580 | "count": "[length(variables('logCategories'))]",
581 | "input": {
582 | "category": "[variables('logCategories')[copyIndex('logs')]]",
583 | "enabled": true,
584 | "retentionPolicy": {
585 | "enabled": true,
586 | "days": "[parameters('retentionInDays')]"
587 | }
588 | }
589 | },
590 | {
591 | "name": "metrics",
592 | "count": "[length(variables('metricCategories'))]",
593 | "input": {
594 | "category": "[variables('metricCategories')[copyIndex('metrics')]]",
595 | "enabled": true,
596 | "retentionPolicy": {
597 | "enabled": true,
598 | "days": "[parameters('retentionInDays')]"
599 | }
600 | }
601 | }
602 | ],
603 | "diagnosticSettingsName": "diagnosticSettings",
604 | "logCategories": [
605 | "OperationalLogs",
606 | "VNetAndIPFilteringLogs",
607 | "RuntimeAuditLogs",
608 | "ApplicationMetricsLogs"
609 | ],
610 | "metricCategories": [
611 | "AllMetrics"
612 | ],
613 | "serviceBusEndpoint": "[format('{0}/AuthorizationRules/RootManageSharedAccessKey', resourceId('Microsoft.ServiceBus/namespaces', parameters('name')))]"
614 | },
615 | "resources": [
616 | {
617 | "type": "Microsoft.ServiceBus/namespaces",
618 | "apiVersion": "2022-10-01-preview",
619 | "name": "[parameters('name')]",
620 | "location": "[parameters('location')]",
621 | "tags": "[parameters('tags')]",
622 | "sku": {
623 | "name": "[parameters('skuName')]",
624 | "capacity": "[parameters('capacity')]"
625 | },
626 | "properties": {
627 | "zoneRedundant": "[if(equals(parameters('skuName'), 'Premium'), parameters('zoneRedundant'), false())]"
628 | }
629 | },
630 | {
631 | "copy": {
632 | "name": "queue",
633 | "count": "[length(parameters('queueNames'))]"
634 | },
635 | "type": "Microsoft.ServiceBus/namespaces/queues",
636 | "apiVersion": "2022-10-01-preview",
637 | "name": "[format('{0}/{1}', parameters('name'), parameters('queueNames')[copyIndex()])]",
638 | "properties": {
639 | "lockDuration": "[parameters('lockDuration')]",
640 | "maxSizeInMegabytes": 1024,
641 | "requiresDuplicateDetection": "[parameters('requiresDuplicateDetection')]",
642 | "requiresSession": "[parameters('requiresSession')]",
643 | "deadLetteringOnMessageExpiration": "[parameters('deadLetteringOnMessageExpiration')]",
644 | "duplicateDetectionHistoryTimeWindow": "[parameters('duplicateDetectionHistoryTimeWindow')]",
645 | "maxDeliveryCount": "[parameters('maxDeliveryCount')]"
646 | },
647 | "dependsOn": [
648 | "[resourceId('Microsoft.ServiceBus/namespaces', parameters('name'))]"
649 | ]
650 | },
651 | {
652 | "type": "Microsoft.Insights/diagnosticSettings",
653 | "apiVersion": "2021-05-01-preview",
654 | "scope": "[format('Microsoft.ServiceBus/namespaces/{0}', parameters('name'))]",
655 | "name": "[variables('diagnosticSettingsName')]",
656 | "properties": {
657 | "workspaceId": "[parameters('workspaceId')]",
658 | "logs": "[variables('logs')]",
659 | "metrics": "[variables('metrics')]"
660 | },
661 | "dependsOn": [
662 | "[resourceId('Microsoft.ServiceBus/namespaces', parameters('name'))]"
663 | ]
664 | }
665 | ],
666 | "outputs": {
667 | "id": {
668 | "type": "string",
669 | "value": "[resourceId('Microsoft.ServiceBus/namespaces', parameters('name'))]"
670 | },
671 | "name": {
672 | "type": "string",
673 | "value": "[parameters('name')]"
674 | },
675 | "connectionString": {
676 | "type": "string",
677 | "value": "[listKeys(variables('serviceBusEndpoint'), '2022-10-01-preview').primaryConnectionString]"
678 | },
679 | "queues": {
680 | "type": "array",
681 | "copy": {
682 | "count": "[length(parameters('queueNames'))]",
683 | "input": {
684 | "name": "[parameters('name')]",
685 | "id": "[resourceId('Microsoft.ServiceBus/namespaces/queues', parameters('name'), parameters('queueNames')[copyIndex()])]"
686 | }
687 | }
688 | }
689 | }
690 | }
691 | },
692 | "dependsOn": [
693 | "[resourceId('Microsoft.Resources/deployments', 'workspace')]"
694 | ]
695 | },
696 | {
697 | "type": "Microsoft.Resources/deployments",
698 | "apiVersion": "2022-09-01",
699 | "name": "network",
700 | "properties": {
701 | "expressionEvaluationOptions": {
702 | "scope": "inner"
703 | },
704 | "mode": "Incremental",
705 | "parameters": {
706 | "name": {
707 | "value": "[parameters('virtualNetworkName')]"
708 | },
709 | "location": {
710 | "value": "[parameters('location')]"
711 | },
712 | "tags": {
713 | "value": "[parameters('tags')]"
714 | },
715 | "addressPrefixes": {
716 | "value": "[parameters('virtualNetworkAddressPrefixes')]"
717 | },
718 | "infrastructureSubnetName": {
719 | "value": "[parameters('infrastructureSubnetName')]"
720 | },
721 | "infrastructureSubnetAddressPrefix": {
722 | "value": "[parameters('infrastructureSubnetAddressPrefix')]"
723 | },
724 | "privateLinkServiceSubnetName": {
725 | "value": "[parameters('privateLinkServiceSubnetName')]"
726 | },
727 | "privateLinkServiceSubnetAddressPrefix": {
728 | "value": "[parameters('privateLinkServiceSubnetAddressPrefix')]"
729 | },
730 | "workspaceId": {
731 | "value": "[reference(resourceId('Microsoft.Resources/deployments', 'workspace'), '2022-09-01').outputs.id.value]"
732 | },
733 | "serviceBusNamespacePrivateEndpointEnabled": {
734 | "value": "[equals(parameters('serviceBusSkuName'), 'Premium')]"
735 | },
736 | "serviceBusNamespacePrivateEndpointName": {
737 | "value": "[parameters('serviceBusNamespacePrivateEndpointName')]"
738 | },
739 | "serviceBusNamespaceId": {
740 | "value": "[reference(resourceId('Microsoft.Resources/deployments', 'namespace'), '2022-09-01').outputs.id.value]"
741 | },
742 | "acrPrivateEndpointEnabled": {
743 | "value": "[equals(parameters('acrSkuName'), 'Premium')]"
744 | },
745 | "acrPrivateEndpointName": {
746 | "value": "[parameters('acrPrivateEndpointName')]"
747 | },
748 | "acrId": {
749 | "value": "[reference(resourceId('Microsoft.Resources/deployments', 'containerRegistry'), '2022-09-01').outputs.id.value]"
750 | }
751 | },
752 | "template": {
753 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
754 | "contentVersion": "1.0.0.0",
755 | "metadata": {
756 | "_generator": {
757 | "name": "bicep",
758 | "version": "0.18.4.5664",
759 | "templateHash": "7146871086920983355"
760 | }
761 | },
762 | "parameters": {
763 | "name": {
764 | "type": "string",
765 | "metadata": {
766 | "description": "Specifies the name of the Azure Container Apps Environment."
767 | }
768 | },
769 | "addressPrefixes": {
770 | "type": "string",
771 | "defaultValue": "10.0.0.0/8",
772 | "metadata": {
773 | "description": "Specifies the address prefixes of the virtual network."
774 | }
775 | },
776 | "infrastructureSubnetName": {
777 | "type": "string",
778 | "defaultValue": "InfrastructureSubnet",
779 | "metadata": {
780 | "description": "Specifies the name of a subnet for the Azure Container Apps environment infrastructure components. This subnet must be in the same VNET as the subnet defined in runtimeSubnetId. Must not overlap with any other provided IP ranges."
781 | }
782 | },
783 | "infrastructureSubnetAddressPrefix": {
784 | "type": "string",
785 | "defaultValue": "10.0.0.0/21",
786 | "metadata": {
787 | "description": "Specifies the address prefix of a subnet for the Azure Container Apps environment infrastructure components. This subnet must be in the same VNET as the subnet defined in runtimeSubnetId. Must not overlap with any other provided IP ranges."
788 | }
789 | },
790 | "privateLinkServiceSubnetName": {
791 | "type": "string",
792 | "defaultValue": "PlsSubnet",
793 | "metadata": {
794 | "description": "Specifies the name of the subnet hosting the worker nodes of the user agent pool of the AKS cluster."
795 | }
796 | },
797 | "privateLinkServiceSubnetAddressPrefix": {
798 | "type": "string",
799 | "defaultValue": "10.0.16.0/21",
800 | "metadata": {
801 | "description": "Specifies the address prefix of the subnet hosting the worker nodes of the user agent pool of the AKS cluster."
802 | }
803 | },
804 | "serviceBusNamespacePrivateEndpointEnabled": {
805 | "type": "bool",
806 | "defaultValue": false,
807 | "metadata": {
808 | "description": "Specifies whether enabling the private link to the Azure Service Bus namespace."
809 | }
810 | },
811 | "serviceBusNamespacePrivateEndpointName": {
812 | "type": "string",
813 | "defaultValue": "ServiceBusNamespacePrivateEndpoint",
814 | "metadata": {
815 | "description": "Specifies the name of the private link to the Azure Service Bus namespace."
816 | }
817 | },
818 | "serviceBusNamespaceId": {
819 | "type": "string",
820 | "metadata": {
821 | "description": "Specifies the resource id of the Azure Service Bus namespace."
822 | }
823 | },
824 | "acrPrivateEndpointEnabled": {
825 | "type": "bool",
826 | "defaultValue": false,
827 | "metadata": {
828 | "description": "Specifies whether enabling the private link to the Azure Container Registry."
829 | }
830 | },
831 | "acrPrivateEndpointName": {
832 | "type": "string",
833 | "defaultValue": "AcrPrivateEndpoint",
834 | "metadata": {
835 | "description": "Specifies the name of the private link to the Azure Container Registry."
836 | }
837 | },
838 | "acrId": {
839 | "type": "string",
840 | "metadata": {
841 | "description": "Specifies the resource id of the Azure Container Registry."
842 | }
843 | },
844 | "workspaceId": {
845 | "type": "string",
846 | "metadata": {
847 | "description": "Specifies the resource id of the Log Analytics workspace."
848 | }
849 | },
850 | "retentionInDays": {
851 | "type": "int",
852 | "defaultValue": 60,
853 | "metadata": {
854 | "description": "Specifies the workspace data retention in days."
855 | }
856 | },
857 | "location": {
858 | "type": "string",
859 | "defaultValue": "[resourceGroup().location]",
860 | "metadata": {
861 | "description": "Specifies the location."
862 | }
863 | },
864 | "tags": {
865 | "type": "object",
866 | "metadata": {
867 | "description": "Specifies the resource tags."
868 | }
869 | }
870 | },
871 | "variables": {
872 | "copy": [
873 | {
874 | "name": "vnetLogs",
875 | "count": "[length(variables('vnetLogCategories'))]",
876 | "input": {
877 | "category": "[variables('vnetLogCategories')[copyIndex('vnetLogs')]]",
878 | "enabled": true,
879 | "retentionPolicy": {
880 | "enabled": true,
881 | "days": "[parameters('retentionInDays')]"
882 | }
883 | }
884 | },
885 | {
886 | "name": "vnetMetrics",
887 | "count": "[length(variables('vnetMetricCategories'))]",
888 | "input": {
889 | "category": "[variables('vnetMetricCategories')[copyIndex('vnetMetrics')]]",
890 | "enabled": true,
891 | "retentionPolicy": {
892 | "enabled": true,
893 | "days": "[parameters('retentionInDays')]"
894 | }
895 | }
896 | }
897 | ],
898 | "diagnosticSettingsName": "diagnosticSettings",
899 | "vnetLogCategories": [
900 | "VMProtectionAlerts"
901 | ],
902 | "vnetMetricCategories": [
903 | "AllMetrics"
904 | ]
905 | },
906 | "resources": [
907 | {
908 | "type": "Microsoft.Network/virtualNetworks",
909 | "apiVersion": "2022-07-01",
910 | "name": "[parameters('name')]",
911 | "location": "[parameters('location')]",
912 | "tags": "[parameters('tags')]",
913 | "properties": {
914 | "addressSpace": {
915 | "addressPrefixes": [
916 | "[parameters('addressPrefixes')]"
917 | ]
918 | },
919 | "subnets": [
920 | {
921 | "name": "[parameters('infrastructureSubnetName')]",
922 | "properties": {
923 | "addressPrefix": "[parameters('infrastructureSubnetAddressPrefix')]",
924 | "delegations": [],
925 | "privateEndpointNetworkPolicies": "Disabled",
926 | "privateLinkServiceNetworkPolicies": "Enabled"
927 | }
928 | },
929 | {
930 | "name": "[parameters('privateLinkServiceSubnetName')]",
931 | "properties": {
932 | "addressPrefix": "[parameters('privateLinkServiceSubnetAddressPrefix')]",
933 | "delegations": [],
934 | "privateEndpointNetworkPolicies": "Disabled",
935 | "privateLinkServiceNetworkPolicies": "Disabled"
936 | }
937 | }
938 | ],
939 | "virtualNetworkPeerings": [],
940 | "enableDdosProtection": false
941 | }
942 | },
943 | {
944 | "condition": "[parameters('serviceBusNamespacePrivateEndpointEnabled')]",
945 | "type": "Microsoft.Network/privateDnsZones",
946 | "apiVersion": "2020-06-01",
947 | "name": "[format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'servicebus.usgovcloudapi.net', 'servicebus.windows.net'))]",
948 | "location": "global",
949 | "tags": "[parameters('tags')]"
950 | },
951 | {
952 | "condition": "[parameters('acrPrivateEndpointEnabled')]",
953 | "type": "Microsoft.Network/privateDnsZones",
954 | "apiVersion": "2020-06-01",
955 | "name": "[format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'azurecr.us', 'azurecr.io'))]",
956 | "location": "global",
957 | "tags": "[parameters('tags')]"
958 | },
959 | {
960 | "condition": "[parameters('serviceBusNamespacePrivateEndpointEnabled')]",
961 | "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
962 | "apiVersion": "2020-06-01",
963 | "name": "[format('{0}/{1}', format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'servicebus.usgovcloudapi.net', 'servicebus.windows.net')), format('link_to_{0}', toLower(parameters('name'))))]",
964 | "location": "global",
965 | "properties": {
966 | "registrationEnabled": false,
967 | "virtualNetwork": {
968 | "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
969 | }
970 | },
971 | "dependsOn": [
972 | "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'servicebus.usgovcloudapi.net', 'servicebus.windows.net')))]",
973 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
974 | ]
975 | },
976 | {
977 | "condition": "[parameters('acrPrivateEndpointEnabled')]",
978 | "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
979 | "apiVersion": "2020-06-01",
980 | "name": "[format('{0}/{1}', format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'azurecr.us', 'azurecr.io')), format('link_to_{0}', toLower(parameters('name'))))]",
981 | "location": "global",
982 | "properties": {
983 | "registrationEnabled": false,
984 | "virtualNetwork": {
985 | "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
986 | }
987 | },
988 | "dependsOn": [
989 | "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'azurecr.us', 'azurecr.io')))]",
990 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
991 | ]
992 | },
993 | {
994 | "condition": "[parameters('serviceBusNamespacePrivateEndpointEnabled')]",
995 | "type": "Microsoft.Network/privateEndpoints",
996 | "apiVersion": "2021-08-01",
997 | "name": "[parameters('serviceBusNamespacePrivateEndpointName')]",
998 | "location": "[parameters('location')]",
999 | "tags": "[parameters('tags')]",
1000 | "properties": {
1001 | "privateLinkServiceConnections": [
1002 | {
1003 | "name": "[parameters('serviceBusNamespacePrivateEndpointName')]",
1004 | "properties": {
1005 | "privateLinkServiceId": "[parameters('serviceBusNamespaceId')]",
1006 | "groupIds": [
1007 | "namespace"
1008 | ]
1009 | }
1010 | }
1011 | ],
1012 | "subnet": {
1013 | "id": "[format('{0}/subnets/{1}', resourceId('Microsoft.Network/virtualNetworks', parameters('name')), parameters('privateLinkServiceSubnetName'))]"
1014 | }
1015 | },
1016 | "dependsOn": [
1017 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
1018 | ]
1019 | },
1020 | {
1021 | "condition": "[parameters('serviceBusNamespacePrivateEndpointEnabled')]",
1022 | "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
1023 | "apiVersion": "2021-08-01",
1024 | "name": "[format('{0}/{1}', parameters('serviceBusNamespacePrivateEndpointName'), 'PrivateDnsZoneGroupName')]",
1025 | "properties": {
1026 | "privateDnsZoneConfigs": [
1027 | {
1028 | "name": "dnsConfig",
1029 | "properties": {
1030 | "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'servicebus.usgovcloudapi.net', 'servicebus.windows.net')))]"
1031 | }
1032 | }
1033 | ]
1034 | },
1035 | "dependsOn": [
1036 | "[resourceId('Microsoft.Network/privateEndpoints', parameters('serviceBusNamespacePrivateEndpointName'))]",
1037 | "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'servicebus.usgovcloudapi.net', 'servicebus.windows.net')))]"
1038 | ]
1039 | },
1040 | {
1041 | "condition": "[parameters('acrPrivateEndpointEnabled')]",
1042 | "type": "Microsoft.Network/privateEndpoints",
1043 | "apiVersion": "2022-09-01",
1044 | "name": "[parameters('acrPrivateEndpointName')]",
1045 | "location": "[parameters('location')]",
1046 | "tags": "[parameters('tags')]",
1047 | "properties": {
1048 | "privateLinkServiceConnections": [
1049 | {
1050 | "name": "[parameters('acrPrivateEndpointName')]",
1051 | "properties": {
1052 | "privateLinkServiceId": "[parameters('acrId')]",
1053 | "groupIds": [
1054 | "registry"
1055 | ]
1056 | }
1057 | }
1058 | ],
1059 | "subnet": {
1060 | "id": "[format('{0}/subnets/{1}', resourceId('Microsoft.Network/virtualNetworks', parameters('name')), parameters('privateLinkServiceSubnetName'))]"
1061 | }
1062 | },
1063 | "dependsOn": [
1064 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
1065 | ]
1066 | },
1067 | {
1068 | "condition": "[parameters('acrPrivateEndpointEnabled')]",
1069 | "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
1070 | "apiVersion": "2022-09-01",
1071 | "name": "[format('{0}/{1}', parameters('acrPrivateEndpointName'), 'acrPrivateDnsZoneGroup')]",
1072 | "properties": {
1073 | "privateDnsZoneConfigs": [
1074 | {
1075 | "name": "dnsConfig",
1076 | "properties": {
1077 | "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'azurecr.us', 'azurecr.io')))]"
1078 | }
1079 | }
1080 | ]
1081 | },
1082 | "dependsOn": [
1083 | "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.{0}', if(equals(toLower(environment().name), 'azureusgovernment'), 'azurecr.us', 'azurecr.io')))]",
1084 | "[resourceId('Microsoft.Network/privateEndpoints', parameters('acrPrivateEndpointName'))]"
1085 | ]
1086 | },
1087 | {
1088 | "type": "Microsoft.Insights/diagnosticSettings",
1089 | "apiVersion": "2021-05-01-preview",
1090 | "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]",
1091 | "name": "[variables('diagnosticSettingsName')]",
1092 | "properties": {
1093 | "workspaceId": "[parameters('workspaceId')]",
1094 | "logs": "[variables('vnetLogs')]",
1095 | "metrics": "[variables('vnetMetrics')]"
1096 | },
1097 | "dependsOn": [
1098 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
1099 | ]
1100 | }
1101 | ],
1102 | "outputs": {
1103 | "id": {
1104 | "type": "string",
1105 | "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
1106 | },
1107 | "name": {
1108 | "type": "string",
1109 | "value": "[parameters('name')]"
1110 | },
1111 | "infrastructureSubnetId": {
1112 | "type": "string",
1113 | "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('name'), parameters('infrastructureSubnetName'))]"
1114 | },
1115 | "privateLinkServiceSubnetId": {
1116 | "type": "string",
1117 | "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('name'), parameters('privateLinkServiceSubnetName'))]"
1118 | }
1119 | }
1120 | }
1121 | },
1122 | "dependsOn": [
1123 | "[resourceId('Microsoft.Resources/deployments', 'containerRegistry')]",
1124 | "[resourceId('Microsoft.Resources/deployments', 'namespace')]",
1125 | "[resourceId('Microsoft.Resources/deployments', 'workspace')]"
1126 | ]
1127 | },
1128 | {
1129 | "type": "Microsoft.Resources/deployments",
1130 | "apiVersion": "2022-09-01",
1131 | "name": "environment",
1132 | "properties": {
1133 | "expressionEvaluationOptions": {
1134 | "scope": "inner"
1135 | },
1136 | "mode": "Incremental",
1137 | "parameters": {
1138 | "name": {
1139 | "value": "[parameters('containerAppEnvironmentName')]"
1140 | },
1141 | "location": {
1142 | "value": "[parameters('location')]"
1143 | },
1144 | "tags": {
1145 | "value": "[parameters('tags')]"
1146 | },
1147 | "internal": {
1148 | "value": "[parameters('internal')]"
1149 | },
1150 | "dockerBridgeCidr": {
1151 | "value": "[parameters('dockerBridgeCidr')]"
1152 | },
1153 | "platformReservedCidr": {
1154 | "value": "[parameters('platformReservedCidr')]"
1155 | },
1156 | "platformReservedDnsIP": {
1157 | "value": "[parameters('platformReservedDnsIP')]"
1158 | },
1159 | "zoneRedundant": {
1160 | "value": "[parameters('zoneRedundant')]"
1161 | },
1162 | "workspaceName": {
1163 | "value": "[parameters('logAnalyticsWorkspaceName')]"
1164 | },
1165 | "infrastructureSubnetId": {
1166 | "value": "[reference(resourceId('Microsoft.Resources/deployments', 'network'), '2022-09-01').outputs.infrastructureSubnetId.value]"
1167 | }
1168 | },
1169 | "template": {
1170 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
1171 | "contentVersion": "1.0.0.0",
1172 | "metadata": {
1173 | "_generator": {
1174 | "name": "bicep",
1175 | "version": "0.18.4.5664",
1176 | "templateHash": "14449268650626830847"
1177 | }
1178 | },
1179 | "parameters": {
1180 | "name": {
1181 | "type": "string",
1182 | "metadata": {
1183 | "description": "Specifies the name of the Azure Container Apps Environment."
1184 | }
1185 | },
1186 | "location": {
1187 | "type": "string",
1188 | "defaultValue": "[resourceGroup().location]",
1189 | "metadata": {
1190 | "description": "Specifies the location."
1191 | }
1192 | },
1193 | "tags": {
1194 | "type": "object",
1195 | "metadata": {
1196 | "description": "Specifies the resource tags."
1197 | }
1198 | },
1199 | "internal": {
1200 | "type": "bool",
1201 | "defaultValue": false,
1202 | "metadata": {
1203 | "description": "Specifies whether the environment only has an internal load balancer. These environments do not have a public static IP resource. They must provide infrastructureSubnetId if enabling this property"
1204 | }
1205 | },
1206 | "dockerBridgeCidr": {
1207 | "type": "string",
1208 | "metadata": {
1209 | "description": "Specifies the IP range in CIDR notation assigned to the Docker bridge, network. Must not overlap with any other provided IP ranges."
1210 | }
1211 | },
1212 | "platformReservedCidr": {
1213 | "type": "string",
1214 | "metadata": {
1215 | "description": "Specifies the IP range in CIDR notation that can be reserved for environment infrastructure IP addresses. Must not overlap with any other provided IP ranges."
1216 | }
1217 | },
1218 | "platformReservedDnsIP": {
1219 | "type": "string",
1220 | "metadata": {
1221 | "description": "Specifies an IP address from the IP range defined by platformReservedCidr that will be reserved for the internal DNS server."
1222 | }
1223 | },
1224 | "zoneRedundant": {
1225 | "type": "bool",
1226 | "defaultValue": true,
1227 | "metadata": {
1228 | "description": "Specifies whether the Azure Container Apps environment should be zone-redundant."
1229 | }
1230 | },
1231 | "infrastructureSubnetId": {
1232 | "type": "string",
1233 | "metadata": {
1234 | "description": "Specifies the resource id of the infrastructure subnet."
1235 | }
1236 | },
1237 | "workspaceName": {
1238 | "type": "string",
1239 | "metadata": {
1240 | "description": "Specifies the name of the Log Analytics workspace."
1241 | }
1242 | },
1243 | "daprAIInstrumentationKey": {
1244 | "type": "string",
1245 | "defaultValue": "",
1246 | "metadata": {
1247 | "description": "Specifies the Azure Monitor instrumentation key used by Dapr to export Service to Service communication telemetry."
1248 | }
1249 | },
1250 | "daprAIConnectionString": {
1251 | "type": "string",
1252 | "defaultValue": "",
1253 | "metadata": {
1254 | "description": "Specifies the configuration of Dapr component."
1255 | }
1256 | },
1257 | "certificatePassword": {
1258 | "type": "securestring",
1259 | "defaultValue": "",
1260 | "metadata": {
1261 | "description": "Specifies the certificate password."
1262 | }
1263 | },
1264 | "certificateValue": {
1265 | "type": "string",
1266 | "defaultValue": "",
1267 | "metadata": {
1268 | "description": "Specifies the PFX or PEM certificate value."
1269 | }
1270 | },
1271 | "dnsSuffix": {
1272 | "type": "string",
1273 | "defaultValue": "",
1274 | "metadata": {
1275 | "description": "Specifies the DNS suffix for the environment domain."
1276 | }
1277 | },
1278 | "workloadProfiles": {
1279 | "type": "array",
1280 | "defaultValue": [],
1281 | "metadata": {
1282 | "description": "Specifies workload profiles configured for the Managed Environment."
1283 | }
1284 | }
1285 | },
1286 | "resources": [
1287 | {
1288 | "type": "Microsoft.App/managedEnvironments",
1289 | "apiVersion": "2023-04-01-preview",
1290 | "name": "[parameters('name')]",
1291 | "location": "[parameters('location')]",
1292 | "tags": "[parameters('tags')]",
1293 | "properties": {
1294 | "customDomainConfiguration": "[if(and(and(empty(parameters('certificatePassword')), empty(parameters('certificateValue'))), empty(parameters('dnsSuffix'))), null(), createObject('certificatePassword', parameters('certificatePassword'), 'certificateValue', parameters('certificateValue'), 'dnsSuffix', parameters('dnsSuffix')))]",
1295 | "daprAIInstrumentationKey": "[parameters('daprAIInstrumentationKey')]",
1296 | "daprAIConnectionString": "[parameters('daprAIConnectionString')]",
1297 | "vnetConfiguration": {
1298 | "internal": "[parameters('internal')]",
1299 | "infrastructureSubnetId": "[parameters('infrastructureSubnetId')]",
1300 | "dockerBridgeCidr": "[parameters('dockerBridgeCidr')]",
1301 | "platformReservedCidr": "[parameters('platformReservedCidr')]",
1302 | "platformReservedDnsIP": "[parameters('platformReservedDnsIP')]"
1303 | },
1304 | "appLogsConfiguration": {
1305 | "destination": "log-analytics",
1306 | "logAnalyticsConfiguration": {
1307 | "customerId": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName')), '2021-12-01-preview').customerId]",
1308 | "sharedKey": "[listKeys(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName')), '2021-12-01-preview').primarySharedKey]"
1309 | }
1310 | },
1311 | "zoneRedundant": "[parameters('zoneRedundant')]",
1312 | "workloadProfiles": "[parameters('workloadProfiles')]"
1313 | }
1314 | }
1315 | ],
1316 | "outputs": {
1317 | "id": {
1318 | "type": "string",
1319 | "value": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]"
1320 | },
1321 | "name": {
1322 | "type": "string",
1323 | "value": "[parameters('name')]"
1324 | },
1325 | "daprConfiguration": {
1326 | "type": "object",
1327 | "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2023-04-01-preview').daprConfiguration]"
1328 | },
1329 | "kedaConfiguration": {
1330 | "type": "object",
1331 | "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2023-04-01-preview').kedaConfiguration]"
1332 | },
1333 | "appLogsConfiguration": {
1334 | "type": "object",
1335 | "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2023-04-01-preview').appLogsConfiguration]"
1336 | }
1337 | }
1338 | }
1339 | },
1340 | "dependsOn": [
1341 | "[resourceId('Microsoft.Resources/deployments', 'network')]"
1342 | ]
1343 | },
1344 | {
1345 | "type": "Microsoft.Resources/deployments",
1346 | "apiVersion": "2022-09-01",
1347 | "name": "managedIdentity",
1348 | "properties": {
1349 | "expressionEvaluationOptions": {
1350 | "scope": "inner"
1351 | },
1352 | "mode": "Incremental",
1353 | "parameters": {
1354 | "name": {
1355 | "value": "[parameters('managedIdentityName')]"
1356 | },
1357 | "location": {
1358 | "value": "[parameters('location')]"
1359 | },
1360 | "serviceBusNamespace": {
1361 | "value": "[reference(resourceId('Microsoft.Resources/deployments', 'namespace'), '2022-09-01').outputs.name.value]"
1362 | },
1363 | "acrName": {
1364 | "value": "[reference(resourceId('Microsoft.Resources/deployments', 'containerRegistry'), '2022-09-01').outputs.name.value]"
1365 | },
1366 | "tags": {
1367 | "value": "[parameters('tags')]"
1368 | }
1369 | },
1370 | "template": {
1371 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
1372 | "contentVersion": "1.0.0.0",
1373 | "metadata": {
1374 | "_generator": {
1375 | "name": "bicep",
1376 | "version": "0.18.4.5664",
1377 | "templateHash": "7072514701931445872"
1378 | }
1379 | },
1380 | "parameters": {
1381 | "name": {
1382 | "type": "string",
1383 | "metadata": {
1384 | "description": "Specifies the name of the user-defined managed identity."
1385 | }
1386 | },
1387 | "serviceBusNamespace": {
1388 | "type": "string",
1389 | "metadata": {
1390 | "description": "Specifies the name of the Service Bus namespace."
1391 | }
1392 | },
1393 | "acrName": {
1394 | "type": "string",
1395 | "metadata": {
1396 | "description": "Specifies the name of the Azure Container Registry"
1397 | }
1398 | },
1399 | "location": {
1400 | "type": "string",
1401 | "defaultValue": "[resourceGroup().location]",
1402 | "metadata": {
1403 | "description": "Specifies the location."
1404 | }
1405 | },
1406 | "tags": {
1407 | "type": "object",
1408 | "metadata": {
1409 | "description": "Specifies the resource tags."
1410 | }
1411 | }
1412 | },
1413 | "variables": {
1414 | "serviceBusDataOwnerRoleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '090c5cfd-751d-490a-894a-3ce6f1109419')]",
1415 | "acrPullRoleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]"
1416 | },
1417 | "resources": [
1418 | {
1419 | "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
1420 | "apiVersion": "2023-01-31",
1421 | "name": "[parameters('name')]",
1422 | "location": "[parameters('location')]",
1423 | "tags": "[parameters('tags')]"
1424 | },
1425 | {
1426 | "type": "Microsoft.Authorization/roleAssignments",
1427 | "apiVersion": "2022-04-01",
1428 | "scope": "[format('Microsoft.ServiceBus/namespaces/{0}', parameters('serviceBusNamespace'))]",
1429 | "name": "[guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), resourceId('Microsoft.ServiceBus/namespaces', parameters('serviceBusNamespace')), variables('serviceBusDataOwnerRoleDefinitionId'))]",
1430 | "properties": {
1431 | "roleDefinitionId": "[variables('serviceBusDataOwnerRoleDefinitionId')]",
1432 | "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').principalId]",
1433 | "principalType": "ServicePrincipal"
1434 | },
1435 | "dependsOn": [
1436 | "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]"
1437 | ]
1438 | },
1439 | {
1440 | "type": "Microsoft.Authorization/roleAssignments",
1441 | "apiVersion": "2022-04-01",
1442 | "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('acrName'))]",
1443 | "name": "[guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName')), variables('acrPullRoleDefinitionId'))]",
1444 | "properties": {
1445 | "roleDefinitionId": "[variables('acrPullRoleDefinitionId')]",
1446 | "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').principalId]",
1447 | "principalType": "ServicePrincipal"
1448 | },
1449 | "dependsOn": [
1450 | "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]"
1451 | ]
1452 | }
1453 | ],
1454 | "outputs": {
1455 | "id": {
1456 | "type": "string",
1457 | "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]"
1458 | },
1459 | "name": {
1460 | "type": "string",
1461 | "value": "[parameters('name')]"
1462 | },
1463 | "clientId": {
1464 | "type": "string",
1465 | "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').clientId]"
1466 | },
1467 | "principalId": {
1468 | "type": "string",
1469 | "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').principalId]"
1470 | }
1471 | }
1472 | }
1473 | },
1474 | "dependsOn": [
1475 | "[resourceId('Microsoft.Resources/deployments', 'containerRegistry')]",
1476 | "[resourceId('Microsoft.Resources/deployments', 'namespace')]"
1477 | ]
1478 | },
1479 | {
1480 | "type": "Microsoft.Resources/deployments",
1481 | "apiVersion": "2022-09-01",
1482 | "name": "containerRegistry",
1483 | "properties": {
1484 | "expressionEvaluationOptions": {
1485 | "scope": "inner"
1486 | },
1487 | "mode": "Incremental",
1488 | "parameters": {
1489 | "name": {
1490 | "value": "[parameters('acrName')]"
1491 | },
1492 | "sku": {
1493 | "value": "[parameters('acrSkuName')]"
1494 | },
1495 | "adminUserEnabled": {
1496 | "value": "[parameters('acrAdminUserEnabled')]"
1497 | },
1498 | "workspaceId": {
1499 | "value": "[reference(resourceId('Microsoft.Resources/deployments', 'workspace'), '2022-09-01').outputs.id.value]"
1500 | },
1501 | "retentionInDays": {
1502 | "value": "[parameters('logAnalyticsRetentionInDays')]"
1503 | },
1504 | "location": {
1505 | "value": "[parameters('location')]"
1506 | },
1507 | "tags": {
1508 | "value": "[parameters('tags')]"
1509 | }
1510 | },
1511 | "template": {
1512 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
1513 | "contentVersion": "1.0.0.0",
1514 | "metadata": {
1515 | "_generator": {
1516 | "name": "bicep",
1517 | "version": "0.18.4.5664",
1518 | "templateHash": "13018784895517120517"
1519 | }
1520 | },
1521 | "parameters": {
1522 | "name": {
1523 | "type": "string",
1524 | "defaultValue": "[format('acr{0}', uniqueString(resourceGroup().id))]",
1525 | "maxLength": 50,
1526 | "minLength": 5,
1527 | "metadata": {
1528 | "description": "Name of your Azure Container Registry"
1529 | }
1530 | },
1531 | "adminUserEnabled": {
1532 | "type": "bool",
1533 | "defaultValue": false,
1534 | "metadata": {
1535 | "description": "Enable admin user that have push / pull permission to the registry."
1536 | }
1537 | },
1538 | "sku": {
1539 | "type": "string",
1540 | "defaultValue": "Premium",
1541 | "allowedValues": [
1542 | "Basic",
1543 | "Standard",
1544 | "Premium"
1545 | ],
1546 | "metadata": {
1547 | "description": "Tier of your Azure Container Registry."
1548 | }
1549 | },
1550 | "workspaceId": {
1551 | "type": "string",
1552 | "metadata": {
1553 | "description": "Specifies the resource id of the Log Analytics workspace."
1554 | }
1555 | },
1556 | "retentionInDays": {
1557 | "type": "int",
1558 | "defaultValue": 60,
1559 | "metadata": {
1560 | "description": "Specifies the workspace data retention in days."
1561 | }
1562 | },
1563 | "location": {
1564 | "type": "string",
1565 | "defaultValue": "[resourceGroup().location]",
1566 | "metadata": {
1567 | "description": "Specifies the location."
1568 | }
1569 | },
1570 | "tags": {
1571 | "type": "object",
1572 | "metadata": {
1573 | "description": "Specifies the resource tags."
1574 | }
1575 | }
1576 | },
1577 | "variables": {
1578 | "copy": [
1579 | {
1580 | "name": "logs",
1581 | "count": "[length(variables('logCategories'))]",
1582 | "input": {
1583 | "category": "[variables('logCategories')[copyIndex('logs')]]",
1584 | "enabled": true,
1585 | "retentionPolicy": {
1586 | "enabled": true,
1587 | "days": "[parameters('retentionInDays')]"
1588 | }
1589 | }
1590 | },
1591 | {
1592 | "name": "metrics",
1593 | "count": "[length(variables('metricCategories'))]",
1594 | "input": {
1595 | "category": "[variables('metricCategories')[copyIndex('metrics')]]",
1596 | "enabled": true,
1597 | "retentionPolicy": {
1598 | "enabled": true,
1599 | "days": "[parameters('retentionInDays')]"
1600 | }
1601 | }
1602 | }
1603 | ],
1604 | "diagnosticSettingsName": "diagnosticSettings",
1605 | "logCategories": [
1606 | "ContainerRegistryRepositoryEvents",
1607 | "ContainerRegistryLoginEvents"
1608 | ],
1609 | "metricCategories": [
1610 | "AllMetrics"
1611 | ]
1612 | },
1613 | "resources": [
1614 | {
1615 | "type": "Microsoft.ContainerRegistry/registries",
1616 | "apiVersion": "2021-12-01-preview",
1617 | "name": "[parameters('name')]",
1618 | "location": "[parameters('location')]",
1619 | "tags": "[parameters('tags')]",
1620 | "sku": {
1621 | "name": "[parameters('sku')]"
1622 | },
1623 | "properties": {
1624 | "adminUserEnabled": "[parameters('adminUserEnabled')]"
1625 | }
1626 | },
1627 | {
1628 | "type": "Microsoft.Insights/diagnosticSettings",
1629 | "apiVersion": "2021-05-01-preview",
1630 | "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]",
1631 | "name": "[variables('diagnosticSettingsName')]",
1632 | "properties": {
1633 | "workspaceId": "[parameters('workspaceId')]",
1634 | "logs": "[variables('logs')]",
1635 | "metrics": "[variables('metrics')]"
1636 | },
1637 | "dependsOn": [
1638 | "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]"
1639 | ]
1640 | }
1641 | ],
1642 | "outputs": {
1643 | "id": {
1644 | "type": "string",
1645 | "value": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]"
1646 | },
1647 | "name": {
1648 | "type": "string",
1649 | "value": "[parameters('name')]"
1650 | }
1651 | }
1652 | }
1653 | },
1654 | "dependsOn": [
1655 | "[resourceId('Microsoft.Resources/deployments', 'workspace')]"
1656 | ]
1657 | }
1658 | ]
1659 | }
--------------------------------------------------------------------------------
/bicep/infra/main.parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | }
6 | }
--------------------------------------------------------------------------------
/bicep/infra/managed-identity.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Specifies the name of the user-defined managed identity.')
3 | param name string
4 |
5 | @description('Specifies the name of the Service Bus namespace.')
6 | param serviceBusNamespace string
7 |
8 | @description('Specifies the name of the Azure Container Registry')
9 | param acrName string
10 |
11 | @description('Specifies the location.')
12 | param location string = resourceGroup().location
13 |
14 | @description('Specifies the resource tags.')
15 | param tags object
16 |
17 | // Variables
18 | var serviceBusDataOwnerRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '090c5cfd-751d-490a-894a-3ce6f1109419')
19 | var acrPullRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
20 |
21 | // Existing Resources
22 | resource namespace 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' existing= {
23 | name: serviceBusNamespace
24 | }
25 |
26 | resource acr 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = {
27 | name: acrName
28 | }
29 |
30 | // Managed Identity
31 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
32 | name: name
33 | location: location
34 | tags: tags
35 | }
36 |
37 | // Role assignments
38 | resource serviceBusDataOwnerRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
39 | name: guid(managedIdentity.id, namespace.id, serviceBusDataOwnerRoleDefinitionId)
40 | scope: namespace
41 | properties: {
42 | roleDefinitionId: serviceBusDataOwnerRoleDefinitionId
43 | principalId: managedIdentity.properties.principalId
44 | principalType: 'ServicePrincipal'
45 | }
46 | }
47 |
48 | resource acrPullRoleDefinitionAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
49 | name: guid(managedIdentity.id, acr.id, acrPullRoleDefinitionId)
50 | scope: acr
51 | properties: {
52 | roleDefinitionId: acrPullRoleDefinitionId
53 | principalId: managedIdentity.properties.principalId
54 | principalType: 'ServicePrincipal'
55 | }
56 | }
57 |
58 | // Outputs
59 | output id string = managedIdentity.id
60 | output name string = managedIdentity.name
61 | output clientId string = managedIdentity.properties.clientId
62 | output principalId string = managedIdentity.properties.principalId
63 |
--------------------------------------------------------------------------------
/bicep/infra/service-bus.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Specifies the name of the Service Bus namespace.')
3 | param name string
4 |
5 | @description('Enabling this property creates a Premium Service Bus Namespace in regions supported availability zones.')
6 | param zoneRedundant bool = true
7 |
8 | @description('Specifies the name of Service Bus namespace SKU.')
9 | @allowed([
10 | 'Basic'
11 | 'Premium'
12 | 'Standard'
13 | ])
14 | param skuName string = 'Standard'
15 |
16 | @description('Specifies the messaging units for the Service Bus namespace. For Premium tier, capacity are 1,2 and 4.')
17 | param capacity int = 1
18 |
19 | @description('Specifies the name of the Service Bus queue.')
20 | param queueNames array = []
21 |
22 | @description('Specifies the lock duration of the queue.')
23 | param lockDuration string = 'PT5M'
24 |
25 | @description('Specifies the maximum delivery count of the queue.')
26 | param maxDeliveryCount int = 10
27 |
28 | @description('Specifies whether duplication is enabled on the queue.')
29 | param requiresDuplicateDetection bool = false
30 |
31 | @description('Specifies whether session is enabled on the queue.')
32 | param requiresSession bool = false
33 |
34 | @description('Specifies whether dead lettering is enabled on the queue.')
35 | param deadLetteringOnMessageExpiration bool = false
36 |
37 | @description('Specifies the duplicate detection history time of the queue.')
38 | param duplicateDetectionHistoryTimeWindow string = 'PT10M'
39 |
40 | @description('Specifies the resource id of the Log Analytics workspace.')
41 | param workspaceId string
42 |
43 | @description('Specifies the workspace data retention in days.')
44 | param retentionInDays int = 60
45 |
46 | @description('Specifies the location.')
47 | param location string = resourceGroup().location
48 |
49 | @description('Specifies the resource tags.')
50 | param tags object
51 |
52 | // Variables
53 | var diagnosticSettingsName = 'diagnosticSettings'
54 | var logCategories = [
55 | 'OperationalLogs'
56 | 'VNetAndIPFilteringLogs'
57 | 'RuntimeAuditLogs'
58 | 'ApplicationMetricsLogs'
59 | ]
60 | var metricCategories = [
61 | 'AllMetrics'
62 | ]
63 | var logs = [for category in logCategories: {
64 | category: category
65 | enabled: true
66 | retentionPolicy: {
67 | enabled: true
68 | days: retentionInDays
69 | }
70 | }]
71 | var metrics = [for category in metricCategories: {
72 | category: category
73 | enabled: true
74 | retentionPolicy: {
75 | enabled: true
76 | days: retentionInDays
77 | }
78 | }]
79 |
80 | var serviceBusEndpoint = '${namespace.id}/AuthorizationRules/RootManageSharedAccessKey'
81 | var serviceBusConnectionString = listKeys(serviceBusEndpoint, namespace.apiVersion).primaryConnectionString
82 |
83 | // Resources
84 | resource namespace 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' = {
85 | name: name
86 | location: location
87 | tags: tags
88 | sku: {
89 | name: skuName
90 | capacity: capacity
91 | }
92 | properties: {
93 | zoneRedundant: skuName == 'Premium' ? zoneRedundant : false
94 | }
95 | }
96 |
97 | resource queue 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = [for queueName in queueNames: {
98 | parent: namespace
99 | name: queueName
100 | properties: {
101 | lockDuration: lockDuration
102 | maxSizeInMegabytes: 1024
103 | requiresDuplicateDetection: requiresDuplicateDetection
104 | requiresSession: requiresSession
105 | deadLetteringOnMessageExpiration: deadLetteringOnMessageExpiration
106 | duplicateDetectionHistoryTimeWindow: duplicateDetectionHistoryTimeWindow
107 | maxDeliveryCount: maxDeliveryCount
108 | }
109 | }]
110 |
111 | resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
112 | name: diagnosticSettingsName
113 | scope: namespace
114 | properties: {
115 | workspaceId: workspaceId
116 | logs: logs
117 | metrics: metrics
118 | }
119 | }
120 |
121 | // Outputs
122 | output id string = namespace.id
123 | output name string = namespace.name
124 | output connectionString string = serviceBusConnectionString
125 | output queues array = [for (queueName, i) in queueNames: {
126 | name: name
127 | id: queue[i].id
128 | }]
129 |
--------------------------------------------------------------------------------
/bicep/infra/virtual-network.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Specifies the name of the Azure Container Apps Environment.')
3 | param name string
4 |
5 | @description('Specifies the address prefixes of the virtual network.')
6 | param addressPrefixes string = '10.0.0.0/8'
7 |
8 | @description('Specifies the name of a subnet for the Azure Container Apps environment infrastructure components. This subnet must be in the same VNET as the subnet defined in runtimeSubnetId. Must not overlap with any other provided IP ranges.')
9 | param infrastructureSubnetName string = 'InfrastructureSubnet'
10 |
11 | @description('Specifies the address prefix of a subnet for the Azure Container Apps environment infrastructure components. This subnet must be in the same VNET as the subnet defined in runtimeSubnetId. Must not overlap with any other provided IP ranges.')
12 | param infrastructureSubnetAddressPrefix string = '10.0.0.0/21'
13 |
14 | @description('Specifies the name of the subnet hosting the worker nodes of the user agent pool of the AKS cluster.')
15 | param privateLinkServiceSubnetName string = 'PlsSubnet'
16 |
17 | @description('Specifies the address prefix of the subnet hosting the worker nodes of the user agent pool of the AKS cluster.')
18 | param privateLinkServiceSubnetAddressPrefix string = '10.0.16.0/21'
19 |
20 | @description('Specifies whether enabling the private link to the Azure Service Bus namespace.')
21 | param serviceBusNamespacePrivateEndpointEnabled bool = false
22 |
23 | @description('Specifies the name of the private link to the Azure Service Bus namespace.')
24 | param serviceBusNamespacePrivateEndpointName string = 'ServiceBusNamespacePrivateEndpoint'
25 |
26 | @description('Specifies the resource id of the Azure Service Bus namespace.')
27 | param serviceBusNamespaceId string
28 |
29 | @description('Specifies whether enabling the private link to the Azure Container Registry.')
30 | param acrPrivateEndpointEnabled bool = false
31 |
32 | @description('Specifies the name of the private link to the Azure Container Registry.')
33 | param acrPrivateEndpointName string = 'AcrPrivateEndpoint'
34 |
35 | @description('Specifies the resource id of the Azure Container Registry.')
36 | param acrId string
37 |
38 | @description('Specifies the resource id of the Log Analytics workspace.')
39 | param workspaceId string
40 |
41 | @description('Specifies the workspace data retention in days.')
42 | param retentionInDays int = 60
43 |
44 | @description('Specifies the location.')
45 | param location string = resourceGroup().location
46 |
47 | @description('Specifies the resource tags.')
48 | param tags object
49 |
50 | // Variables
51 | var diagnosticSettingsName = 'diagnosticSettings'
52 | var vnetLogCategories = [
53 | 'VMProtectionAlerts'
54 | ]
55 | var vnetMetricCategories = [
56 | 'AllMetrics'
57 | ]
58 | var vnetLogs = [for category in vnetLogCategories: {
59 | category: category
60 | enabled: true
61 | retentionPolicy: {
62 | enabled: true
63 | days: retentionInDays
64 | }
65 | }]
66 | var vnetMetrics = [for category in vnetMetricCategories: {
67 | category: category
68 | enabled: true
69 | retentionPolicy: {
70 | enabled: true
71 | days: retentionInDays
72 | }
73 | }]
74 |
75 | // Resources
76 | resource vnet 'Microsoft.Network/virtualNetworks@2022-07-01' = {
77 | name: name
78 | location: location
79 | tags: tags
80 | properties: {
81 | addressSpace: {
82 | addressPrefixes: [
83 | addressPrefixes
84 | ]
85 | }
86 | subnets: [
87 | {
88 | name: infrastructureSubnetName
89 | properties: {
90 | addressPrefix: infrastructureSubnetAddressPrefix
91 | delegations: []
92 | privateEndpointNetworkPolicies: 'Disabled'
93 | privateLinkServiceNetworkPolicies: 'Enabled'
94 | }
95 | }
96 | {
97 | name: privateLinkServiceSubnetName
98 | properties: {
99 | addressPrefix: privateLinkServiceSubnetAddressPrefix
100 | delegations: []
101 | privateEndpointNetworkPolicies: 'Disabled'
102 | privateLinkServiceNetworkPolicies: 'Disabled'
103 | }
104 | }
105 | ]
106 | virtualNetworkPeerings: []
107 | enableDdosProtection: false
108 | }
109 | }
110 |
111 | // Private DNS Zones
112 | resource serviceBusPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (serviceBusNamespacePrivateEndpointEnabled) {
113 | name: 'privatelink.${toLower(environment().name) == 'azureusgovernment' ? 'servicebus.usgovcloudapi.net' : 'servicebus.windows.net'}'
114 | location: 'global'
115 | tags: tags
116 | }
117 |
118 | resource acrPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (acrPrivateEndpointEnabled) {
119 | name: 'privatelink.${toLower(environment().name) == 'azureusgovernment' ? 'azurecr.us' : 'azurecr.io'}'
120 | location: 'global'
121 | tags: tags
122 | }
123 |
124 | // Virtual Network Links
125 | resource serviceBusPrivateDnsZoneVirtualNetworkLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = if (serviceBusNamespacePrivateEndpointEnabled) {
126 | parent:serviceBusPrivateDnsZone
127 | name: 'link_to_${toLower(name)}'
128 | location: 'global'
129 | properties: {
130 | registrationEnabled: false
131 | virtualNetwork: {
132 | id: vnet.id
133 | }
134 | }
135 | }
136 |
137 | resource acrPrivateDnsZoneVirtualNetworkLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = if (acrPrivateEndpointEnabled) {
138 | parent: acrPrivateDnsZone
139 | name: 'link_to_${toLower(name)}'
140 | location: 'global'
141 | properties: {
142 | registrationEnabled: false
143 | virtualNetwork: {
144 | id: vnet.id
145 | }
146 | }
147 | }
148 |
149 | // Private Endpoints and Private DNS Zone Groups
150 | resource serviceBusNamespacePrivateEndpoint 'Microsoft.Network/privateEndpoints@2021-08-01' = if (serviceBusNamespacePrivateEndpointEnabled) {
151 | name: serviceBusNamespacePrivateEndpointName
152 | location: location
153 | tags: tags
154 | properties: {
155 | privateLinkServiceConnections: [
156 | {
157 | name: serviceBusNamespacePrivateEndpointName
158 | properties: {
159 | privateLinkServiceId: serviceBusNamespaceId
160 | groupIds: [
161 | 'namespace'
162 | ]
163 | }
164 | }
165 | ]
166 | subnet: {
167 | id: '${vnet.id}/subnets/${privateLinkServiceSubnetName}'
168 | }
169 | }
170 | }
171 |
172 | resource serviceBusNamespacePrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-08-01' = if (serviceBusNamespacePrivateEndpointEnabled) {
173 | parent: serviceBusNamespacePrivateEndpoint
174 | name: 'PrivateDnsZoneGroupName'
175 | properties: {
176 | privateDnsZoneConfigs: [
177 | {
178 | name: 'dnsConfig'
179 | properties: {
180 | privateDnsZoneId: serviceBusPrivateDnsZone.id
181 | }
182 | }
183 | ]
184 | }
185 | }
186 |
187 | resource acrPrivateEndpoint 'Microsoft.Network/privateEndpoints@2022-09-01' = if (acrPrivateEndpointEnabled) {
188 | name: acrPrivateEndpointName
189 | location: location
190 | tags: tags
191 | properties: {
192 | privateLinkServiceConnections: [
193 | {
194 | name: acrPrivateEndpointName
195 | properties: {
196 | privateLinkServiceId: acrId
197 | groupIds: [
198 | 'registry'
199 | ]
200 | }
201 | }
202 | ]
203 | subnet: {
204 | id: '${vnet.id}/subnets/${privateLinkServiceSubnetName}'
205 | }
206 | }
207 | }
208 |
209 | resource acrPrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-09-01' = if (acrPrivateEndpointEnabled) {
210 | parent: acrPrivateEndpoint
211 | name: 'acrPrivateDnsZoneGroup'
212 | properties: {
213 | privateDnsZoneConfigs: [
214 | {
215 | name: 'dnsConfig'
216 | properties: {
217 | privateDnsZoneId: acrPrivateDnsZone.id
218 | }
219 | }
220 | ]
221 | }
222 | }
223 |
224 | // Diagnostic Settings
225 | resource vnetDiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
226 | name: diagnosticSettingsName
227 | scope: vnet
228 | properties: {
229 | workspaceId: workspaceId
230 | logs: vnetLogs
231 | metrics: vnetMetrics
232 | }
233 | }
234 |
235 | // Outputs
236 | // Outputs
237 | output id string = vnet.id
238 | output name string = vnet.name
239 | output infrastructureSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, infrastructureSubnetName)
240 | output privateLinkServiceSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, privateLinkServiceSubnetName)
241 |
--------------------------------------------------------------------------------
/bicep/jobs/container-apps-job.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Specifies the name of the Azure Container Apps Job.')
3 | param name string
4 |
5 | @description('Specifies the location.')
6 | param location string = resourceGroup().location
7 |
8 | @description('Specifies the resource tags.')
9 | param tags object
10 |
11 | @description('Specifies the resource id of the user-defined managed identity.')
12 | param managedIdentityId string
13 |
14 | @description('Specifies the resource id of the Azure Container Apps Environment.')
15 | param environmentId string
16 |
17 | @description('Specifies a workload profile name to pin for container app execution.')
18 | param workloadProfileName string = ''
19 |
20 | @description('Specifies a list of volume definitions for the Container App.')
21 | param volumes array = []
22 |
23 | @description('Specifies the Collection of private container registry credentials used by a Container apps job. For more information, see https://learn.microsoft.com/en-us/azure/templates/microsoft.app/jobs?pivots=deployment-language-bicep#registrycredentials')
24 | param registries array = []
25 |
26 | @description('Specifies the maximum number of retries before failing the job.')
27 | param replicaRetryLimit int = 1
28 |
29 | @description('Specifies the maximum number of seconds a Container Apps job replica is allowed to run.')
30 | param replicaTimeout int = 60
31 |
32 | @description('Specifies the Collection of secrets used by a Container Apps job. For more information, see https://learn.microsoft.com/en-us/azure/templates/microsoft.app/jobs?pivots=deployment-language-bicep#secret')
33 | param secrets array = []
34 |
35 | @description('Specified the trigger type for the Azure Container Apps Job.')
36 | @allowed([
37 | 'Event'
38 | 'Manual'
39 | 'Schedule'
40 | ])
41 | param triggerType string = 'Manual'
42 |
43 | @description('Specifies the Cron formatted repeating schedule ("* * * * *") of a Cron Job.')
44 | param cronExpression string = '* * * * *'
45 |
46 | @description('Specifies the number of parallel replicas of a Container Apps job that can run at a given time.')
47 | param parallelism int = 1
48 |
49 | @description('Specifies the minimum number of successful replica completions before overall Container Apps job completion.')
50 | param replicaCompletionCount int = 1
51 |
52 | @description('Specifies the minimum number of job executions to run per polling interval.')
53 | param minExecutions int = 1
54 |
55 | @description('Specifies the maximum number of job executions to run per polling interval.')
56 | param maxExecutions int = 10
57 |
58 | @description('Specifies the polling interval in seconds.')
59 | param pollingInterval int = 60
60 |
61 | @description('Specifies the scaling rules. In event-driven jobs, each Container Apps job typically processes a single event, and a scaling rule determines the number of job replicas to run.')
62 | param rules array = []
63 |
64 | @description('Specifies the container start command arguments.')
65 | param args array = []
66 |
67 | @description('Specifies the container start command.')
68 | param command array = []
69 |
70 | @description('Specifies the container environment variables.')
71 | param env array = []
72 |
73 | @description('Specifies the container image.')
74 | param containerImage string
75 |
76 | @description('Specifies the container name.')
77 | param containerName string = 'main'
78 |
79 | @description('Specifies the Required CPU in cores, e.g. 0.5 for the first Azure Container Apps Job. Specify a decimal value as a string. For example, specify 0.5 for 1/2 core.')
80 | param cpu string = '0.25'
81 |
82 | @description('Specifies the Required memory in gigabytes for the second Azure Container Apps Job. E.g. 1.5 Specify a decimal value as a string. For example, specify 1.5 for 1.5 GB.')
83 | param memory string = '0.5Gi'
84 |
85 | @description('Specifies the container volume mounts.')
86 | param volumeMounts array = []
87 |
88 | // Resources
89 | resource job 'Microsoft.App/jobs@2023-04-01-preview' = {
90 | name: toLower(name)
91 | location: location
92 | tags: tags
93 | identity: {
94 | type: 'UserAssigned'
95 | userAssignedIdentities: {
96 | '${managedIdentityId}': {}
97 | }
98 | }
99 | properties: {
100 | configuration: {
101 | manualTriggerConfig: triggerType == 'Manual' ? {
102 | replicaCompletionCount: replicaCompletionCount
103 | parallelism: parallelism
104 | } : null
105 | scheduleTriggerConfig: triggerType == 'Schedule' ? {
106 | cronExpression: cronExpression
107 | replicaCompletionCount: replicaCompletionCount
108 | parallelism: parallelism
109 | } : null
110 | eventTriggerConfig: triggerType == 'Event' ? {
111 | replicaCompletionCount: replicaCompletionCount
112 | parallelism: parallelism
113 | scale: {
114 | maxExecutions: maxExecutions
115 | minExecutions: minExecutions
116 | pollingInterval: pollingInterval
117 | rules: rules
118 | }
119 | } : null
120 | registries: registries
121 | replicaRetryLimit: replicaRetryLimit
122 | replicaTimeout: replicaTimeout
123 | secrets: secrets
124 | triggerType: triggerType
125 | }
126 | environmentId: environmentId
127 | template: {
128 | containers: [
129 | {
130 | args: args
131 | command: command
132 | env: env
133 | image: containerImage
134 | name: containerName
135 | resources: {
136 | cpu: json(cpu)
137 | memory: memory
138 | }
139 | volumeMounts: volumeMounts
140 | }
141 | ]
142 | volumes: volumes
143 | }
144 | workloadProfileName: workloadProfileName
145 | }
146 | }
147 |
148 | // Outputs
149 | output id string = job.id
150 | output name string = job.name
151 |
--------------------------------------------------------------------------------
/bicep/jobs/container-registry.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Specifies the name of the Azure Container Registry.')
3 | param name string
4 |
5 | @description('Specifies the principal id of the user-defined managed identity.')
6 | param managedIdentityPrincipalId string
7 |
8 | // Variables
9 | var acrPullRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
10 |
11 | // Resources
12 | resource acr 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = {
13 | name: name
14 | }
15 |
16 | resource acrPullRoleDefinitionAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
17 | name: guid(managedIdentityPrincipalId, acr.id, acrPullRoleDefinitionId)
18 | scope: acr
19 | properties: {
20 | roleDefinitionId: acrPullRoleDefinitionId
21 | principalId: managedIdentityPrincipalId
22 | principalType: 'ServicePrincipal'
23 | }
24 | }
25 |
26 | //Outputs
27 | output id string = acr.id
28 | output name string = acr.name
29 | output loginServer string = acr.properties.loginServer
30 |
--------------------------------------------------------------------------------
/bicep/jobs/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Variables
4 | location='northeurope'
5 | deploymentName='main'
6 | prefix='Gundam'
7 | resourceGroupName="${prefix}RG"
8 |
9 | # Commands
10 | validateTemplate=1
11 | useWhatIf=1
12 | deploy=1
13 |
14 | # Template
15 | template="main.bicep"
16 | parameters="main.parameters.json"
17 |
18 | # Subscription id, subscription name, and tenant id of the current subscription
19 | subscriptionId=$(az account show --query id --output tsv)
20 | subscriptionName=$(az account show --query name --output tsv)
21 | tenantId=$(az account show --query tenantId --output tsv)
22 |
23 | # Check if the resource group already exists
24 | echo "Checking if [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription..."
25 |
26 | az group show --name $resourceGroupName &>/dev/null
27 |
28 | if [[ $? != 0 ]]; then
29 | echo "No [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription"
30 | echo "Creating [$resourceGroupName] resource group in the [$subscriptionName] subscription..."
31 |
32 | # Create the resource group
33 | az group create --name $resourceGroupName --location $location 1>/dev/null
34 |
35 | if [[ $? == 0 ]]; then
36 | echo "[$resourceGroupName] resource group successfully created in the [$subscriptionName] subscription"
37 | else
38 | echo "Failed to create [$resourceGroupName] resource group in the [$subscriptionName] subscription"
39 | exit
40 | fi
41 | else
42 | echo "[$resourceGroupName] resource group already exists in the [$subscriptionName] subscription"
43 | fi
44 |
45 | # Validate the Bicep template
46 | if [[ $validateTemplate == 1 ]]; then
47 | if [[ $useWhatIf == 1 ]]; then
48 | # Execute a deployment What-If operation at resource group scope.
49 | echo "Previewing changes deployed by [$template] Bicep template..."
50 | az deployment group what-if \
51 | --resource-group $resourceGroupName \
52 | --template-file $template \
53 | --no-pretty-print \
54 | --output jsonc \
55 | --parameters $parameters \
56 | --parameters location=$location \
57 | prefix=$prefix
58 |
59 | if [[ $? == 0 ]]; then
60 | echo "[$template] Bicep template validation succeeded"
61 | else
62 | echo "Failed to validate [$template] Bicep template"
63 | exit
64 | fi
65 | else
66 | # Validate the Bicep template
67 | echo "Validating [$template] Bicep template..."
68 | output=$(az deployment group validate \
69 | --resource-group $resourceGroupName \
70 | --template-file $template \
71 | --output jsonc \
72 | --parameters $parameters \
73 | --parameters location=$location \
74 | prefix=$prefix)
75 |
76 | if [[ $? == 0 ]]; then
77 | echo "[$template] Bicep template validation succeeded"
78 | else
79 | echo "Failed to validate [$template] Bicep template"
80 | echo $output
81 | exit
82 | fi
83 | fi
84 | fi
85 |
86 | # Deploy the Bicep template
87 | if [[ $deploy == 1 ]]; then
88 | az deployment group create \
89 | --name $deploymentName \
90 | --resource-group $resourceGroupName \
91 | --template-file $template \
92 | --parameters $parameters \
93 | --parameters location=$location \
94 | prefix=$prefix 1>/dev/null
95 |
96 | if [[ $? == 0 ]]; then
97 | echo "[$deploymentName] deployment successfully created in the [$resourceGroupName] resource group"
98 | else
99 | echo "Failed to create [$deploymentName] deployment in the [$resourceGroupName] resource group"
100 | exit -1
101 | fi
102 | fi
--------------------------------------------------------------------------------
/bicep/jobs/main.bicep:
--------------------------------------------------------------------------------
1 | // Parameters
2 | @description('Specifies the name prefix.')
3 | param prefix string = '$uniqueString(resourceGroup().id)'
4 |
5 | @description('Specifies whether name resources are in CamelCase, UpperCamelCase, or KebabCase.')
6 | @allowed([
7 | 'CamelCase'
8 | 'UpperCamelCase'
9 | 'KebabCase'
10 | ])
11 | param letterCaseType string = 'UpperCamelCase'
12 |
13 | @description('Specifies the location.')
14 | param location string = resourceGroup().location
15 |
16 | @description('Specifies the resource tags.')
17 | param tags object = {
18 | IaC: 'Bicep'
19 | Demo: 'Azure Container Apps Jobs'
20 | }
21 |
22 | @description('Specifies the name of the Azure Container Apps Environment.')
23 | param containerAppEnvironmentName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}Environment' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}Environment' : '${toLower(prefix)}-environment'
24 |
25 | @description('Specifies the Azure Container Registry name.')
26 | param acrName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}Acr' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}Acr' : '${toLower(prefix)}-acr'
27 |
28 | @description('Specifies the name of the Service Bus namespace.')
29 | param serviceBusNamespace string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}ServiceBus' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}ServiceBus' : '${toLower(prefix)}-servicebus'
30 |
31 | @description('Specifies the name of the user-defined managed identity.')
32 | param managedIdentityName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}JobManagedIdentity' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}JobManagedIdentity' : '${toLower(prefix)}-job-managed-identity'
33 |
34 | @description('Specifies the name of the parameters Azure Service Bus queue.')
35 | param parametersServiceBusQueueName string = 'parameters'
36 |
37 | @description('Specifies the name of the results Azure Service Bus queue.')
38 | param resultsServiceBusQueueName string = 'results'
39 |
40 | @description('Specifies the name of the sender job.')
41 | param senderJobName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}Sender' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}Sender' : '${toLower(prefix)}-sender'
42 |
43 | @description('Specifies the name of the processor job.')
44 | param processorJobName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}Processor' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}Processor' : '${toLower(prefix)}-processor'
45 |
46 | @description('Specifies the name of the receiver job.')
47 | param receiverJobName string = letterCaseType == 'UpperCamelCase' ? '${toUpper(first(prefix))}${toLower(substring(prefix, 1, length(prefix) - 1))}Receiver' : letterCaseType == 'CamelCase' ? '${toLower(prefix)}Receiver' : '${toLower(prefix)}-receiver'
48 |
49 | @description('Specifies the name (e.g., sbsender) of the container image of the sender job.')
50 | param senderImageName string = 'sbsender'
51 |
52 | @description('Specifies the name (e.g., sbprocessor) of the container image of the processor job.')
53 | param processorImageName string = 'sbprocessor'
54 |
55 | @description('Specifies the name (e.g., sbreceiver) of the container image of the receiver job.')
56 | param receiverImageName string = 'sbreceiver'
57 |
58 | @description('Specifies the tag (e.g., v1) of the container image of the sender job.')
59 | param senderImageTag string = 'v1'
60 |
61 | @description('Specifies the tag (e.g., v1) of the container image of the processor job.')
62 | param processorImageTag string = 'v1'
63 |
64 | @description('Specifies the tag (e.g., v1) of the container image of the receiver job.')
65 | param receiverImageTag string = 'v1'
66 |
67 | @description('Maximum number of replicas of the sender job to run per execution.')
68 | param senderParallelism int = 1
69 |
70 | @description('Maximum number of replicas of the sender job to run per execution.')
71 | param processorParallelism int = 5
72 |
73 | @description('Maximum number of replicas of the sender job to run per execution.')
74 | param receiverParallelism int = 5
75 |
76 | @description('Specifies the minimum number of job executions to run per polling interval.')
77 | param minExecutions int = 1
78 |
79 | @description('Specifies the maximum number of job executions to run per polling interval.')
80 | param maxExecutions int = 10
81 |
82 | @description('Specifies the polling interval in seconds.')
83 | param pollingInterval int = 30
84 |
85 | @description('Specifies the maximum number of retries before the replica fails..')
86 | param replicaRetryLimit int = 1
87 |
88 | @description('Specifies the maximum number of seconds a replica can execute.')
89 | param replicaTimeout int = 300
90 |
91 | // Existing Resources
92 | resource environment 'Microsoft.App/managedEnvironments@2023-04-01-preview' existing = {
93 | name: containerAppEnvironmentName
94 | }
95 |
96 | resource acr 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = {
97 | name: acrName
98 | }
99 |
100 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
101 | name: managedIdentityName
102 | }
103 |
104 | resource namespace 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' existing = {
105 | name: serviceBusNamespace
106 | }
107 |
108 | // Variables
109 | var serviceBusEndpoint = '${namespace.id}/AuthorizationRules/RootManageSharedAccessKey'
110 | var serviceBusConnectionString = listKeys(serviceBusEndpoint, namespace.apiVersion).primaryConnectionString
111 |
112 | // Modules
113 | module senderJob 'container-apps-job.bicep' = {
114 | name: 'senderJob'
115 | params: {
116 | name: toLower(senderJobName)
117 | location: location
118 | managedIdentityId: managedIdentity.id
119 | containerImage: '${acr.properties.loginServer}/${senderImageName}:${senderImageTag}'
120 | triggerType: 'Manual'
121 | parallelism: senderParallelism
122 | replicaCompletionCount: senderParallelism
123 | replicaRetryLimit: replicaRetryLimit
124 | replicaTimeout: replicaTimeout
125 | environmentId: environment.id
126 | tags: tags
127 | registries: [
128 | {
129 | server: acr.properties.loginServer
130 | identity: managedIdentity.id
131 | }
132 | ]
133 | env: [
134 | {
135 | name: 'AZURE_CLIENT_ID'
136 | value: managedIdentity.properties.clientId
137 | }
138 | {
139 | name: 'FULLY_QUALIFIED_NAMESPACE'
140 | value: toLower('${serviceBusNamespace}.servicebus.windows.net')
141 | }
142 | {
143 | name: 'INPUT_QUEUE_NAME'
144 | value: parametersServiceBusQueueName
145 | }
146 | {
147 | name: 'MIN_NUMBER'
148 | value: '1'
149 | }
150 | {
151 | name: 'MAX_NUMBER'
152 | value: '10'
153 | }
154 | {
155 | name: 'MESSAGE_COUNT'
156 | value: '100'
157 | }
158 | {
159 | name: 'SEND_TYPE'
160 | value: 'list'
161 | }
162 | ]
163 | }
164 | }
165 |
166 | module processorJob 'container-apps-job.bicep' = {
167 | name: 'processorJob'
168 | params: {
169 | name: processorJobName
170 | location: location
171 | managedIdentityId: managedIdentity.id
172 | containerImage: '${acr.properties.loginServer}/${processorImageName}:${processorImageTag}'
173 | triggerType: 'Schedule'
174 | cronExpression: '*/5 * * * *'
175 | parallelism: processorParallelism
176 | replicaCompletionCount: processorParallelism
177 | replicaRetryLimit: replicaRetryLimit
178 | replicaTimeout: replicaTimeout
179 | environmentId: environment.id
180 | tags: tags
181 | registries: [
182 | {
183 | server: acr.properties.loginServer
184 | identity: managedIdentity.id
185 | }
186 | ]
187 | env: [
188 | {
189 | name: 'AZURE_CLIENT_ID'
190 | value: managedIdentity.properties.clientId
191 | }
192 | {
193 | name: 'FULLY_QUALIFIED_NAMESPACE'
194 | value: toLower('${serviceBusNamespace}.servicebus.windows.net')
195 | }
196 | {
197 | name: 'INPUT_QUEUE_NAME'
198 | value: parametersServiceBusQueueName
199 | }
200 | {
201 | name: 'OUTPUT_QUEUE_NAME'
202 | value: resultsServiceBusQueueName
203 | }
204 | {
205 | name: 'MAX_MESSAGE_COUNT'
206 | value: '20'
207 | }
208 | {
209 | name: 'MAX_WAIT_TIME'
210 | value: '5'
211 | }
212 | ]
213 | }
214 | }
215 |
216 | module receiverJob 'container-apps-job.bicep' = {
217 | name: 'receiverJob'
218 | params: {
219 | name: receiverJobName
220 | location: location
221 | managedIdentityId: managedIdentity.id
222 | containerImage: '${acr.properties.loginServer}/${receiverImageName}:${receiverImageTag}'
223 | triggerType: 'Event'
224 | maxExecutions: maxExecutions
225 | minExecutions: minExecutions
226 | pollingInterval: pollingInterval
227 | parallelism: receiverParallelism
228 | replicaCompletionCount: receiverParallelism
229 | replicaRetryLimit: replicaRetryLimit
230 | replicaTimeout: replicaTimeout
231 | environmentId: environment.id
232 | tags: tags
233 | registries: [
234 | {
235 | server: acr.properties.loginServer
236 | identity: managedIdentity.id
237 | }
238 | ]
239 | env: [
240 | {
241 | name: 'AZURE_CLIENT_ID'
242 | value: managedIdentity.properties.clientId
243 | }
244 | {
245 | name: 'FULLY_QUALIFIED_NAMESPACE'
246 | value: toLower('${serviceBusNamespace}.servicebus.windows.net')
247 | }
248 | {
249 | name: 'OUTPUT_QUEUE_NAME'
250 | value: resultsServiceBusQueueName
251 | }
252 | {
253 | name: 'MAX_MESSAGE_COUNT'
254 | value: '20'
255 | }
256 | {
257 | name: 'MAX_WAIT_TIME'
258 | value: '5'
259 | }
260 | ]
261 | secrets: [
262 | {
263 | name: 'service-bus-connection-string'
264 | value: serviceBusConnectionString
265 | }
266 | ]
267 | rules: [
268 | {
269 | name: 'azure-servicebus-queue-rule'
270 | type: 'azure-servicebus'
271 | metadata: {
272 | messageCount: '5'
273 | namespace: serviceBusNamespace
274 | queueName: resultsServiceBusQueueName
275 | }
276 | auth: [
277 | {
278 | secretRef: 'service-bus-connection-string'
279 | triggerParameter: 'connection'
280 | }
281 | ]
282 | }
283 | ]
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/bicep/jobs/main.parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | }
6 | }
--------------------------------------------------------------------------------
/images/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/container-apps-jobs/2d9e50554a1ad635a3eb3ac17136e80bdf6db500/images/architecture.png
--------------------------------------------------------------------------------
/images/executionhistory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/container-apps-jobs/2d9e50554a1ad635a3eb3ac17136e80bdf6db500/images/executionhistory.png
--------------------------------------------------------------------------------
/images/executionlogs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/container-apps-jobs/2d9e50554a1ad635a3eb3ac17136e80bdf6db500/images/executionlogs.png
--------------------------------------------------------------------------------
/images/joblogs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/container-apps-jobs/2d9e50554a1ad635a3eb3ac17136e80bdf6db500/images/joblogs.png
--------------------------------------------------------------------------------
/images/messageflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/container-apps-jobs/2d9e50554a1ad635a3eb3ac17136e80bdf6db500/images/messageflow.png
--------------------------------------------------------------------------------
/images/resources.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/container-apps-jobs/2d9e50554a1ad635a3eb3ac17136e80bdf6db500/images/resources.png
--------------------------------------------------------------------------------
/scripts/00-variables.sh:
--------------------------------------------------------------------------------
1 | # Variables
2 | prefix="Gundam"
3 | name="${prefix}Receiver"
4 | managedIdentityName="${prefix}JobManagedIdentity"
5 | serviceBusNamespace="${prefix}ServiceBus"
6 | resourceGroupName="${prefix}RG"
7 | environmentName="${prefix}Environment"
8 | imageName="paolosalvatori.azurecr.io/sbsender:v1"
9 | acrName="${prefix}Acr"
10 | replicaTimeout=300
11 | replicaRetryLimit=1
12 | cpu="0.25"
13 | memory="0.5Gi"
14 |
15 | # Queues
16 | inputQueueName="input"
17 | outputQueueName="output"
18 |
19 | # Sender Job
20 | senderJobName="${prefix,,}azcliSender"
21 | senderJobTriggerType="Manual"
22 | senderJobReplicaCompletionCount=1
23 | senderJobParallelism=1
24 | senderJobImageName="sbsender"
25 | senderJobImageTag="v1"
26 | senderJobMinNumber=1
27 | senderJobMaxNumber=10
28 | senderJobMessageCount=100
29 | senderJobSendType="list"
30 |
31 | # Processor Job
32 | processorJobName="${prefix,,}azcliprocessor"
33 | processorJobTriggerType="Schedule"
34 | processorJobReplicaCompletionCount=5
35 | processorJobParallelism=5
36 | processorJobCronExpression="*/5 * * * *"
37 | processorJobImageName="sbprocessor"
38 | processorJobImageTag="v1"
39 | processorMaxMessageCount=20
40 | processorMaxWaitTime=5
41 |
42 | # Receiver Job
43 | receiverJobName="${prefix,,}azclireceiver"
44 | receiverJobTriggerType="Event"
45 | receiverJobReplicaCompletionCount=5
46 | receiverJobParallelism=5
47 | receiverJobImageName="sbreceiver"
48 | receiverJobImageTag="v1"
49 | receiverMinExecutions=1
50 | receiverMaxExecutions=10
51 | receiverMaxMessageCount=20
52 | receiverMaxWaitTime=5
53 | receiverMessageCount=5
54 |
55 | # Bicep generated entities
56 | bicepSenderJobName="${prefix,,}sender"
57 | bicepProcessorJobName="${prefix,,}processor"
58 | bicepReceiverJobName="${prefix,,}receiver"
--------------------------------------------------------------------------------
/scripts/01-create-job.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Variables
4 | source ./00-variables.sh
5 |
6 | # Print the menu
7 | echo "====================================="
8 | echo "Create Azure Container App Job (1-4): "
9 | echo "====================================="
10 | options=(
11 | "Sender Job"
12 | "Processor Job"
13 | "Receiver Job"
14 | "Quit")
15 | name=""
16 | # Select an option
17 | COLUMNS=0
18 | select option in "${options[@]}"; do
19 | case $option in
20 | "Sender Job")
21 | # Retrieve the resource id of the user-assigned managed identity
22 | echo "Retrieving the resource id for the [$managedIdentityName] managed identity..."
23 | managedIdentityId=$(az identity show \
24 | --name $managedIdentityName \
25 | --resource-group $resourceGroupName \
26 | --query id \
27 | --output tsv)
28 |
29 | if [[ -n $managedIdentityId ]]; then
30 | echo "[$managedIdentityId] resource id for the [$managedIdentityName] managed identity successfully retrieved"
31 | else
32 | echo "Failed to retrieve the resource id for the [$managedIdentityName] managed identity"
33 | exit
34 | fi
35 |
36 | # Retrieve the client id of the user-assigned managed identity
37 | echo "Retrieving the client id for the [$managedIdentityName] managed identity..."
38 | clientId=$(az identity show \
39 | --name $managedIdentityName \
40 | --resource-group $resourceGroupName \
41 | --query clientId \
42 | --output tsv)
43 |
44 | if [[ -n $clientId ]]; then
45 | echo "[$clientId] client id for the [$managedIdentityName] managed identity successfully retrieved"
46 | else
47 | echo "Failed to retrieve the client id for the [$managedIdentityName] managed identity"
48 | exit
49 | fi
50 |
51 | # Retrieve the ACR loginServer
52 | echo "Retrieving the loginServer for the [$acrName] container registry..."
53 | loginServer=$(az acr show \
54 | --name $acrName \
55 | --resource-group $resourceGroupName \
56 | --query loginServer \
57 | --output tsv)
58 |
59 | if [[ -n $loginServer ]]; then
60 | echo "[$loginServer] loginServer for the [$acrName] container registry successfully retrieved"
61 | else
62 | echo "Failed to retrieve the loginServer for the [$acrName] container registry"
63 | exit
64 | fi
65 |
66 | # Create the sender job
67 | echo "Retrieving the [${senderJobName,,}] job..."
68 | az containerapp job show \
69 | --name ${senderJobName,,} \
70 | --resource-group $resourceGroupName &>/dev/null
71 | if [[ $? != 0 ]]; then
72 | echo "No [${senderJobName,,}] job actually exists in the [$environmentName] environment"
73 | echo "Creating [${senderJobName,,}] job in the [$environmentName] environment..."
74 | az containerapp job create \
75 | --name ${senderJobName,,} \
76 | --resource-group $resourceGroupName \
77 | --environment $environmentName \
78 | --trigger-type $senderJobTriggerType \
79 | --replica-timeout $replicaTimeout \
80 | --replica-retry-limit $replicaRetryLimit \
81 | --replica-completion-count $senderJobReplicaCompletionCount \
82 | --parallelism $senderJobParallelism \
83 | --image "${loginServer}/${senderJobImageName}:${senderJobImageTag}" \
84 | --cpu $cpu \
85 | --memory $memory \
86 | --registry-identity $managedIdentityId \
87 | --registry-server $loginServer \
88 | --env-vars \
89 | AZURE_CLIENT_ID=$clientId \
90 | FULLY_QUALIFIED_NAMESPACE="${serviceBusNamespace}.servicebus.windows.net" \
91 | INPUT_QUEUE_NAME=$inputQueueName \
92 | MIN_NUMBER="$senderJobMinNumber" \
93 | MAX_NUMBER="$senderJobMaxNumber" \
94 | MESSAGE_COUNT="$senderJobMessageCount" \
95 | SEND_TYPE="$senderJobSendType" 1>/dev/null
96 | if [[ $? == 0 ]]; then
97 | echo "[$environmentName] environment successfully created in the [$subscriptionName] subscription"
98 | else
99 | echo "Failed to create [$environmentName] environment in the [$subscriptionName] subscription"
100 | exit
101 | fi
102 | else
103 | echo "[$environmentName] environment already contains a [${senderJobName,,}] job"
104 | fi
105 | break
106 | ;;
107 | "Processor Job")
108 | # Retrieve the resource id of the user-assigned managed identity
109 | echo "Retrieving the resource id for the [$managedIdentityName] managed identity..."
110 | managedIdentityId=$(az identity show \
111 | --name $managedIdentityName \
112 | --resource-group $resourceGroupName \
113 | --query id \
114 | --output tsv)
115 |
116 | if [[ -n $managedIdentityId ]]; then
117 | echo "[$managedIdentityId] resource id for the [$managedIdentityName] managed identity successfully retrieved"
118 | else
119 | echo "Failed to retrieve the resource id for the [$managedIdentityName] managed identity"
120 | exit
121 | fi
122 |
123 | # Retrieve the client id of the user-assigned managed identity
124 | echo "Retrieving the client id for the [$managedIdentityName] managed identity..."
125 | clientId=$(az identity show \
126 | --name $managedIdentityName \
127 | --resource-group $resourceGroupName \
128 | --query clientId \
129 | --output tsv)
130 |
131 | if [[ -n $clientId ]]; then
132 | echo "[$clientId] client id for the [$managedIdentityName] managed identity successfully retrieved"
133 | else
134 | echo "Failed to retrieve the client id for the [$managedIdentityName] managed identity"
135 | exit
136 | fi
137 |
138 | # Retrieve the ACR loginServer
139 | echo "Retrieving the loginServer for the [$acrName] container registry..."
140 | loginServer=$(az acr show \
141 | --name $acrName \
142 | --resource-group $resourceGroupName \
143 | --query loginServer \
144 | --output tsv)
145 |
146 | if [[ -n $loginServer ]]; then
147 | echo "[$loginServer] loginServer for the [$acrName] container registry successfully retrieved"
148 | else
149 | echo "Failed to retrieve the loginServer for the [$acrName] container registry"
150 | exit
151 | fi
152 |
153 | # Create the processor job
154 | echo "Retrieving the [${processorJobName,,}] job..."
155 | az containerapp job show \
156 | --name ${processorJobName,,} \
157 | --resource-group $resourceGroupName &>/dev/null
158 | if [[ $? != 0 ]]; then
159 | echo "No [${processorJobName,,}] job actually exists in the [$environmentName] environment"
160 | echo "Creating [${processorJobName,,}] job in the [$environmentName] environment..."
161 | az containerapp job create \
162 | --name ${processorJobName,,} \
163 | --resource-group $resourceGroupName \
164 | --environment $environmentName \
165 | --trigger-type $processorJobTriggerType \
166 | --replica-timeout $replicaTimeout \
167 | --replica-retry-limit $replicaRetryLimit \
168 | --replica-completion-count $processorJobReplicaCompletionCount \
169 | --parallelism $processorJobParallelism \
170 | --cron-expression "$processorJobCronExpression" \
171 | --image "${loginServer}/${processorJobImageName}:${processorJobImageTag}" \
172 | --cpu $cpu \
173 | --memory $memory \
174 | --registry-identity $managedIdentityId \
175 | --registry-server $loginServer \
176 | --env-vars \
177 | AZURE_CLIENT_ID=$clientId \
178 | FULLY_QUALIFIED_NAMESPACE="${serviceBusNamespace}.servicebus.windows.net" \
179 | INPUT_QUEUE_NAME=$inputQueueName \
180 | OUTPUT_QUEUE_NAME=$outputQueueName \
181 | MAX_MESSAGE_COUNT="$processorMaxMessageCount" \
182 | MAX_WAIT_TIME="$processorMaxWaitTime" 1>/dev/null
183 | if [[ $? == 0 ]]; then
184 | echo "[$environmentName] environment successfully created in the [$subscriptionName] subscription"
185 | else
186 | echo "Failed to create [$environmentName] environment in the [$subscriptionName] subscription"
187 | exit
188 | fi
189 | else
190 | echo "[$environmentName] environment already contains a [${processorJobName,,}] job"
191 | fi
192 | break
193 | ;;
194 | "Receiver Job")
195 | # Retrieve the resource id of the user-assigned managed identity
196 | echo "Retrieving the resource id for the [$managedIdentityName] managed identity..."
197 | managedIdentityId=$(az identity show \
198 | --name $managedIdentityName \
199 | --resource-group $resourceGroupName \
200 | --query id \
201 | --output tsv)
202 |
203 | if [[ -n $managedIdentityId ]]; then
204 | echo "[$managedIdentityId] resource id for the [$managedIdentityName] managed identity successfully retrieved"
205 | else
206 | echo "Failed to retrieve the resource id for the [$managedIdentityName] managed identity"
207 | exit
208 | fi
209 |
210 | # Retrieve the client id of the user-assigned managed identity
211 | echo "Retrieving the client id for the [$managedIdentityName] managed identity..."
212 | clientId=$(az identity show \
213 | --name $managedIdentityName \
214 | --resource-group $resourceGroupName \
215 | --query clientId \
216 | --output tsv)
217 |
218 | if [[ -n $clientId ]]; then
219 | echo "[$clientId] client id for the [$managedIdentityName] managed identity successfully retrieved"
220 | else
221 | echo "Failed to retrieve the client id for the [$managedIdentityName] managed identity"
222 | exit
223 | fi
224 |
225 | # Retrieve the ACR loginServer
226 | echo "Retrieving the loginServer for the [$acrName] container registry..."
227 | loginServer=$(az acr show \
228 | --name $acrName \
229 | --resource-group $resourceGroupName \
230 | --query loginServer \
231 | --output tsv)
232 |
233 | if [[ -n $loginServer ]]; then
234 | echo "[$loginServer] loginServer for the [$acrName] container registry successfully retrieved"
235 | else
236 | echo "Failed to retrieve the loginServer for the [$acrName] container registry"
237 | exit
238 | fi
239 |
240 | # Retrieve the Service Bus namespace connection string
241 | echo "Retrieving the connection string for the [$serviceBusNamespace] Service Bus namespace..."
242 | serviceBusConnectionString=$(az servicebus namespace authorization-rule keys list \
243 | --name RootManageSharedAccessKey \
244 | --namespace-name $serviceBusNamespace \
245 | --resource-group $resourceGroupName \
246 | --query primaryConnectionString \
247 | --output tsv)
248 | if [[ -n $serviceBusConnectionString ]]; then
249 | echo "[$serviceBusConnectionString] connection string for the [$serviceBusNamespace] Service Bus namespace successfully retrieved"
250 | else
251 | echo "Failed to retrieve the connection string for the [$serviceBusNamespace] Service Bus namespace"
252 | exit
253 | fi
254 |
255 | # Create the receiver job
256 | echo "Retrieving the [${receiverJobName,,}] job..."
257 | az containerapp job show \
258 | --name ${receiverJobName,,} \
259 | --resource-group $resourceGroupName &>/dev/null
260 | if [[ $? != 0 ]]; then
261 | echo "No [${receiverJobName,,}] job actually exists in the [$environmentName] environment"
262 | echo "Creating [${receiverJobName,,}] job in the [$environmentName] environment..."
263 | az containerapp job create \
264 | --name ${receiverJobName,,} \
265 | --resource-group $resourceGroupName \
266 | --environment $environmentName \
267 | --trigger-type $receiverJobTriggerType \
268 | --replica-timeout $replicaTimeout \
269 | --replica-retry-limit $replicaRetryLimit \
270 | --replica-completion-count $receiverJobReplicaCompletionCount \
271 | --parallelism $receiverJobParallelism \
272 | --image "${loginServer}/${receiverJobImageName}:${receiverJobImageTag}" \
273 | --cpu $cpu \
274 | --memory $memory \
275 | --min-executions $receiverMinExecutions \
276 | --max-executions $receiverMaxExecutions \
277 | --registry-identity $managedIdentityId \
278 | --registry-server $loginServer \
279 | --secrets service-bus-connection-string="$serviceBusConnectionString" \
280 | --env-vars \
281 | AZURE_CLIENT_ID=$clientId \
282 | FULLY_QUALIFIED_NAMESPACE="${serviceBusNamespace}.servicebus.windows.net" \
283 | OUTPUT_QUEUE_NAME=$outputQueueName \
284 | MAX_MESSAGE_COUNT="$receiverMaxMessageCount" \
285 | MAX_WAIT_TIME="$receiverMaxWaitTime" \
286 | --scale-rule-name azure-servicebus-queue-rule \
287 | --scale-rule-type azure-servicebus \
288 | --scale-rule-metadata "queueName=$outputQueueName" \
289 | "namespace=$serviceBusNamespace" \
290 | "messageCount=$receiverMessageCount" \
291 | --scale-rule-auth "connection=service-bus-connection-string" 1>/dev/null
292 | if [[ $? == 0 ]]; then
293 | echo "[$environmentName] environment successfully created in the [$subscriptionName] subscription"
294 | else
295 | echo "Failed to create [$environmentName] environment in the [$subscriptionName] subscription"
296 | exit
297 | fi
298 | else
299 | echo "[$environmentName] environment already contains a [${receiverJobName,,}] job"
300 | fi
301 | break
302 | ;;
303 | "Quit")
304 | exit
305 | ;;
306 | *) echo "invalid option $REPLY" ;;
307 | esac
308 | done
309 |
--------------------------------------------------------------------------------
/scripts/02-start-job.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Variables
4 | source ./00-variables.sh
5 |
6 | # Print the menu
7 | echo "===================================="
8 | echo "Start Azure Container App Job (1-7): "
9 | echo "===================================="
10 | options=(
11 | "Bicep Sender Job"
12 | "Bicep Processor Job"
13 | "Bicep Receiver Job"
14 | "Azure CLI Sender Job"
15 | "Azure CLI Processor Job"
16 | "Azure CLI Receiver Job"
17 | "Quit"
18 | )
19 | name=""
20 | # Select an option
21 | COLUMNS=0
22 | select option in "${options[@]}"; do
23 | case $option in
24 | "Bicep Sender Job")
25 | name=$bicepSenderJobName
26 | break
27 | ;;
28 | "Bicep Processor Job")
29 | name=$bicepProcessorJobName
30 | break
31 | ;;
32 | "Bicep Receiver Job")
33 | name=$bicepReceiverJobName
34 | break
35 | ;;
36 | "Azure CLI Sender Job")
37 | name=$senderJobName
38 | break
39 | ;;
40 | "Azure CLI Processor Job")
41 | name=$processorJobName
42 | break
43 | ;;
44 | "Azure CLI Receiver Job")
45 | name=$receiverJobName
46 | break
47 | ;;
48 | "Quit")
49 | exit
50 | ;;
51 | *) echo "invalid option $REPLY" ;;
52 | esac
53 | done
54 |
55 | # Start job
56 | echo "Starting the [$name] job..."
57 | az containerapp job start --name $name --resource-group $resourceGroupName
58 | if [[ $? == 0 ]]; then
59 | echo "[$name] job successfully started"
60 | else
61 | echo "Failed to start the [$name] job"
62 | fi
--------------------------------------------------------------------------------
/scripts/03-view-managed-identity.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # For more information on Azure Container App Managed Identity, see:
4 | # - https://learn.microsoft.com/en-us/azure/container-apps/managed-identity?tabs=portal%2Cpython
5 | # - https://learn.microsoft.com/en-us/azure/container-apps/managed-identity-image-pull?tabs=azure-cli&pivots=azure-portal
6 | # - https://azuresdkdocs.blob.core.windows.net/$web/dotnet/Azure.Identity/1.0.0/api/index.html
7 | # - https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/managed-identities-status
8 | # - https://pypi.org/project/azure-identity/
9 |
10 | # Variables
11 | source ./00-variables.sh
12 |
13 | # Print the menu
14 | echo "========================================================"
15 | echo "View Managed Identity for Azure Container App Job (1-7): "
16 | echo "========================================================"
17 | options=("Bicep Sender Job"
18 | "Bicep Processor Job"
19 | "Bicep Receiver Job"
20 | "Azure CLI Sender Job"
21 | "Azure CLI Processor Job"
22 | "Azure CLI Receiver Job"
23 | "Quit")
24 | name=""
25 | # Select an option
26 | COLUMNS=0
27 | select option in "${options[@]}"; do
28 | case $option in
29 | "Bicep Sender Job")
30 | name=$bicepSenderJobName
31 | break
32 | ;;
33 | "Bicep Processor Job")
34 | name=$bicepProcessorJobName
35 | break
36 | ;;
37 | "Bicep Receiver Job")
38 | name=$bicepReceiverJobName
39 | break
40 | ;;
41 | "Azure CLI Sender Job")
42 | name=$senderJobName
43 | break
44 | ;;
45 | "Azure CLI Processor Job")
46 | name=$processorJobName
47 | break
48 | ;;
49 | "Azure CLI Receiver Job")
50 | name=$receiverJobName
51 | break
52 | ;;
53 | "Quit")
54 | exit
55 | ;;
56 | *) echo "invalid option $REPLY" ;;
57 | esac
58 | done
59 |
60 | # View Azure Containers App Job Managed Identity
61 | echo "Viewing the [$name] job managed identity..."
62 | az containerapp job identity show --name $name -g $resourceGroupName
--------------------------------------------------------------------------------
/src/.env:
--------------------------------------------------------------------------------
1 | FULLY_QUALIFIED_NAMESPACE=paolosalvatori.servicebus.windows.net
2 | INPUT_QUEUE_NAME=inputqueue
3 | OUTPUT_QUEUE_NAME=outputqueue
4 | MAX_WAIT_TIME=5
5 | MAX_MESSAGE_COUNT=20
6 | MIN_NUMBER=1
7 | MAX_NUMBER=20
8 | MESSAGE_COUNT=100
9 | SEND_TYPE=list
--------------------------------------------------------------------------------
/src/.local:
--------------------------------------------------------------------------------
1 | AZURE_CLIENT_ID=
2 | AZURE_CLIENT_SECRET=
3 | AZURE_TENANT_ID=
--------------------------------------------------------------------------------
/src/00-variables.sh:
--------------------------------------------------------------------------------
1 | # Variables
2 | prefix="Gundam"
3 | acrName="${prefix}Acr"
4 | acrResourceGrougName="${prefix}RG"
5 | location="northeurope"
6 | attachAcr=false
7 | senderImageName="sbsender"
8 | processorImageName="sbprocessor"
9 | receiverImageName="sbreceiver"
10 | images=($senderImageName $processorImageName $receiverImageName)
11 | filenames=(sbsender.py sbprocessor.py sbreceiver.py)
12 | tag="v1"
13 |
14 | # Azure Subscription and Tenant
15 | subscriptionId=$(az account show --query id --output tsv)
16 | subscriptionName=$(az account show --query name --output tsv)
17 | tenantId=$(az account show --query tenantId --output tsv)
18 |
--------------------------------------------------------------------------------
/src/01-build-docker-images.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Variables
4 | source ./00-variables.sh
5 |
6 | # Use a for loop to build the docker images using the array index
7 | for index in ${!images[@]}; do
8 | # Build the docker image
9 | docker build -t ${images[$index]}:$tag -f Dockerfile --build-arg FILENAME=${filenames[$index]} .
10 | done
--------------------------------------------------------------------------------
/src/02-run-docker-container.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Variables
4 | source ./00-variables.sh
5 |
6 | # Print the menu
7 | echo "===================================="
8 | echo "Run Docker Container (1-4): "
9 | echo "===================================="
10 | options=(
11 | "Sender"
12 | "Processor"
13 | "Receiver"
14 | "Quit"
15 | )
16 | name=""
17 | # Select an option
18 | COLUMNS=0
19 | select option in "${options[@]}"; do
20 | case $option in
21 | "Sender")
22 | docker run -it \
23 | --rm \
24 | --env-file .env \
25 | --env-file .local \
26 | --name $senderImageName \
27 | $senderImageName:$tag
28 | break
29 | ;;
30 | "Processor")
31 | docker run -it \
32 | --rm \
33 | --env-file .env \
34 | --env-file .local \
35 | --name $processorImageName \
36 | $processorImageName:$tag
37 | break
38 | ;;
39 | "Receiver")
40 | docker run -it \
41 | --rm \
42 | --env-file .env \
43 | --env-file .local \
44 | --name $receiverImageName \
45 | $receiverImageName:$tag
46 | break
47 | ;;
48 | "Quit")
49 | exit
50 | ;;
51 | *) echo "invalid option $REPLY" ;;
52 | esac
53 | done
--------------------------------------------------------------------------------
/src/03-push-docker-image.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Variables
4 | source ./00-variables.sh
5 |
6 | # Login to ACR
7 | az acr login --name $acrName
8 |
9 | # Retrieve ACR login server. Each container image needs to be tagged with the loginServer name of the registry.
10 | loginServer=$(az acr show --name $acrName --query loginServer --output tsv)
11 |
12 | # Use a for loop to tag and push the local docker images to the Azure Container Registry
13 | for index in ${!images[@]}; do
14 | # Tag the local sender image with the loginServer of ACR
15 | docker tag ${images[$index],,}:$tag $loginServer/${images[$index],,}:$tag
16 |
17 | # Push the container image to ACR
18 | docker push $loginServer/${images[$index],,}:$tag
19 | done
--------------------------------------------------------------------------------
/src/Dockerfile:
--------------------------------------------------------------------------------
1 | # app/Dockerfile
2 |
3 | # # Stage 1 - Install build dependencies
4 |
5 | # A Dockerfile must start with a FROM instruction which sets the base image for the container.
6 | # The Python images come in many flavors, each designed for a specific use case.
7 | # The python:3.11-slim image is a good base image for most applications.
8 | # It is a minimal image built on top of Debian Linux and includes only the necessary packages to run Python.
9 | # The slim image is a good choice because it is small and contains only the packages needed to run Python.
10 | # For more information, see:
11 | # * https://hub.docker.com/_/python
12 | # * https://docs.streamlit.io/knowledge-base/tutorials/deploy/docker
13 | FROM python:3.11-slim AS builder
14 |
15 | # The WORKDIR instruction sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it in the Dockerfile.
16 | # If the WORKDIR doesn’t exist, it will be created even if it’s not used in any subsequent Dockerfile instruction.
17 | # For more information, see: https://docs.docker.com/engine/reference/builder/#workdir
18 | WORKDIR /app
19 |
20 | # Set environment variables.
21 | # The ENV instruction sets the environment variable to the value .
22 | # This value will be in the environment of all “descendant” Dockerfile commands and can be replaced inline in many as well.
23 | # For more information, see: https://docs.docker.com/engine/reference/builder/#env
24 | ENV PYTHONDONTWRITEBYTECODE 1
25 | ENV PYTHONUNBUFFERED 1
26 |
27 | # Install git so that we can clone the app code from a remote repo using the RUN instruction.
28 | # The RUN comand has 2 forms:
29 | # * RUN (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows)
30 | # * RUN ["executable", "param1", "param2"] (exec form)
31 | # The RUN instruction will execute any commands in a new layer on top of the current image and commit the results.
32 | # The resulting committed image will be used for the next step in the Dockerfile.
33 | # For more information, see: https://docs.docker.com/engine/reference/builder/#run
34 | RUN apt-get update && apt-get install -y \
35 | build-essential \
36 | curl \
37 | software-properties-common \
38 | git \
39 | && rm -rf /var/lib/apt/lists/*
40 |
41 | # Create a virtualenv to keep dependencies together
42 | RUN python -m venv /opt/venv
43 | ENV PATH="/opt/venv/bin:$PATH"
44 |
45 | # Clone the requirements.txt which contains dependencies to WORKDIR
46 | # COPY has two forms:
47 | # * COPY (this copies the files from the local machine to the container's own filesystem)
48 | # * COPY ["",... ""] (this form is required for paths containing whitespace)
49 | # For more information, see: https://docs.docker.com/engine/reference/builder/#copy
50 | COPY requirements.txt .
51 |
52 | # Install the Python dependencies
53 | RUN pip install --no-cache-dir --no-deps -r requirements.txt
54 |
55 | # Stage 2 - Copy only necessary files to the runner stage
56 |
57 | # The FROM instruction initializes a new build stage for the application
58 | FROM python:3.11-slim
59 |
60 | # Define the filename to copy as an argument
61 | ARG FILENAME
62 |
63 | # Set an environment variable
64 | ENV FILENAME=${FILENAME}
65 |
66 | # Sets the working directory to /app
67 | WORKDIR /app
68 |
69 | # Copy the virtual environment from the builder stage
70 | COPY --from=builder /opt/venv /opt/venv
71 |
72 | # Set environment variables
73 | ENV PATH="/opt/venv/bin:$PATH"
74 |
75 | # Clone the ${FILENAME} containing the application code
76 | COPY $FILENAME .
77 |
78 | # The ENTRYPOINT instruction has two forms:
79 | # * ENTRYPOINT ["executable", "param1", "param2"] (exec form, preferred)
80 | # * ENTRYPOINT command param1 param2 (shell form)
81 | # The ENTRYPOINT instruction allows you to configure a container that will run as an executable.
82 | # For more information, see: https://docs.docker.com/engine/reference/builder/#entrypoint
83 | ENTRYPOINT python $FILENAME
--------------------------------------------------------------------------------
/src/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp==3.8.4
2 | aiosignal==1.3.1
3 | async-timeout==4.0.2
4 | attrs==23.1.0
5 | azure-core==1.26.4
6 | azure-identity==1.13.0
7 | azure-servicebus==7.10.0
8 | certifi==2023.5.7
9 | cffi==1.15.1
10 | charset-normalizer==3.1.0
11 | cryptography==41.0.0
12 | frozenlist==1.3.3
13 | idna==3.4
14 | isodate==0.6.1
15 | msal==1.22.0
16 | msal-extensions==1.0.0
17 | multidict==6.0.4
18 | portalocker==2.7.0
19 | pycparser==2.21
20 | PyJWT==2.7.0
21 | python-dotenv==1.0.0
22 | requests==2.31.0
23 | six==1.16.0
24 | typing_extensions==4.6.2
25 | urllib3==2.0.2
26 | yarl==1.9.2
27 |
--------------------------------------------------------------------------------
/src/sbprocessor.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # --------------------------------------------------------------------------------------------
4 | # Copyright (c) 2023 Paolo Salvatori
5 | # Microsoft Corporation. All rights reserved.
6 | # Licensed under the MIT License. See License.txt in the project root for license information.
7 | # --------------------------------------------------------------------------------------------
8 |
9 | import os
10 | import asyncio
11 | from azure.servicebus.aio import ServiceBusClient
12 | from azure.servicebus import ServiceBusMessage
13 | from azure.identity.aio import DefaultAzureCredential
14 | from dotenv import load_dotenv
15 | from dotenv import dotenv_values
16 |
17 | # Load environment variables from .env file
18 | if os.path.exists(".env"):
19 | load_dotenv(override=True)
20 | config = dotenv_values(".env")
21 |
22 | # Read environment variables
23 | fully_qualified_namespace = os.getenv("FULLY_QUALIFIED_NAMESPACE")
24 | input_queue_name = os.getenv("INPUT_QUEUE_NAME")
25 | output_queue_name = os.getenv("OUTPUT_QUEUE_NAME")
26 | max_message_count = int(os.getenv("MAX_MESSAGE_COUNT", 20))
27 | max_wait_time = int(os.getenv("MAX_WAIT_TIME", 5))
28 |
29 | # Print environment variables
30 | print(f"FULLY_QUALIFIED_NAMESPACE: {fully_qualified_namespace}")
31 | print(f"INPUT_QUEUE_NAME: {input_queue_name}")
32 | print(f"OUTPUT_QUEUE_NAME: {output_queue_name}")
33 | print(f"MAX_MESSAGE_COUNT: {max_message_count}")
34 | print(f"MAX_WAIT_TIME: {max_wait_time}")
35 |
36 | # Get credential object
37 | credential = DefaultAzureCredential()
38 |
39 | async def fibonacci(n):
40 | if n <= 0:
41 | raise ValueError("n must be a positive integer")
42 | elif n == 1:
43 | return 0
44 | elif n == 2:
45 | return 1
46 | else:
47 | fib1 = await fibonacci(n - 1)
48 | fib2 = await fibonacci(n - 2)
49 | return fib1 + fib2
50 |
51 | async def send_message(message_text: str, i: int):
52 | # Check that the message is not empty
53 | if message_text:
54 | try:
55 | # Create a Service Bus client using the credential
56 | async with ServiceBusClient(
57 | fully_qualified_namespace = fully_qualified_namespace,
58 | credential = credential,
59 | logging_enable = True) as servicebus_client:
60 | # Get a Queue Sender object to send messages to the output queue
61 | sender = servicebus_client.get_queue_sender(queue_name = output_queue_name)
62 | async with sender:
63 | # Create a Service Bus message and send it to the queue
64 | message = ServiceBusMessage(message_text)
65 | # Send a message to the output queue
66 | await sender.send_messages(message)
67 | print(f"[{i}] Sent result message: {message_text}")
68 | except Exception as e:
69 | print(f"An error occurred while sending [{i}] message to the {output_queue_name} queue: {e}")
70 | else:
71 | print(f"The [{i}] message is empty. Please, enter a valid message.")
72 |
73 | async def receive_messages():
74 | # create a Service Bus client using the connection string
75 | async with ServiceBusClient(
76 | fully_qualified_namespace = fully_qualified_namespace,
77 | credential = credential,
78 | logging_enable = False) as servicebus_client:
79 |
80 | async with servicebus_client:
81 | # Get the Queue Receiver object for the input queue
82 | receiver = servicebus_client.get_queue_receiver(queue_name = input_queue_name)
83 | async with receiver:
84 | try:
85 | received_msgs = await receiver.receive_messages(max_wait_time = max_wait_time, max_message_count = max_message_count)
86 | i = 0
87 | for msg in received_msgs:
88 | # Check if message contains an integer value
89 | try:
90 | n = int(str(msg))
91 | i += 1
92 | print(f"[{i}] Received Message: {n}")
93 | # Calculate Fibonacci number
94 | result = await fibonacci(n)
95 | print(f"[{i}] The Fibonacci number for {n} is {result}")
96 | # Send result to the output queue
97 | await send_message(str(result), i)
98 | except ValueError:
99 | print(f"[{i}] Received message {str(msg)} is not an integer number")
100 | continue
101 | finally:
102 | # Complete the message so that the message is removed from the queue
103 | await receiver.complete_message(msg)
104 | print(f"[{i}] Completed message: {str(msg)}")
105 | except Exception as e:
106 | print(f"An error occurred while receiving messages from the {input_queue_name} queue: {e}")
107 |
108 | # Receive messages from the input queue
109 | asyncio.run(receive_messages())
110 |
111 | # Close credential object when it's no longer needed
112 | asyncio.run(credential.close())
--------------------------------------------------------------------------------
/src/sbreceiver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # --------------------------------------------------------------------------------------------
4 | # Copyright (c) 2023 Paolo Salvatori
5 | # Microsoft Corporation. All rights reserved.
6 | # Licensed under the MIT License. See License.txt in the project root for license information.
7 | # --------------------------------------------------------------------------------------------
8 |
9 | import os
10 | import asyncio
11 | from azure.servicebus.aio import ServiceBusClient
12 | from azure.identity.aio import DefaultAzureCredential
13 | from dotenv import load_dotenv
14 | from dotenv import dotenv_values
15 |
16 | # Load environment variables from .env file
17 | if os.path.exists(".env"):
18 | load_dotenv(override=True)
19 | config = dotenv_values(".env")
20 |
21 | # Read environment variables
22 | fully_qualified_namespace = os.getenv("FULLY_QUALIFIED_NAMESPACE")
23 | queue_name = os.getenv("OUTPUT_QUEUE_NAME")
24 | max_message_count = int(os.getenv("MAX_MESSAGE_COUNT", 20))
25 | max_wait_time = int(os.getenv("MAX_WAIT_TIME", 5))
26 |
27 | # Print environment variables
28 | print(f"FULLY_QUALIFIED_NAMESPACE: {fully_qualified_namespace}")
29 | print(f"OUTPUT_QUEUE_NAME: {queue_name}")
30 | print(f"MAX_MESSAGE_COUNT: {max_message_count}")
31 | print(f"MAX_WAIT_TIME: {max_wait_time}")
32 |
33 | # Get credential object
34 | credential = DefaultAzureCredential()
35 |
36 | async def receive_messages():
37 | # create a Service Bus client using the connection string
38 | async with ServiceBusClient(
39 | fully_qualified_namespace = fully_qualified_namespace,
40 | credential = credential,
41 | logging_enable = False) as servicebus_client:
42 |
43 | async with servicebus_client:
44 | # Get the Queue Receiver object for the input queue
45 | receiver = servicebus_client.get_queue_receiver(queue_name = queue_name)
46 | async with receiver:
47 | i = 0
48 | # Receive a batch of messages until the queue is empty
49 | while True:
50 | try:
51 | received_msgs = await receiver.receive_messages(max_wait_time = max_wait_time, max_message_count = max_message_count)
52 | if len(received_msgs) == 0:
53 | break
54 | for msg in received_msgs:
55 | # Check if message contains an integer value
56 | try:
57 | n = int(str(msg))
58 | i += 1
59 | print(f"[{i}] Received Message: {n}")
60 | except ValueError:
61 | print(f"[{i}] Received message {str(msg)} is not an integer number")
62 | continue
63 | finally:
64 | # Complete the message so that the message is removed from the queue
65 | await receiver.complete_message(msg)
66 | print(f"[{i}] Completed message: {str(msg)}")
67 | except Exception as e:
68 | print(f"An error occurred while receiving messages from the {queue_name} queue: {e}")
69 | break
70 |
71 | # Receive messages from the input queue
72 | asyncio.run(receive_messages())
73 |
74 | # Close credential object when it's no longer needed
75 | asyncio.run(credential.close())
--------------------------------------------------------------------------------
/src/sbsender.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # --------------------------------------------------------------------------------------------
4 | # Copyright (c) 2023 Paolo Salvatori
5 | # Microsoft Corporation. All rights reserved.
6 | # Licensed under the MIT License. See License.txt in the project root for license information.
7 | # --------------------------------------------------------------------------------------------
8 |
9 | import os
10 | import asyncio
11 | import random
12 | from azure.servicebus.aio import ServiceBusClient
13 | from azure.servicebus import ServiceBusMessage
14 | from azure.identity.aio import DefaultAzureCredential
15 | from dotenv import load_dotenv
16 | from dotenv import dotenv_values
17 |
18 | # Load environment variables from .env file
19 | if os.path.exists(".env"):
20 | load_dotenv(override = True)
21 | config = dotenv_values(".env")
22 |
23 | # Read environment variables
24 | fully_qualified_namespace = os.getenv("FULLY_QUALIFIED_NAMESPACE")
25 | queue_name = os.getenv("INPUT_QUEUE_NAME")
26 | min_number = int(os.getenv("MIN_NUMBER", 1))
27 | max_number = int(os.getenv("MAX_NUMBER", 10))
28 | message_count = int(os.getenv("MESSAGE_COUNT", 100))
29 | send_type = os.getenv("SEND_TYPE", "list")
30 |
31 | # Print environment variables
32 | print(f"FULLY_QUALIFIED_NAMESPACE: {fully_qualified_namespace}")
33 | print(f"INPUT_QUEUE_NAME: {queue_name}")
34 | print(f"MIN_NUMBER: {min_number}")
35 | print(f"MAX_NUMBER: {max_number}")
36 | print(f"MESSAGE_COUNT: {message_count}")
37 | print(f"SEND_TYPE: {send_type}")
38 |
39 | # Get credential object
40 | credential = DefaultAzureCredential()
41 |
42 | async def send_a_list_of_messages(sender):
43 | try:
44 | # Create a list of messages and send it to the queue
45 | messages = [ServiceBusMessage(f"{random.randint(min_number, max_number)}") for _ in range(message_count)]
46 | await sender.send_messages(messages)
47 | print(f"Sent a list of {message_count} messages to the {queue_name} queue")
48 | except Exception as e:
49 | print(f"An error occurred while sending a list of message to the {queue_name} queue: {e}")
50 |
51 | async def send_batch_message(sender):
52 | # Create a batch of messages
53 | async with sender:
54 | batch_message = await sender.create_message_batch()
55 | for _ in range(message_count):
56 | try:
57 | # Add a message to the batch
58 | batch_message.add_message(ServiceBusMessage(f"{random.randint(min_number, max_number)}"))
59 | except Exception as e:
60 | print(f"An error occurred while creating a batch of messages: {e}")
61 | break
62 | # Send the batch of messages to the queue
63 | try:
64 | await sender.send_messages(batch_message)
65 | print(f"Sent a batch of {message_count} messages to the {queue_name} queue")
66 | except Exception as e:
67 | print(f"An error occurred while sending a batch of message to the {queue_name} queue: {e}")
68 |
69 | async def run():
70 | # create a Service Bus client using the credential
71 | async with ServiceBusClient(
72 | fully_qualified_namespace = fully_qualified_namespace,
73 | credential = credential,
74 | logging_enable = False) as servicebus_client:
75 | # get a Queue Sender object to send messages to the queue
76 | sender = servicebus_client.get_queue_sender(queue_name = queue_name)
77 | async with sender:
78 | if send_type == "list":
79 | await send_a_list_of_messages(sender)
80 | elif send_type == "batch":
81 | await send_batch_message(sender)
82 | else:
83 | print(f"Invalid send type: {send_type}")
84 |
85 | # Send messages to the input queue
86 | asyncio.run(run())
87 |
88 | # Close credential object when it's no longer needed
89 | asyncio.run(credential.close())
--------------------------------------------------------------------------------
/visio/aca-jobs.vsdx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/container-apps-jobs/2d9e50554a1ad635a3eb3ac17136e80bdf6db500/visio/aca-jobs.vsdx
--------------------------------------------------------------------------------